├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── blueprints ├── hellovcloud │ └── blueprint.yaml ├── public_ip │ └── blueprint.yaml ├── sample-input-ip.json └── sample-input.json ├── dev-requirements.txt ├── examples ├── blueprint.yaml ├── integration_test_env.example ├── vcloud_config.json.example ├── vcloud_config.yaml.example ├── vcloud_config_ondemand.yaml.example ├── vcloud_integration_test_config.json.example └── vcloud_integration_test_config.yaml.example ├── manager_blueprint ├── inputs.json.example ├── inputs.yaml.example ├── scripts │ ├── configure.py │ └── configure_docker.py ├── vcloud-manager-blueprint.yaml └── vcloud.yaml ├── network_plugin ├── __init__.py ├── floatingip.py ├── keypair.py ├── network.py ├── port.py ├── public_nat.py ├── security_group.py └── utils.py ├── plugin.yaml ├── server_plugin ├── __init__.py ├── server.py └── volume.py ├── setup.py ├── system_tests ├── __init__.py └── vcloud_handler.py ├── test-requirements.txt ├── tests ├── __init__.py ├── integration │ ├── __init__.py │ ├── run_all_tests.py │ ├── test_combined.py │ ├── test_network_plugin.py │ └── test_server_plugin.py ├── syntax_check.py └── unittests │ ├── __init__.py │ ├── test_mock_base.py │ ├── test_mock_network_plugin.py │ ├── test_mock_network_plugin_floatingip.py │ ├── test_mock_network_plugin_keypair.py │ ├── test_mock_network_plugin_network.py │ ├── test_mock_network_plugin_network_subroutes.py │ ├── test_mock_network_plugin_port.py │ ├── test_mock_network_plugin_public_nat.py │ ├── test_mock_network_plugin_security_group.py │ ├── test_mock_server_plugin_server.py │ ├── test_mock_server_plugin_server_subroutes.py │ ├── test_mock_server_plugin_volume.py │ ├── test_mock_vcloud_plugin_common.py │ └── test_mock_vcloud_plugin_common_vca_client.py ├── tox.ini └── vcloud_plugin_common └── __init__.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | build 3 | dist 4 | .idea 5 | *.iml 6 | *.patch 7 | *.orig 8 | *.rej 9 | *.diff 10 | *.egg* 11 | .Python 12 | bin 13 | include 14 | lib 15 | .tox 16 | cover 17 | .coverage 18 | .venv/ 19 | .idea/ 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | sudo: false 3 | python: 4 | - "2.7" 5 | env: 6 | - TOX_ENV=pep8 7 | - TOX_ENV=py27-unittests 8 | install: 9 | - pip install tox 10 | script: 11 | - tox -e $TOX_ENV 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VMware has ended active development of this project, this repository will no longer be updated. 2 | 3 | tosca-vcloud-plugin 4 | ===================== 5 | 6 | 7 | ## Running Integration Tests 8 | 9 | Create virtual environment and install plugin in dev-mode 10 | ``` 11 | virtualenv venv && source venv/bin/activate 12 | pip install -r tosca-vcloud-plugin/dev-requirements.txt 13 | pip install -r tosca-vcloud-plugin/test-requirements.txt 14 | pip install -e tosca-vcloud-plugin 15 | ``` 16 | Copy configuration files from `examples` folder, fill them with relevant data 17 | ``` 18 | cp tosca-vcloud-plugin/examples/vcloud_config.yaml.example vcloud_config.yaml 19 | cp tosca-vcloud-plugin/examples/vcloud_integration_test_config.yaml.example vcloud_integration_test_config.yaml 20 | ``` 21 | Export config files with environment variables 22 | ``` 23 | export VCLOUD_CONFIG_PATH="~/vcloud_config.yaml" 24 | export VCLOUD_INTEGRATION_TEST_CONFIG_PATH="~/vcloud_intergation_test_config.yaml" 25 | ``` 26 | Run tests using nosetests. For subscription account use the following command 27 | ``` 28 | nosetests --tc=subscription: tosca-vcloud-plugin/tests/integration 29 | ``` 30 | For OnDemand 31 | ``` 32 | nosetests --tc=ondemand: tosca-vcloud-plugin/tests/integration 33 | ``` 34 | Run tests using tox. For subscription account use the following command 35 | ``` 36 | tox -e py27-subscription 37 | ``` 38 | For OnDemand 39 | ``` 40 | tox -e py27-ondemand 41 | ``` 42 | For all accounts 43 | ``` 44 | tox 45 | ``` 46 | For specify particular test, you can add full test name after '--'. 47 | For example 48 | ``` 49 | tox -e ondemand -- \test_network_plugin.py 50 | tox -e ondemand -- \test_network_plugin.py:ValidationOperationsTestCase 51 | tox -e ondemand -- \test_network_plugin.py:ValidationOperationsTestCase.test_validation 52 | 53 | ``` 54 | -------------------------------------------------------------------------------- /blueprints/hellovcloud/blueprint.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: cloudify_dsl_1_0 2 | 3 | imports: 4 | - http://www.getcloudify.org/spec/cloudify/3.1/types.yaml 5 | - https://raw.githubusercontent.com/vmware/tosca-vcloud-plugin/1.1m1/plugin.yaml 6 | 7 | inputs: 8 | username: 9 | type: string 10 | password: 11 | type: string 12 | url: 13 | type: string 14 | service: 15 | type: string 16 | vdc: 17 | type: string 18 | catalog: 19 | type: string 20 | template: 21 | type: string 22 | network_name: 23 | type: string 24 | server_name: 25 | type: string 26 | 27 | node_types: 28 | vcloud_configuration: 29 | derived_from: cloudify.nodes.Root 30 | properties: 31 | vcloud_config: {} 32 | 33 | node_templates: 34 | example_server: 35 | type: cloudify.vcloud.nodes.Server 36 | properties: 37 | server: 38 | name: { get_input: server_name } 39 | catalog: { get_input: catalog } 40 | template: { get_input: template } 41 | management_network: { get_input: network_name } 42 | vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } 43 | relationships: 44 | - target: example_port 45 | type: cloudify.vcloud.server_connected_to_port 46 | 47 | example_port: 48 | type: cloudify.vcloud.nodes.Port 49 | properties: 50 | port: 51 | network: { get_input: network_name } 52 | ip_allocation_mode: pool 53 | primary_interface: true 54 | vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } 55 | 56 | vcloud_configuration: 57 | type: vcloud_configuration 58 | properties: 59 | vcloud_config: 60 | username: { get_input: username } 61 | password: { get_input: password } 62 | url: { get_input: url } 63 | service: { get_input: service } 64 | vdc: { get_input: vdc } 65 | -------------------------------------------------------------------------------- /blueprints/public_ip/blueprint.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: cloudify_dsl_1_0 2 | 3 | imports: 4 | - http://www.getcloudify.org/spec/cloudify/3.1/types.yaml 5 | - https://raw.githubusercontent.com/vmware/tosca-vcloud-plugin/1.1m1/plugin.yaml 6 | 7 | inputs: 8 | username: 9 | type: string 10 | password: 11 | type: string 12 | url: 13 | type: string 14 | service: 15 | type: string 16 | vdc: 17 | type: string 18 | catalog: 19 | type: string 20 | template: 21 | type: string 22 | network_name: 23 | type: string 24 | server_name: 25 | type: string 26 | public_ip: 27 | type: string 28 | 29 | node_types: 30 | vcloud_configuration: 31 | derived_from: cloudify.nodes.Root 32 | properties: 33 | vcloud_config: {} 34 | 35 | node_templates: 36 | example_server: 37 | type: cloudify.vcloud.nodes.Server 38 | properties: 39 | server: 40 | name: { get_input: server_name } 41 | catalog: { get_input: catalog } 42 | template: { get_input: template } 43 | management_network: { get_input: network_name } 44 | vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } 45 | relationships: 46 | - target: example_port 47 | type: cloudify.vcloud.server_connected_to_port 48 | - target: example_floating_ip 49 | type: cloudify.vcloud.server_connected_to_floating_ip 50 | 51 | example_port: 52 | type: cloudify.vcloud.nodes.Port 53 | properties: 54 | port: 55 | network: { get_input: network_name } 56 | ip_allocation_mode: pool 57 | primary_interface: true 58 | vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } 59 | 60 | example_floating_ip: 61 | type: cloudify.vcloud.nodes.FloatingIP 62 | properties: 63 | floatingip: 64 | edge_gateway: { get_input: vdc } 65 | public_ip: { get_input: public_ip } 66 | vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } 67 | 68 | vcloud_configuration: 69 | type: vcloud_configuration 70 | properties: 71 | vcloud_config: 72 | username: { get_input: username } 73 | password: { get_input: password } 74 | url: { get_input: url } 75 | service: { get_input: service } 76 | vdc: { get_input: vdc } 77 | -------------------------------------------------------------------------------- /blueprints/sample-input-ip.json: -------------------------------------------------------------------------------- 1 | { 2 | "username": "user@company.com", 3 | "password": "****************", 4 | "url": "https://vchs.vmware.com", 5 | "service": "ServiceName", 6 | "vdc": "OrgName", 7 | "catalog": "Public Catalog", 8 | "template": "name-of-the-template", 9 | "network_name": "default-routed-network", 10 | "server_name": "myserver", 11 | "public_ip": "22.23.24.25" 12 | } 13 | -------------------------------------------------------------------------------- /blueprints/sample-input.json: -------------------------------------------------------------------------------- 1 | { 2 | "username": "user@company.com", 3 | "password": "****************", 4 | "url": "https://vchs.vmware.com", 5 | "service": "ServiceName", 6 | "vdc": "OrgName", 7 | "catalog": "Public Catalog", 8 | "template": "name-of-the-template", 9 | "network_name": "default-routed-network", 10 | "server_name": "myserver" 11 | } 12 | -------------------------------------------------------------------------------- /dev-requirements.txt: -------------------------------------------------------------------------------- 1 | -e git+https://github.com/cloudify-cosmo/cloudify-dsl-parser@master#egg=cloudify-dsl-parser==3.2 2 | -e git+https://github.com/cloudify-cosmo/cloudify-rest-client@master#egg=cloudify-rest-client==3.2 3 | -e git+https://github.com/cloudify-cosmo/cloudify-plugins-common@master#egg=cloudify-plugins-common==3.2 4 | IPy 5 | pyvcloud 6 | -------------------------------------------------------------------------------- /examples/blueprint.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: cloudify_dsl_1_0 2 | 3 | imports: 4 | - http://www.getcloudify.org/spec/cloudify/3.2/types.yaml 5 | - https://raw.githubusercontent.com/cloudify-cosmo/tosca-vcloud-plugin/master/plugin.yaml 6 | 7 | node_types: 8 | vcloud_configuration: 9 | derived_from: cloudify.nodes.Root 10 | properties: 11 | vcloud_config: {} 12 | 13 | node_templates: 14 | example_server: 15 | type: cloudify.vcloud.nodes.Server 16 | properties: 17 | server: 18 | name: cfy-plugin-example-server 19 | catalog: giga 20 | template: plugin-test-VApp 21 | hardware: 22 | cpu: 3 23 | memory: 2048 24 | management_network: CFY-Internal 25 | vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } 26 | relationships: 27 | - target: example_port 28 | type: cloudify.vcloud.server_connected_to_port 29 | - target: example_port2 30 | type: cloudify.vcloud.server_connected_to_port 31 | - target: example_floating_ip 32 | type: cloudify.vcloud.server_connected_to_floating_ip 33 | 34 | example_server2: 35 | type: cloudify.vcloud.nodes.Server 36 | properties: 37 | server: 38 | name: cfy-plugin-example-server2 39 | catalog: giga 40 | template: plugin-test-VApp 41 | management_network: CFY-Internal 42 | vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } 43 | relationships: 44 | - target: example_public_nat_for_vm 45 | type: cloudify.vcloud.server_connected_to_public_nat 46 | 47 | example_server3: 48 | type: cloudify.vcloud.nodes.Server 49 | properties: 50 | server: 51 | name: cfy-plugin-example-server3 52 | catalog: giga 53 | template: plugin-test-VApp 54 | management_network: CFY-Internal 55 | vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } 56 | relationships: 57 | - target: example_network 58 | type: cloudify.vcloud.server_connected_to_network 59 | - target: example_floating_ip 60 | type: cloudify.vcloud.server_connected_to_floating_ip 61 | - target: volume 62 | type: cloudify.vcloud.volume_attached_to_server 63 | 64 | 65 | example_floating_ip: 66 | type: cloudify.vcloud.nodes.FloatingIP 67 | properties: 68 | floatingip: 69 | edge_gateway: M000000000-1111 70 | public_ip: 24.44.44.244 71 | vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } 72 | 73 | example_public_nat_for_net: 74 | type: cloudify.vcloud.nodes.PublicNAT 75 | properties: 76 | nat: 77 | edge_gateway: M000000000-1111 78 | public_ip: 24.44.44.244 79 | rules: 80 | - type: SNAT 81 | 82 | example_public_nat_for_vm: 83 | type: cloudify.vcloud.nodes.PublicNAT 84 | properties: 85 | nat: 86 | edge_gateway: M000000000-1111 87 | public_ip: 24.44.44.244 88 | rules: 89 | - type: SNAT 90 | - type: DNAT 91 | protocol: tcp 92 | original_port: 80 93 | translated_port: 8080 94 | 95 | example_port: 96 | type: cloudify.vcloud.nodes.Port 97 | properties: 98 | port: 99 | network: CFY-Internal 100 | ip_allocation_mode: dhcp 101 | primary_interface: true 102 | vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } 103 | 104 | example_network: 105 | type: cloudify.vcloud.nodes.Network 106 | properties: 107 | use_external_resource: true 108 | resource_id: CFY-Internal 109 | vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } 110 | relationships: 111 | - target: example_public_nat_for_net 112 | type: cloudify.vcloud.net_connected_to_public_nat 113 | 114 | example_port2: 115 | type: cloudify.vcloud.nodes.Port 116 | properties: 117 | port: 118 | network: plugin-test-nw 119 | ip_allocation_mode: manual 120 | ip_address: 10.10.0.2 121 | primary_interface: false 122 | vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } 123 | relationships: 124 | - target: example_network2 125 | type: cloudify.vcloud.port_connected_to_network 126 | 127 | example_network2: 128 | type: cloudify.vcloud.nodes.Network 129 | properties: 130 | network: 131 | edge_gateway: M000000000-1111 132 | name: plugin-test-nw 133 | static_range: 10.10.0.2-10.10.0.128 134 | netmask: 255.255.255.0 135 | gateway_ip: 10.10.0.1 136 | dns: 137 | - 8.8.8.8 138 | - 10.10.0.1 139 | dns_suffix: test 140 | dhcp: 141 | dhcp_range: 10.10.0.129-10.10.0.254 142 | default_lease: 3600 143 | max_lease: 7200 144 | vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } 145 | 146 | volume: 147 | type: cloudify.vcloud.nodes.Volume 148 | properties: 149 | volume: 150 | name: my_disk 151 | # size in Megabytes 152 | size: 1024 153 | vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } 154 | 155 | vcloud_configuration: 156 | type: vcloud_configuration 157 | properties: 158 | vcloud_config: 159 | username: user 160 | password: pw 161 | url: https://vchs.vmware.com 162 | service: M000000000-1111 163 | org: M000000000-1111 164 | edge_gateway: M000000000-1111 165 | -------------------------------------------------------------------------------- /examples/integration_test_env.example: -------------------------------------------------------------------------------- 1 | export VCLOUD_CONFIG_PATH="~/vcloud_config.json" 2 | export VCLOUD_INTEGRATION_TEST_CONFIG_PATH="~/vcloud_intergation_test_config.json" -------------------------------------------------------------------------------- /examples/vcloud_config.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "username": "", 3 | "password": "", 4 | "url": "", 5 | "service": "", 6 | "vdc": "", 7 | "service_type": "", 8 | "edge_gateway" : "" 9 | } 10 | 11 | -------------------------------------------------------------------------------- /examples/vcloud_config.yaml.example: -------------------------------------------------------------------------------- 1 | edge_gateway: '' 2 | password: '' 3 | service: '' 4 | service_type: '' 5 | url: '' 6 | username: '' 7 | org: '' 8 | -------------------------------------------------------------------------------- /examples/vcloud_config_ondemand.yaml.example: -------------------------------------------------------------------------------- 1 | password: password 2 | instance: 00000000-0000-0000-0000-000000000000 3 | service_type: ondemand 4 | url: iam.vchs.vmware.com 5 | username: user 6 | org: VDC1 7 | -------------------------------------------------------------------------------- /examples/vcloud_integration_test_config.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "test_vm": "vapp_name", 3 | "test_network_name": "test-network", 4 | "management_network": "networkname", 5 | "ondemand_management_network": "default-routed-network", 6 | "server": 7 | { 8 | "catalog": "", 9 | "template": "", 10 | "ondemand_catalog": "Public Catalog", 11 | "ondemand_template": "Ubuntu Server 12.04 LTS (amd64 20150127)", 12 | "guest_customization": 13 | { 14 | "admin_password": "123", 15 | "computer_name": "custom-name", 16 | "script_executor": "/bin/bash", 17 | "pre_script": "echo 'prescript works' > /tmp/guest_cust.out", 18 | "post_script": "echo 'postscript works' > /tmp/guest_cust.out", 19 | "public_keys": [] 20 | }, 21 | "hardware": 22 | { 23 | "cpu": 3, 24 | "memory": 2048 25 | } 26 | }, 27 | "manager_keypair": 28 | { 29 | "key": "mangerkey", 30 | "user": "ubuntu" 31 | }, 32 | "agent_keypair": 33 | { 34 | "key": "agentkey", 35 | "user": "ubuntu" 36 | }, 37 | "floatingip": 38 | { 39 | "public_ip": "1.2.3.4", 40 | "edge_gateway": "gatewayname" 41 | }, 42 | "network": 43 | { 44 | "name": "newnetworkname", 45 | "static_range": "192.168.0.100-192.168.0.199", 46 | "edge_gateway": "gatewayname", 47 | "gateway_ip": "192.168.0.1", 48 | "netmask": "255.255.255.0", 49 | "dns": "10.147.115.1", 50 | "dns_suffix": "example.com", 51 | "dhcp": 52 | { 53 | "dhcp_range": "192.168.0.200-192.168.0.210", 54 | "default_lease": 3600, 55 | "max_lease": 7200 56 | } 57 | }, 58 | "security_group": 59 | { 60 | "security_group": 61 | { 62 | "edge_gateway" : "gatewayname" 63 | }, 64 | "rules" :[ 65 | { 66 | "description": "Rule description", 67 | "source": "external", 68 | "source_port": 29, 69 | "destination": "internal", 70 | "destination_port": "any", 71 | "protocol": "Tcp", 72 | "action": "allow", 73 | "log_traffic": False 74 | }] 75 | }, 76 | "public_nat": 77 | { 78 | "network_name": "newnet", 79 | "test_vm": "vapp_name", 80 | "nat": 81 | { 82 | "public_ip": "23.92.245.236", 83 | "edge_gateway": "gatewayname" 84 | }, 85 | "rules_net": 86 | { 87 | "type": ["SNAT"] 88 | }, 89 | "rules_port": 90 | { 91 | "type": ["SNAT", "DNAT"], 92 | "protocol": "Tcp", 93 | "original_port": 80, 94 | "translated_port": 8080 95 | } 96 | }, 97 | "combined": 98 | { 99 | "network_use_existing": false, 100 | "network_name": "existing-network" 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /examples/vcloud_integration_test_config.yaml.example: -------------------------------------------------------------------------------- 1 | agent_keypair: 2 | key: agentkey 3 | user: ubuntu 4 | combined: 5 | network_name: existing-network 6 | network_use_existing: false 7 | floatingip: 8 | edge_gateway: gatewayname 9 | public_ip: 1.2.3.4 10 | management_network: networkname 11 | manager_keypair: 12 | key: mangerkey 13 | user: ubuntu 14 | network: 15 | dhcp: 16 | default_lease: 3600 17 | dhcp_range: 192.168.0.200-192.168.0.210 18 | max_lease: 7200 19 | dns: 20 | - 10.147.115.1 21 | dns_suffix: example.com 22 | edge_gateway: gatewayname 23 | gateway_ip: 192.168.0.1 24 | name: newnetworkname 25 | netmask: 255.255.255.0 26 | static_range: 192.168.0.100-192.168.0.199 27 | ondemand_management_network: default-routed-network 28 | public_nat: 29 | nat: 30 | edge_gateway: gatewayname 31 | public_ip: 23.92.245.236 32 | network_name: newnet 33 | rules_net: 34 | - type: SNAT 35 | rules_port: 36 | - type: SNAT 37 | - type: DNAT 38 | original_port: 80 39 | translated_port: 8080 40 | protocol: Tcp 41 | - type: DNAT 42 | original_port: 90 43 | translated_port: 9090 44 | protocol: Tcp 45 | test_vm: vapp_name 46 | security_group: 47 | rules: 48 | - action: allow 49 | description: Rule description 50 | destination: internal 51 | destination_port: any 52 | log_traffic: false 53 | protocol: Tcp 54 | source: external 55 | source_port: 29 56 | - source: host 57 | destination: any 58 | action: allow 59 | description: > 60 | backward network connection for host updates 61 | protocol: any 62 | security_group: 63 | edge_gateway: gatewayname 64 | server: 65 | catalog: '' 66 | guest_customization: 67 | admin_password: '123' 68 | computer_name: custom-name 69 | post_script: echo 'postscript works' > /tmp/guest_cust.out 70 | pre_script: echo 'prescript works' > /tmp/guest_cust.out 71 | public_keys: [] 72 | script_executor: /bin/bash 73 | hardware: 74 | cpu: 3 75 | memory: 2048 76 | ondemand_catalog: Public Catalog 77 | ondemand_template: Ubuntu Server 12.04 LTS (amd64 20150127) 78 | template: '' 79 | volume: 80 | name: test_disk 81 | name_exists: manager_disk 82 | size: 10 83 | test_network_name: test-network 84 | test_vm: vapp_name 85 | -------------------------------------------------------------------------------- /manager_blueprint/inputs.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "vcloud_username": "", 3 | "vcloud_password": "", 4 | "vcloud_url": "", 5 | "vcloud_service": "", 6 | "vcloud_vdc": "", 7 | "manager_server_name": "", 8 | "manager_server_catalog": "", 9 | "manager_server_template": "", 10 | "management_network_use_existing": true, 11 | "management_network_name": "", 12 | "edge_gateway": "", 13 | "floating_ip_public_ip": "", 14 | "manager_private_key_path": "~/.ssh/manager.pem", 15 | "agent_private_key_path": "~/.ssh/agent.pem", 16 | "manager_public_key": "", 17 | "agent_public_key": "" 18 | } -------------------------------------------------------------------------------- /manager_blueprint/inputs.yaml.example: -------------------------------------------------------------------------------- 1 | vcloud_username: some_user 2 | vcloud_password: some_password 3 | vcloud_url: https://iam.vchs.vmware.com 4 | vcloud_service: VDC 5 | vcloud_org: VDC 6 | vcloud_vdc: VDC 7 | vcloud_service_type: ondemand 8 | vcloud_instance: 00000000-0000-0000-0000-000000000000 9 | edge_gateway: gateway 10 | manager_server_name: some-server 11 | manager_server_catalog: default-catalog 12 | manager_server_template: Ubuntu Server 12.04 LTS (amd64 20150127) 13 | manager_server_public_ip: 14 | management_network_use_existing: true 15 | management_network_public_nat_use_existing: true 16 | management_network_name: some-net 17 | management_network_public_ip: 18 | manager_private_key_path: ~/.ssh/manager.pem 19 | agent_private_key_path: ~/.ssh/manager.pem 20 | manager_public_key: some_key 21 | agent_public_key: some_key 22 | management_port_ip_allocation_mode: pool 23 | -------------------------------------------------------------------------------- /manager_blueprint/scripts/configure.py: -------------------------------------------------------------------------------- 1 | import tempfile 2 | import json 3 | 4 | import fabric 5 | 6 | import vcloud_plugin_common 7 | from cloudify import ctx 8 | 9 | PROVIDER_CONTEXT_RUNTIME_PROPERTY = 'provider_context' 10 | 11 | 12 | def configure(vcloud_config): 13 | """ 14 | copy configuration to managment host, 15 | install docker 16 | and save current context to .cloudify/context 17 | For now - we have saved only managment network name 18 | """ 19 | _copy_vsphere_configuration_to_manager(vcloud_config) 20 | _install_docker() 21 | _save_context() 22 | 23 | 24 | def _copy_vsphere_configuration_to_manager(vcloud_config): 25 | """ 26 | Copy current config to remote node 27 | """ 28 | tmp = tempfile.mktemp() 29 | with open(tmp, 'w') as f: 30 | json.dump(vcloud_config, f) 31 | fabric.api.put(tmp, 32 | vcloud_plugin_common.Config.VCLOUD_CONFIG_PATH_DEFAULT) 33 | 34 | 35 | def _install_docker(): 36 | """ 37 | install docker from https://get.docker.com/ 38 | """ 39 | distro = fabric.api.run( 40 | 'python -c "import platform; print platform.dist()[0]"') 41 | kernel_version = fabric.api.run( 42 | 'python -c "import platform; print platform.release()"') 43 | if kernel_version.startswith("3.13") and 'Ubuntu' in distro: 44 | fabric.api.run("wget -qO- https://get.docker.com/ | sudo sh") 45 | 46 | 47 | def _save_context(): 48 | """ 49 | save current managment network for use as default network for 50 | all new nodes 51 | """ 52 | resources = dict() 53 | 54 | node_instances = ctx._endpoint.storage.get_node_instances() 55 | nodes_by_id = \ 56 | {node.id: node for node in ctx._endpoint.storage.get_nodes()} 57 | 58 | for node_instance in node_instances: 59 | props = nodes_by_id[node_instance.node_id].properties 60 | 61 | if "management_network" == node_instance.node_id: 62 | resources['int_network'] = { 63 | "name": props.get('resource_id') 64 | } 65 | 66 | provider = { 67 | 'resources': resources 68 | } 69 | 70 | ctx.instance.runtime_properties[PROVIDER_CONTEXT_RUNTIME_PROPERTY] = \ 71 | provider 72 | -------------------------------------------------------------------------------- /manager_blueprint/scripts/configure_docker.py: -------------------------------------------------------------------------------- 1 | import fabric 2 | 3 | 4 | def configure(vcloud_config): 5 | """ 6 | only update container with vcloud specific packages 7 | """ 8 | _update_container() 9 | 10 | 11 | def _update_container(): 12 | """ install some packeges for future deployments creation """ 13 | # update system to last version 14 | fabric.api.run("sudo docker exec -i -t cfy apt-get " 15 | "update -q -y 2>&1") 16 | fabric.api.run("sudo docker exec -i -t cfy apt-get " 17 | "dist-upgrade -q -y 2>&1") 18 | # install: 19 | fabric.api.run("sudo docker exec -i -t cfy apt-get " 20 | "install gcc python-dev libxml2-dev libxslt-dev " 21 | "zlib1g-dev -q -y 2>&1") 22 | -------------------------------------------------------------------------------- /manager_blueprint/vcloud.yaml: -------------------------------------------------------------------------------- 1 | tosca_definitions_version: cloudify_dsl_1_0 2 | 3 | imports: 4 | - http://www.getcloudify.org/spec/cloudify/3.1/types.yaml 5 | - https://raw.githubusercontent.com/vmware/tosca-vcloud-plugin/master/plugin.yaml 6 | - http://www.getcloudify.org/spec/fabric-plugin/1.1/plugin.yaml 7 | 8 | inputs: 9 | vcloud_username: 10 | type: string 11 | 12 | vcloud_password: 13 | type: string 14 | 15 | vcloud_token: 16 | type: string 17 | default: '' 18 | 19 | vcloud_url: 20 | type: string 21 | 22 | vcloud_service: 23 | type: string 24 | 25 | vcloud_service_type: 26 | type: string 27 | default: 'subscription' 28 | 29 | vcloud_region: 30 | type: string 31 | default: '' 32 | description: > 33 | Only required for ondemand service type. 34 | 35 | vcloud_api_version: 36 | type: string 37 | default: '5.6' 38 | 39 | vcloud_org_url: 40 | type: string 41 | default: '' 42 | description: > 43 | Only required if using token based login on a private vcloud 44 | director. This can be obtained by following the vcloud API 45 | example docs. If you are unsure of how to obtain this, you will 46 | need to use password based login. 47 | 48 | vcloud_vdc: 49 | type: string 50 | 51 | manager_server_name: 52 | type: string 53 | 54 | manager_server_catalog: 55 | type: string 56 | 57 | manager_server_template: 58 | type: string 59 | 60 | manager_server_cpus: 61 | type: string 62 | default: 2 63 | 64 | manager_server_memory: 65 | type: string 66 | default: 4096 67 | 68 | management_network_use_existing: 69 | type: boolean 70 | default: false 71 | 72 | management_network_name: 73 | type: string 74 | 75 | management_port_ip_allocation_mode: 76 | type: string 77 | default: dhcp 78 | 79 | management_port_ip_address: 80 | type: string 81 | default: '' 82 | 83 | edge_gateway: 84 | type: string 85 | default: gateway 86 | description: > 87 | For 'ondemand' service type, the value of edge_gateway 88 | is always 'gateway' 89 | 90 | floating_ip_public_ip: 91 | type: string 92 | default: '' 93 | description: > 94 | For 'ondemand' service type, the value of 95 | floating_ip_public_ip can be empty 96 | 97 | agents_user: 98 | type: string 99 | default: ubuntu 100 | 101 | manager_server_user: 102 | default: ubuntu 103 | type: string 104 | 105 | manager_private_key_path: 106 | default: ~/.ssh/cloudify-manager-kp.pem 107 | type: string 108 | 109 | agent_private_key_path: 110 | default: ~/.ssh/cloudify-agent-kp.pem 111 | type: string 112 | 113 | manager_public_key: 114 | type: string 115 | default: '' 116 | 117 | agent_public_key: 118 | type: string 119 | default: '' 120 | 121 | resources_prefix: 122 | type: string 123 | default: '' 124 | 125 | 126 | node_types: 127 | vcloud_configuration: 128 | derived_from: cloudify.nodes.Root 129 | properties: 130 | vcloud_config: {} 131 | 132 | node_templates: 133 | manager_server: 134 | type: cloudify.vcloud.nodes.Server 135 | properties: 136 | install_agent: false 137 | server: 138 | name: { get_input: manager_server_name } 139 | catalog: { get_input: manager_server_catalog } 140 | template: { get_input: manager_server_template } 141 | guest_customization: 142 | public_keys: 143 | - { get_property: [manager_keypair, public_key] } 144 | - { get_property: [agent_keypair, public_key] } 145 | computer_name: { get_input: manager_server_name } 146 | hardware: 147 | cpu: { get_input: manager_server_cpus } 148 | memory: { get_input: manager_server_memory } 149 | management_network: { get_input: management_network_name } 150 | vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } 151 | relationships: 152 | - target: management_port 153 | type: cloudify.vcloud.server_connected_to_port 154 | - target: manager_floating_ip 155 | type: cloudify.vcloud.server_connected_to_floating_ip 156 | 157 | management_port: 158 | type: cloudify.vcloud.nodes.Port 159 | properties: 160 | port: 161 | network: { get_input: management_network_name } 162 | ip_allocation_mode: { get_input: management_port_ip_allocation_mode } 163 | ip_address: { get_input: management_port_ip_address } 164 | primary_interface: true 165 | vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } 166 | relationships: 167 | - target: management_network 168 | type: cloudify.vcloud.port_connected_to_network 169 | 170 | management_network: 171 | type: cloudify.vcloud.nodes.Network 172 | properties: 173 | use_external_resource: { get_input: management_network_use_existing } 174 | resource_id: { get_input: management_network_name } 175 | network: 176 | edge_gateway: { get_input: edge_gateway } 177 | static_range: 10.67.79.129-10.67.79.254 178 | netmask: 255.255.255.0 179 | gateway_ip: 10.67.79.1 180 | dns: 10.67.79.1 181 | dhcp: 182 | dhcp_range: 10.67.79.2-10.67.79.128 183 | vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } 184 | 185 | manager_floating_ip: 186 | type: cloudify.vcloud.nodes.FloatingIP 187 | properties: 188 | floatingip: 189 | edge_gateway: { get_input: edge_gateway } 190 | public_ip: { get_input: floating_ip_public_ip } 191 | vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } 192 | 193 | vcloud_configuration: 194 | type: vcloud_configuration 195 | properties: 196 | vcloud_config: 197 | username: { get_input: vcloud_username } 198 | password: { get_input: vcloud_password } 199 | token: { get_input: vcloud_token } 200 | url: { get_input: vcloud_url } 201 | service: { get_input: vcloud_service } 202 | vdc: { get_input: vcloud_vdc } 203 | service_type: { get_input: vcloud_service_type } 204 | region: { get_input: vcloud_region } 205 | api_version: { get_input: vcloud_api_version } 206 | org_url: { get_input: vcloud_org_url } 207 | edge_gateway: { get_input: edge_gateway } 208 | 209 | manager_keypair: 210 | type: cloudify.vcloud.nodes.KeyPair 211 | properties: 212 | private_key_path: { get_input: manager_private_key_path } 213 | public_key: 214 | key: { get_input: manager_public_key } 215 | user: { get_input: manager_server_user } 216 | vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } 217 | 218 | agent_keypair: 219 | type: cloudify.vcloud.nodes.KeyPair 220 | properties: 221 | private_key_path: { get_input: agent_private_key_path } 222 | public_key: 223 | key: { get_input: agent_public_key } 224 | user: { get_input: agents_user } 225 | vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } 226 | 227 | manager: 228 | type: cloudify.nodes.CloudifyManager 229 | properties: 230 | cloudify_packages: 231 | server: 232 | components_package_url: http://gigaspaces-repository-eu.s3.amazonaws.com/org/cloudify3/3.1.0/ga-RELEASE/cloudify-components_3.1.0-ga-b85_amd64.deb 233 | core_package_url: http://gigaspaces-repository-eu.s3.amazonaws.com/org/cloudify3/3.1.0/ga-RELEASE/cloudify-core_3.1.0-ga-b85_amd64.deb 234 | ui_package_url: http://gigaspaces-repository-eu.s3.amazonaws.com/org/cloudify3/3.1.0/ga-RELEASE/cloudify-ui_3.1.0-ga-b85_amd64.deb 235 | agents: 236 | ubuntu_agent_url: http://gigaspaces-repository-eu.s3.amazonaws.com/org/cloudify3/3.1.0/ga-RELEASE/cloudify-ubuntu-precise-agent_3.1.0-ga-b85_amd64.deb 237 | centos_agent_url: http://gigaspaces-repository-eu.s3.amazonaws.com/org/cloudify3/3.1.0/ga-RELEASE/cloudify-centos-final-agent_3.1.0-ga-b85_amd64.deb 238 | windows_agent_url: http://gigaspaces-repository-eu.s3.amazonaws.com/org/cloudify3/3.1.0/ga-RELEASE/cloudify-windows-agent_3.1.0-ga-b85_amd64.deb 239 | 240 | cloudify: 241 | resources_prefix: { get_input: resources_prefix } 242 | 243 | cloudify_agent: 244 | min_workers: 2 245 | max_workers: 5 246 | remote_execution_port: 22 247 | user: { get_input: agents_user } 248 | 249 | workflows: 250 | task_retries: -1 # this means forever 251 | task_retry_interval: 30 252 | 253 | policy_engine: 254 | start_timeout: 30 255 | 256 | relationships: 257 | - target: manager_server 258 | type: cloudify.relationships.contained_in 259 | 260 | interfaces: 261 | cloudify.interfaces.lifecycle: 262 | configure: 263 | implementation: fabric.fabric_plugin.tasks.run_task 264 | inputs: 265 | tasks_file: scripts/configure.py 266 | task_name: configure 267 | task_properties: 268 | vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } 269 | fabric_env: 270 | user: { get_input: manager_server_user } 271 | key_filename: { get_input: manager_private_key_path } 272 | host_string: { get_attribute: [manager_floating_ip, public_ip] } 273 | start: 274 | implementation: fabric.fabric_plugin.tasks.run_module_task 275 | inputs: 276 | task_mapping: cloudify_cli.bootstrap.tasks.bootstrap 277 | task_properties: 278 | cloudify_packages: { get_property: [manager, cloudify_packages] } 279 | agent_local_key_path: { get_input: agent_private_key_path } 280 | fabric_env: 281 | user: { get_input: manager_server_user } 282 | key_filename: { get_input: manager_private_key_path } 283 | host_string: { get_attribute: [manager_floating_ip, public_ip] } 284 | cloudify.interfaces.validation: 285 | creation: 286 | implementation: cli.cloudify_cli.bootstrap.tasks.creation_validation 287 | inputs: 288 | cloudify_packages: { get_property: [manager, cloudify_packages] } 289 | 290 | 291 | plugins: 292 | cli: 293 | install: false 294 | executor: central_deployment_agent 295 | 296 | 297 | outputs: 298 | manager_ip: 299 | value: { get_attribute: [manager_floating_ip, public_ip] } 300 | -------------------------------------------------------------------------------- /network_plugin/__init__.py: -------------------------------------------------------------------------------- 1 | from IPy import IP 2 | from cloudify import exceptions as cfy_exc 3 | import collections 4 | from pyvcloud.schema.vcd.v1_5.schemas.vcloud import taskType 5 | from vcloud_plugin_common import (wait_for_task, get_vcloud_config, 6 | is_subscription) 7 | 8 | VCLOUD_VAPP_NAME = 'vcloud_vapp_name' 9 | PUBLIC_IP = 'public_ip' 10 | NAT_ROUTED = 'natRouted' 11 | CREATE = 1 12 | DELETE = 2 13 | 14 | 15 | AssignedIPs = collections.namedtuple('AssignedIPs', 'external internal') 16 | BUSY_MESSAGE = "The entity gateway is busy completing an operation." 17 | 18 | 19 | def check_ip(address): 20 | """ 21 | check ip 22 | """ 23 | try: 24 | IP(address) 25 | except ValueError: 26 | raise cfy_exc.NonRecoverableError( 27 | "Incorrect IP address: {0}".format(address)) 28 | except TypeError: 29 | raise cfy_exc.NonRecoverableError( 30 | "Incorrect type of IP address value : {0}".format(address)) 31 | return address 32 | 33 | 34 | def is_valid_ip_range(start, end): 35 | """ 36 | check start ip < end ip 37 | """ 38 | return IP(start) < IP(end) 39 | 40 | 41 | def is_separate_ranges(first, second): 42 | """ 43 | check that we dont have shared ips in ranges, e.g 44 | first.end < second.start or first.start > second.end 45 | """ 46 | return IP(first.end) < IP(second.start) or IP(first.start) > IP(second.end) 47 | 48 | 49 | def is_ips_in_same_subnet(ips, netmask): 50 | """ 51 | check that we have ip with same mask 52 | """ 53 | subnets = [IP("{0}/{1}".format(ip, netmask), make_net=True) for ip in ips] 54 | return len(set(subnets)) == 1 55 | 56 | 57 | def CheckAssignedExternalIp(ip, gateway): 58 | """ 59 | check ip have already assigned to some node as public ip 60 | """ 61 | if ip in [address.external for address in collectAssignedIps(gateway)]: 62 | raise cfy_exc.NonRecoverableError( 63 | "IP address: {0} already assigned. Gateway has free IP: {1}" 64 | .format(ip, getFreeIP(gateway))) 65 | 66 | 67 | def CheckAssignedInternalIp(ip, gateway): 68 | """ 69 | check ip have already assigned to som node as internal ip 70 | """ 71 | if ip in [address.internal for address in collectAssignedIps(gateway)]: 72 | raise cfy_exc.NonRecoverableError( 73 | "VM private IP {0} already has public ip assigned ".format(ip)) 74 | 75 | 76 | def collectAssignedIps(gateway): 77 | """ 78 | get full list of assigned ips as set of (external, internal) 79 | """ 80 | ips = [] 81 | if gateway: 82 | for natRule in gateway.get_nat_rules(): 83 | rule = natRule.get_GatewayNatRule() 84 | rule_type = natRule.get_RuleType() 85 | if rule_type == "DNAT": 86 | ips.append(AssignedIPs(rule.get_OriginalIp(), 87 | rule.get_TranslatedIp())) 88 | else: 89 | ips.append(AssignedIPs(rule.get_TranslatedIp(), 90 | rule.get_OriginalIp())) 91 | return set(ips) 92 | 93 | 94 | def get_vm_ip(vca_client, ctx, gateway): 95 | """ 96 | get ip assigned to current vm from connected primary network. 97 | """ 98 | try: 99 | vappName = get_vapp_name(ctx.source.instance.runtime_properties) 100 | vdc = vca_client.get_vdc(get_vcloud_config()['vdc']) 101 | vapp = vca_client.get_vapp(vdc, vappName) 102 | if not vapp: 103 | raise cfy_exc.NonRecoverableError( 104 | "Could not find vApp {0}".format(vappName)) 105 | 106 | vm_info = vapp.get_vms_network_info() 107 | # assume that we have 1 vm per vApp 108 | for connection in vm_info[0]: 109 | if connection['is_connected'] and connection['is_primary']: 110 | if is_network_routed(vca_client, 111 | connection['network_name'], 112 | gateway): 113 | return connection['ip'] 114 | else: 115 | raise cfy_exc.NonRecoverableError( 116 | "Primary network {0} not routed" 117 | .format(connection['network_name'])) 118 | raise cfy_exc.NonRecoverableError("No connected primary network") 119 | except IndexError: 120 | raise cfy_exc.NonRecoverableError("Could not get vm IP address") 121 | 122 | 123 | def get_vapp_name(runtime_properties): 124 | """ 125 | get vapp name from runtime properties 126 | """ 127 | vapp_name = runtime_properties.get(VCLOUD_VAPP_NAME) 128 | if not vapp_name: 129 | raise cfy_exc.NonRecoverableError( 130 | "Could not find vApp name in runtime properties") 131 | return vapp_name 132 | 133 | 134 | def save_gateway_configuration(gateway, vca_client): 135 | """ 136 | save gateway configuration, 137 | return 138 | True - everything successfully finished 139 | False - gateway busy 140 | raise NonRecoverableError - can't get task description 141 | """ 142 | task = gateway.save_services_configuration() 143 | if task: 144 | wait_for_task(vca_client, task) 145 | return True 146 | else: 147 | error = taskType.parseString(gateway.response.content, True) 148 | if BUSY_MESSAGE in error.message: 149 | return False 150 | else: 151 | raise cfy_exc.NonRecoverableError(error.message) 152 | 153 | 154 | def getFreeIP(gateway): 155 | """ 156 | return list of free public ips as difference 157 | between assigned ips to nodes and full list of public ip 158 | assigned to gateway 159 | """ 160 | public_ips = set(gateway.get_public_ips()) 161 | allocated_ips = set([address.external 162 | for address in collectAssignedIps(gateway)]) 163 | available_ips = public_ips - allocated_ips 164 | if not available_ips: 165 | raise cfy_exc.NonRecoverableError( 166 | "Can't get public IP address") 167 | return list(available_ips)[0] 168 | 169 | 170 | def get_network_name(properties): 171 | """ 172 | get network name from properties 173 | """ 174 | if properties.get('use_external_resource'): 175 | name = properties.get('resource_id') 176 | if not name: 177 | raise cfy_exc.NonRecoverableError( 178 | "Parameter 'resource_id; for external resource not defined.") 179 | return name 180 | if not properties.get('network'): 181 | raise cfy_exc.NonRecoverableError( 182 | "Parameter 'network' for Network node not defined.") 183 | name = properties["network"].get("name") 184 | if not name: 185 | raise cfy_exc.NonRecoverableError( 186 | "Parameter 'name' for network properties not defined.") 187 | return name 188 | 189 | 190 | def is_network_exists(vca_client, network_name): 191 | """ 192 | network already exist 193 | """ 194 | return bool(vca_client.get_network(get_vcloud_config()['vdc'], 195 | network_name)) 196 | 197 | 198 | def is_network_routed(vca_client, network_name, gateway): 199 | """ 200 | network routed and exist in interfaces for this gateway 201 | """ 202 | network = get_network(vca_client, network_name) 203 | if network.get_Configuration().get_FenceMode() != NAT_ROUTED: 204 | return False 205 | interfaces = gateway.get_interfaces('internal') 206 | for interface in interfaces: 207 | if interface.get_Name() == network_name: 208 | return True 209 | return False 210 | 211 | 212 | def get_network(vca_client, network_name): 213 | """ 214 | return network by name 215 | """ 216 | if not network_name: 217 | raise cfy_exc.NonRecoverableError( 218 | "Network name is empty".format(network_name)) 219 | network = vca_client.get_network(get_vcloud_config()['vdc'], network_name) 220 | if not network: 221 | raise cfy_exc.NonRecoverableError( 222 | "Network {0} could not be found".format(network_name)) 223 | return network 224 | 225 | 226 | def get_ondemand_public_ip(vca_client, gateway, ctx): 227 | """ 228 | try to allocate new public ip for ondemand service 229 | """ 230 | old_public_ips = set(gateway.get_public_ips()) 231 | task = gateway.allocate_public_ip() 232 | if task: 233 | wait_for_task(vca_client, task) 234 | else: 235 | raise cfy_exc.NonRecoverableError( 236 | "Can't get public ip for ondemand service") 237 | # update gateway for new IP address 238 | gateway = vca_client.get_gateways(get_vcloud_config()['vdc'])[0] 239 | new_public_ips = set(gateway.get_public_ips()) 240 | new_ip = new_public_ips - old_public_ips 241 | if new_ip: 242 | ctx.logger.info("Assign public IP {0}".format(new_ip)) 243 | else: 244 | raise cfy_exc.NonRecoverableError( 245 | "Can't get new public IP address") 246 | return list(new_ip)[0] 247 | 248 | 249 | def del_ondemand_public_ip(vca_client, gateway, ip, ctx): 250 | """ 251 | try to deallocate public ip 252 | """ 253 | task = gateway.deallocate_public_ip(ip) 254 | if task: 255 | wait_for_task(vca_client, task) 256 | ctx.logger.info("Public IP {0} deallocated".format(ip)) 257 | else: 258 | raise cfy_exc.NonRecoverableError( 259 | "Can't deallocate public ip {0} for ondemand service".format(ip)) 260 | 261 | 262 | def get_public_ip(vca_client, gateway, service_type, ctx): 263 | """ 264 | return new public ip 265 | """ 266 | if is_subscription(service_type): 267 | public_ip = getFreeIP(gateway) 268 | ctx.logger.info("Assign external IP {0}".format(public_ip)) 269 | else: 270 | public_ip = get_ondemand_public_ip(vca_client, gateway, ctx) 271 | return public_ip 272 | 273 | 274 | def get_gateway(vca_client, gateway_name): 275 | """ 276 | return gateway by name 277 | """ 278 | gateway = vca_client.get_gateway(get_vcloud_config()['vdc'], 279 | gateway_name) 280 | if not gateway: 281 | raise cfy_exc.NonRecoverableError( 282 | "Gateway {0} not found".format(gateway_name)) 283 | return gateway 284 | -------------------------------------------------------------------------------- /network_plugin/floatingip.py: -------------------------------------------------------------------------------- 1 | from cloudify import ctx 2 | from cloudify import exceptions as cfy_exc 3 | from cloudify.decorators import operation 4 | from vcloud_plugin_common import (with_vca_client, get_vcloud_config, 5 | is_subscription, is_ondemand, get_mandatory) 6 | from network_plugin import (check_ip, CheckAssignedExternalIp, 7 | CheckAssignedInternalIp, get_vm_ip, 8 | save_gateway_configuration, getFreeIP, 9 | CREATE, DELETE, PUBLIC_IP, get_gateway, 10 | get_public_ip, del_ondemand_public_ip) 11 | 12 | 13 | @operation 14 | @with_vca_client 15 | def connect_floatingip(vca_client, **kwargs): 16 | """ 17 | create new floating ip for node 18 | """ 19 | _floatingip_operation(CREATE, vca_client, ctx) 20 | 21 | 22 | @operation 23 | @with_vca_client 24 | def disconnect_floatingip(vca_client, **kwargs): 25 | """ 26 | release floating ip 27 | """ 28 | _floatingip_operation(DELETE, vca_client, ctx) 29 | 30 | 31 | @operation 32 | @with_vca_client 33 | def creation_validation(vca_client, **kwargs): 34 | """ 35 | validate node context, 36 | fields from floatingip dict: 37 | * edge_gateway - mandatory, 38 | * public_ip - prefered ip for node, can be empty 39 | fields from vcloud_config: 40 | * service_type - ondemand, subscription 41 | also check availability of public ip if set or exist some free 42 | ip in subscription case 43 | """ 44 | floatingip = get_mandatory(ctx.node.properties, 'floatingip') 45 | edge_gateway = get_mandatory(floatingip, 'edge_gateway') 46 | gateway = get_gateway(vca_client, edge_gateway) 47 | service_type = get_vcloud_config().get('service_type') 48 | public_ip = floatingip.get(PUBLIC_IP) 49 | if public_ip: 50 | check_ip(public_ip) 51 | CheckAssignedExternalIp(public_ip, gateway) 52 | else: 53 | if is_subscription(service_type): 54 | getFreeIP(gateway) 55 | 56 | 57 | def _floatingip_operation(operation, vca_client, ctx): 58 | """ 59 | create/release floating ip by nat rules for this ip with 60 | relation to internal ip for current node, 61 | save selected public_ip in runtime properties 62 | """ 63 | service_type = get_vcloud_config().get('service_type') 64 | gateway = get_gateway( 65 | vca_client, ctx.target.node.properties['floatingip']['edge_gateway']) 66 | internal_ip = get_vm_ip(vca_client, ctx, gateway) 67 | 68 | nat_operation = None 69 | public_ip = (ctx.target.instance.runtime_properties.get(PUBLIC_IP) 70 | or ctx.target.node.properties['floatingip'].get(PUBLIC_IP)) 71 | if operation == CREATE: 72 | CheckAssignedInternalIp(internal_ip, gateway) 73 | if public_ip: 74 | CheckAssignedExternalIp(public_ip, gateway) 75 | else: 76 | public_ip = get_public_ip(vca_client, gateway, service_type, ctx) 77 | 78 | nat_operation = _add_nat_rule 79 | elif operation == DELETE: 80 | if not public_ip: 81 | ctx.logger.info("Can't get external IP".format(public_ip)) 82 | return 83 | nat_operation = _del_nat_rule 84 | else: 85 | raise cfy_exc.NonRecoverableError( 86 | "Unknown operation {0}".format(operation) 87 | ) 88 | 89 | external_ip = check_ip(public_ip) 90 | 91 | nat_operation(gateway, "SNAT", internal_ip, external_ip) 92 | nat_operation(gateway, "DNAT", external_ip, internal_ip) 93 | if not save_gateway_configuration(gateway, vca_client): 94 | return ctx.operation.retry(message='Waiting for gateway.', 95 | retry_after=10) 96 | 97 | if operation == CREATE: 98 | ctx.target.instance.runtime_properties[PUBLIC_IP] = external_ip 99 | else: 100 | if is_ondemand(service_type): 101 | if not ctx.target.node.properties['floatingip'].get(PUBLIC_IP): 102 | del_ondemand_public_ip( 103 | vca_client, 104 | gateway, 105 | ctx.target.instance.runtime_properties[PUBLIC_IP], 106 | ctx) 107 | del ctx.target.instance.runtime_properties[PUBLIC_IP] 108 | 109 | 110 | def _add_nat_rule(gateway, rule_type, original_ip, translated_ip): 111 | """ 112 | add nat rule with enable any types of trafic from translated_ip 113 | to origin_ip 114 | """ 115 | any_type = "any" 116 | 117 | ctx.logger.info("Create floating ip NAT rule: original_ip '{0}'," 118 | "translated_ip '{1}', rule type '{2}'" 119 | .format(original_ip, translated_ip, rule_type)) 120 | 121 | gateway.add_nat_rule( 122 | rule_type, original_ip, any_type, translated_ip, any_type, any_type) 123 | 124 | 125 | def _del_nat_rule(gateway, rule_type, original_ip, translated_ip): 126 | """ 127 | drop rule created by add_nat_rule 128 | """ 129 | any_type = 'any' 130 | 131 | ctx.logger.info("Delete floating ip NAT rule: original_ip '{0}'," 132 | "translated_ip '{1}', rule type '{2}'" 133 | .format(original_ip, translated_ip, rule_type)) 134 | 135 | gateway.del_nat_rule( 136 | rule_type, original_ip, any_type, translated_ip, any_type, any_type) 137 | -------------------------------------------------------------------------------- /network_plugin/keypair.py: -------------------------------------------------------------------------------- 1 | from cloudify import ctx 2 | from cloudify import exceptions as cfy_exc 3 | from cloudify.decorators import operation 4 | import os.path 5 | 6 | 7 | @operation 8 | def creation_validation(**kwargs): 9 | """ 10 | check availability of path used in field private_key_path of 11 | node properties 12 | """ 13 | key = ctx.node.properties.get('private_key_path') 14 | if key: 15 | key_path = os.path.expanduser(key) 16 | if not os.path.isfile(key_path): 17 | raise cfy_exc.NonRecoverableError( 18 | "Private key file {0} is absent".format(key_path)) 19 | -------------------------------------------------------------------------------- /network_plugin/network.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # * See the License for the specific language governing permissions and 13 | # * limitations under the License. 14 | 15 | from cloudify import ctx 16 | from cloudify import exceptions as cfy_exc 17 | from cloudify.decorators import operation 18 | from vcloud_plugin_common import (with_vca_client, wait_for_task, 19 | get_vcloud_config, get_mandatory) 20 | import collections 21 | from network_plugin import (check_ip, is_valid_ip_range, is_separate_ranges, 22 | is_ips_in_same_subnet, save_gateway_configuration, 23 | get_network_name, is_network_exists) 24 | 25 | 26 | VCLOUD_NETWORK_NAME = 'vcloud_network_name' 27 | ADD_POOL = 1 28 | DELETE_POOL = 2 29 | 30 | 31 | @operation 32 | @with_vca_client 33 | def create(vca_client, **kwargs): 34 | """ 35 | create new vcloud air network, e.g.: 36 | { 37 | 'use_external_resource': False, 38 | 'resource_id': 'secret_network', 39 | 'network': { 40 | 'dhcp': { 41 | 'dhcp_range': "10.1.1.128-10.1.1.255" 42 | }, 43 | 'static_range': "10.1.1.2-10.1.1.127", 44 | 'gateway_ip': "10.1.1.1", 45 | 'edge_gateway': 'gateway', 46 | 'name': 'secret_network', 47 | "netmask": '255.255.255.0', 48 | "dns": ["8.8.8.8", "4.4.4.4"] 49 | } 50 | } 51 | """ 52 | vdc_name = get_vcloud_config()['vdc'] 53 | if ctx.node.properties['use_external_resource']: 54 | network_name = ctx.node.properties['resource_id'] 55 | if not is_network_exists(vca_client, network_name): 56 | raise cfy_exc.NonRecoverableError( 57 | "Can't find external resource: {0}".format(network_name)) 58 | ctx.instance.runtime_properties[VCLOUD_NETWORK_NAME] = network_name 59 | ctx.logger.info( 60 | "External resource {0} has been used".format(network_name)) 61 | return 62 | net_prop = ctx.node.properties["network"] 63 | network_name = get_network_name(ctx.node.properties) 64 | if network_name in _get_network_list(vca_client, 65 | get_vcloud_config()['vdc']): 66 | raise cfy_exc.NonRecoverableError( 67 | "Network {0} already exists, but parameter " 68 | "'use_external_resource' is 'false' or absent" 69 | .format(network_name)) 70 | 71 | ip = _split_adresses(net_prop['static_range']) 72 | gateway_name = net_prop['edge_gateway'] 73 | if not vca_client.get_gateway(vdc_name, gateway_name): 74 | raise cfy_exc.NonRecoverableError( 75 | "Gateway {0} not found".format(gateway_name)) 76 | start_address = ip.start 77 | end_address = ip.end 78 | gateway_ip = net_prop["gateway_ip"] 79 | netmask = net_prop["netmask"] 80 | dns1 = "" 81 | dns2 = "" 82 | dns_list = net_prop.get("dns") 83 | if dns_list: 84 | dns1 = dns_list[0] 85 | if len(dns_list) > 1: 86 | dns2 = dns_list[1] 87 | dns_suffix = net_prop.get("dns_suffix") 88 | success, result = vca_client.create_vdc_network( 89 | vdc_name, network_name, gateway_name, start_address, 90 | end_address, gateway_ip, netmask, dns1, dns2, dns_suffix) 91 | if success: 92 | ctx.logger.info("Network {0} has been successfully created." 93 | .format(network_name)) 94 | else: 95 | raise cfy_exc.NonRecoverableError( 96 | "Could not create network {0}: {1}".format(network_name, result)) 97 | wait_for_task(vca_client, result) 98 | ctx.instance.runtime_properties[VCLOUD_NETWORK_NAME] = network_name 99 | _dhcp_operation(vca_client, network_name, ADD_POOL) 100 | 101 | 102 | @operation 103 | @with_vca_client 104 | def delete(vca_client, **kwargs): 105 | """ 106 | delete vcloud air network 107 | """ 108 | if ctx.node.properties['use_external_resource'] is True: 109 | del ctx.instance.runtime_properties[VCLOUD_NETWORK_NAME] 110 | ctx.logger.info("Network was not deleted - external resource has" 111 | " been used") 112 | return 113 | network_name = get_network_name(ctx.node.properties) 114 | _dhcp_operation(vca_client, network_name, DELETE_POOL) 115 | success, task = vca_client.delete_vdc_network( 116 | get_vcloud_config()['vdc'], network_name) 117 | if success: 118 | ctx.logger.info( 119 | "Network {0} has been successful deleted.".format(network_name)) 120 | else: 121 | raise cfy_exc.NonRecoverableError( 122 | "Could not delete network {0}".format(network_name)) 123 | wait_for_task(vca_client, task) 124 | 125 | 126 | @operation 127 | @with_vca_client 128 | def creation_validation(vca_client, **kwargs): 129 | """ 130 | check network description from node description 131 | """ 132 | network_name = get_network_name(ctx.node.properties) 133 | ctx.logger.info("Validation cloudify.vcloud.nodes.Network node: {0}" 134 | .format(network_name)) 135 | if is_network_exists(vca_client, network_name): 136 | if ctx.node.properties.get('use_external_resource'): 137 | # TODO: check: default gateway must exists 138 | return 139 | else: 140 | raise cfy_exc.NonRecoverableError( 141 | "Network already exsists: {0}".format(network_name)) 142 | 143 | net_prop = get_mandatory(ctx.node.properties, "network") 144 | gateway_name = get_mandatory(net_prop, 'edge_gateway') 145 | if not vca_client.get_gateway(get_vcloud_config()['vdc'], gateway_name): 146 | raise cfy_exc.NonRecoverableError( 147 | "Gateway {0} not found".format(gateway_name)) 148 | 149 | static_ip = _split_adresses(get_mandatory(net_prop, 'static_range')) 150 | check_ip(static_ip.start) 151 | check_ip(static_ip.end) 152 | dns_list = net_prop.get("dns") 153 | if dns_list: 154 | for ip in dns_list: 155 | check_ip(ip) 156 | gateway_ip = check_ip(get_mandatory(net_prop, "gateway_ip")) 157 | netmask = check_ip(get_mandatory(net_prop, "netmask")) 158 | 159 | ips = [gateway_ip, static_ip.start, static_ip.end] 160 | dhcp = net_prop.get("dhcp") 161 | if dhcp: 162 | dhcp_range = get_mandatory(net_prop["dhcp"], "dhcp_range") 163 | dhcp_ip = _split_adresses(dhcp_range) 164 | if not is_separate_ranges(static_ip, dhcp_ip): 165 | raise cfy_exc.NonRecoverableError( 166 | "Static_range and dhcp_range is overlapped.") 167 | ips.extend([dhcp_ip.start, dhcp_ip.end]) 168 | if not is_ips_in_same_subnet(ips, netmask): 169 | raise cfy_exc.NonRecoverableError( 170 | "IP addresses in different subnets.") 171 | 172 | 173 | def _dhcp_operation(vca_client, network_name, operation): 174 | """ 175 | update dhcp setting for network 176 | """ 177 | dhcp_settings = ctx.node.properties['network'].get('dhcp') 178 | if dhcp_settings is None: 179 | return 180 | gateway_name = ctx.node.properties["network"]['edge_gateway'] 181 | gateway = vca_client.get_gateway(get_vcloud_config()['vdc'], gateway_name) 182 | if not gateway: 183 | raise cfy_exc.NonRecoverableError( 184 | "Gateway {0} not found!".format(gateway_name)) 185 | 186 | if operation == ADD_POOL: 187 | ip = _split_adresses(dhcp_settings['dhcp_range']) 188 | low_ip_address = check_ip(ip.start) 189 | hight_ip_address = check_ip(ip.end) 190 | default_lease = dhcp_settings.get('default_lease') 191 | max_lease = dhcp_settings.get('max_lease') 192 | gateway.add_dhcp_pool(network_name, low_ip_address, hight_ip_address, 193 | default_lease, max_lease) 194 | ctx.logger.info("DHCP rule successful created for network {0}" 195 | .format(network_name)) 196 | 197 | if operation == DELETE_POOL: 198 | gateway.delete_dhcp_pool(network_name) 199 | ctx.logger.info("DHCP rule successful deleted for network {0}" 200 | .format(network_name)) 201 | 202 | if not save_gateway_configuration(gateway, vca_client): 203 | return ctx.operation.retry(message='Waiting for gateway.', 204 | retry_after=10) 205 | 206 | 207 | def _split_adresses(address_range): 208 | """ 209 | split network addresses from 1.1.1.1-2.2.2.2 representation to 210 | separate (start,end) tuple 211 | """ 212 | adresses = [ip.strip() for ip in address_range.split('-')] 213 | IPRange = collections.namedtuple('IPRange', 'start end') 214 | try: 215 | start = check_ip(adresses[0]) 216 | end = check_ip(adresses[1]) 217 | if not is_valid_ip_range(start, end): 218 | raise cfy_exc.NonRecoverableError( 219 | "Start address {0} is greater than end address: {1}" 220 | .format(start, end)) 221 | return IPRange(start=start, end=end) 222 | except IndexError: 223 | raise cfy_exc.NonRecoverableError("Can't parse IP range:{0}". 224 | format(address_range)) 225 | 226 | 227 | def _get_network_list(vca_client, vdc_name): 228 | """ 229 | list all avable network for current vdc 230 | """ 231 | vdc = vca_client.get_vdc(vdc_name) 232 | if not vdc: 233 | raise cfy_exc.NonRecoverableError( 234 | "Vdc {0} not found.".format(vdc_name)) 235 | return [net.name for net in vdc.AvailableNetworks.Network] 236 | -------------------------------------------------------------------------------- /network_plugin/port.py: -------------------------------------------------------------------------------- 1 | from cloudify import ctx 2 | from cloudify import exceptions as cfy_exc 3 | from cloudify.decorators import operation 4 | from vcloud_plugin_common import with_vca_client, get_mandatory 5 | from network_plugin import check_ip 6 | 7 | 8 | @operation 9 | @with_vca_client 10 | def creation_validation(vca_client, **kwargs): 11 | """ 12 | validate port settings, 13 | ip_allocation_mode must be in 'manual', 'dhcp', 'pool', 14 | and valid ip_address if set 15 | """ 16 | port = get_mandatory(ctx.node.properties, 'port') 17 | ip_allocation_mode = port.get('ip_allocation_mode') 18 | if ip_allocation_mode: 19 | if ip_allocation_mode.lower() not in ['manual', 'dhcp', 'pool']: 20 | raise cfy_exc.NonRecoverableError( 21 | "Unknown allocation mode {0}".format(ip_allocation_mode)) 22 | ip_address = port.get('ip_address') 23 | if ip_address: 24 | check_ip(ip_address) 25 | -------------------------------------------------------------------------------- /network_plugin/public_nat.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # * See the License for the specific language governing permissions and 13 | # * limitations under the License. 14 | 15 | from cloudify import ctx 16 | from cloudify import exceptions as cfy_exc 17 | from cloudify.decorators import operation 18 | from vcloud_plugin_common import (with_vca_client, get_vcloud_config, 19 | get_mandatory, is_subscription, is_ondemand) 20 | from network_plugin import (check_ip, save_gateway_configuration, 21 | get_vm_ip, get_public_ip, 22 | get_gateway, getFreeIP, CREATE, DELETE, PUBLIC_IP, 23 | del_ondemand_public_ip, utils) 24 | from network_plugin.network import VCLOUD_NETWORK_NAME 25 | from IPy import IP 26 | 27 | PORT_REPLACEMENT = 'port_replacement' 28 | 29 | 30 | @operation 31 | @with_vca_client 32 | def net_connect_to_nat(vca_client, **kwargs): 33 | """ 34 | create nat rule for current node 35 | """ 36 | if ctx.target.node.properties.get('use_external_resource', False): 37 | ctx.logger.info("Using existing Public NAT.") 38 | return 39 | prepare_network_operation(vca_client, CREATE) 40 | 41 | 42 | @operation 43 | @with_vca_client 44 | def net_disconnect_from_nat(vca_client, **kwargs): 45 | """ 46 | drop nat rule for current node 47 | """ 48 | if ctx.target.node.properties.get('use_external_resource', False): 49 | ctx.logger.info("Using existing Public NAT.") 50 | return 51 | prepare_network_operation(vca_client, DELETE) 52 | 53 | 54 | @operation 55 | @with_vca_client 56 | def server_connect_to_nat(vca_client, **kwargs): 57 | """ 58 | create nat rules for server 59 | """ 60 | prepare_server_operation(vca_client, CREATE) 61 | 62 | 63 | @operation 64 | @with_vca_client 65 | def server_disconnect_from_nat(vca_client, **kwargs): 66 | """ 67 | drop nat rules for server 68 | """ 69 | prepare_server_operation(vca_client, DELETE) 70 | 71 | 72 | @operation 73 | @with_vca_client 74 | def creation_validation(vca_client, **kwargs): 75 | """ 76 | validate nat rules in node properties 77 | """ 78 | nat = get_mandatory(ctx.node.properties, 'nat') 79 | gateway = get_gateway(vca_client, get_mandatory(nat, 'edge_gateway')) 80 | service_type = get_vcloud_config().get('service_type') 81 | public_ip = nat.get(PUBLIC_IP) 82 | if public_ip: 83 | check_ip(public_ip) 84 | else: 85 | if is_subscription(service_type): 86 | getFreeIP(gateway) 87 | for rule in get_mandatory(ctx.node.properties, 'rules'): 88 | if rule['type'] == "DNAT": 89 | utils.check_protocol(rule.get('protocol')) 90 | original_port = rule.get('original_port') 91 | if original_port and not isinstance(original_port, int): 92 | raise cfy_exc.NonRecoverableError( 93 | "Parameter 'original_port' must be integer") 94 | translated_port = rule.get('translated_port') 95 | if translated_port and not isinstance(translated_port, int): 96 | raise cfy_exc.NonRecoverableError( 97 | "Parameter 'translated_port' must be integer") 98 | 99 | 100 | @operation 101 | @with_vca_client 102 | def creation_validation(vca_client, **kwargs): 103 | nat = get_mandatory(ctx.node.properties, 'nat') 104 | rules = get_mandatory(ctx.node.properties, 'rules') 105 | gateway = get_gateway(vca_client, get_mandatory(nat, 'edge_gateway')) 106 | service_type = get_vcloud_config().get('service_type') 107 | public_ip = nat.get(PUBLIC_IP) 108 | if public_ip: 109 | check_ip(public_ip) 110 | CheckAssignedExternalIp(public_ip, gateway) 111 | else: 112 | if isSubscription(service_type): 113 | getFreeIP(gateway) 114 | check_protocol(rules.get('protocol', "any")) 115 | original_port = rules.get('original_port') 116 | if original_port and not isinstance(original_port, int): 117 | raise cfy_exc.NonRecoverableError("Parameter 'original_port' must be integer") 118 | translated_port = rules.get('translated_port') 119 | if translated_port and not isinstance(translated_port, int): 120 | raise cfy_exc.NonRecoverableError("Parameter 'translated_port' must be integer") 121 | 122 | 123 | def prepare_network_operation(vca_client, operation): 124 | """ 125 | create nat rules by rules from network node 126 | """ 127 | try: 128 | gateway = get_gateway( 129 | vca_client, ctx.target.node.properties['nat']['edge_gateway']) 130 | public_ip = _obtain_public_ip(vca_client, ctx, gateway, operation) 131 | private_ip = _create_ip_range(vca_client, gateway) 132 | for rule in ctx.target.node.properties['rules']: 133 | rule_type = rule['type'] 134 | nat_network_operation( 135 | vca_client, gateway, operation, 136 | rule_type, public_ip, 137 | private_ip, "any", "any", "any") 138 | except KeyError as e: 139 | raise cfy_exc.NonRecoverableError( 140 | "Parameter not found: {0}".format(e) 141 | ) 142 | _save_configuration(gateway, vca_client, operation, public_ip) 143 | 144 | 145 | def prepare_server_operation(vca_client, operation): 146 | """ 147 | generate nat rules by current list of rules in node 148 | """ 149 | try: 150 | gateway = get_gateway( 151 | vca_client, ctx.target.node.properties['nat']['edge_gateway']) 152 | public_ip = _obtain_public_ip(vca_client, ctx, gateway, operation) 153 | private_ip = get_vm_ip(vca_client, ctx, gateway) 154 | for rule in ctx.target.node.properties['rules']: 155 | rule_type = rule['type'] 156 | protocol = rule.get('protocol', "any") 157 | original_port = rule.get('original_port', "any") 158 | translated_port = rule.get('translated_port', "any") 159 | nat_network_operation( 160 | vca_client, gateway, operation, 161 | rule_type, public_ip, 162 | private_ip, original_port, translated_port, protocol) 163 | except KeyError as e: 164 | raise cfy_exc.NonRecoverableError("Parameter not found: {0}".format(e)) 165 | _save_configuration(gateway, vca_client, operation, public_ip) 166 | 167 | 168 | def nat_network_operation(vca_client, gateway, operation, rule_type, public_ip, 169 | private_ip, original_port, translated_port, 170 | protocol): 171 | """ 172 | create/drop nat rule for current network 173 | """ 174 | if operation == CREATE: 175 | new_original_port = _get_original_port_for_create( 176 | gateway, rule_type, public_ip, original_port, 177 | private_ip, translated_port, protocol) 178 | function = gateway.add_nat_rule 179 | message = "Add" 180 | elif operation == DELETE: 181 | new_original_port = _get_original_port_for_delete( 182 | public_ip, original_port) 183 | function = gateway.del_nat_rule 184 | message = "Remove" 185 | else: 186 | raise cfy_exc.NonRecoverableError( 187 | "Unknown operation: {0}".format(operation)) 188 | 189 | info_message = ("{6} NAT rule: rule type '{2}', original_ip '{0}', " 190 | "translated_ip '{1}',protocol '{3}', " 191 | "original_port '{4}', translated_port '{5}'") 192 | if rule_type == "SNAT": 193 | # for SNAT type ports and protocol must by "any", 194 | # because they are not configurable 195 | ctx.logger.info( 196 | info_message.format(private_ip, public_ip, rule_type, protocol, 197 | new_original_port, translated_port, 198 | message)) 199 | function( 200 | rule_type, private_ip, "any", public_ip, "any", "any") 201 | elif rule_type == "DNAT": 202 | ctx.logger.info( 203 | info_message.format(public_ip, private_ip, rule_type, protocol, 204 | new_original_port, translated_port, 205 | message)) 206 | function(rule_type, public_ip, str(new_original_port), private_ip, 207 | str(translated_port), protocol) 208 | 209 | 210 | def _save_configuration(gateway, vca_client, operation, public_ip): 211 | """ 212 | save/refresh nat rules on gateway 213 | """ 214 | if not save_gateway_configuration(gateway, vca_client): 215 | return ctx.operation.retry(message='Waiting for gateway.', 216 | retry_after=10) 217 | ctx.logger.info("NAT configuration has been saved") 218 | if operation == CREATE: 219 | ctx.target.instance.runtime_properties[PUBLIC_IP] = public_ip 220 | else: 221 | service_type = get_vcloud_config().get('service_type') 222 | if is_ondemand(service_type): 223 | if not ctx.target.node.properties['nat'].get(PUBLIC_IP): 224 | del_ondemand_public_ip( 225 | vca_client, gateway, 226 | ctx.target.instance.runtime_properties[PUBLIC_IP], 227 | ctx 228 | ) 229 | del ctx.target.instance.runtime_properties[PUBLIC_IP] 230 | 231 | 232 | def _create_ip_range(vca_client, gateway): 233 | """ 234 | return ip range by avaible ranges from gateway and current network 235 | """ 236 | network_name = ctx.source.instance.runtime_properties[VCLOUD_NETWORK_NAME] 237 | org_name = get_vcloud_config()['org'] 238 | net = _get_network_ip_range(vca_client, org_name, network_name) 239 | gate = _get_gateway_ip_range(gateway, network_name) 240 | if not net: 241 | raise cfy_exc.NonRecoverableError( 242 | "Unknown network: {0}".format(network_name)) 243 | if gate: 244 | return "{} - {}".format(min(net[0], gate[0]), max(net[1], gate[1])) 245 | else: 246 | return "{} - {}".format(min(net), max(net)) 247 | 248 | 249 | def _get_network_ip_range(vca_client, org_name, network_name): 250 | """ 251 | return ips for network from network configuration ipscopes 252 | """ 253 | networks = vca_client.get_networks(org_name) 254 | ip_scope = [net.Configuration.IpScopes.IpScope 255 | for net in networks if network_name == net.get_name()] 256 | addresses = [] 257 | for scope in ip_scope: 258 | for ip in scope[0].IpRanges.IpRange: 259 | addresses.append(IP(ip.get_StartAddress())) 260 | addresses.append(IP(ip.get_EndAddress())) 261 | if addresses: 262 | return min(addresses), max(addresses) 263 | else: 264 | return None 265 | 266 | 267 | def _get_gateway_ip_range(gateway, network_name): 268 | """ 269 | return avaible ip ranges for current gateway 270 | """ 271 | addresses = [] 272 | pools = gateway.get_dhcp_pools() 273 | if not pools: 274 | return None 275 | for pool in pools: 276 | if pool.Network.name == network_name: 277 | addresses.append(IP(pool.get_LowIpAddress())) 278 | addresses.append(IP(pool.get_HighIpAddress())) 279 | if addresses: 280 | return min(addresses), max(addresses) 281 | else: 282 | return None 283 | 284 | 285 | def _obtain_public_ip(vca_client, ctx, gateway, operation): 286 | """ 287 | return public ip for rules, 288 | in delete case - returned already used 289 | in create case - return new free ip 290 | """ 291 | public_ip = None 292 | if operation == CREATE: 293 | public_ip = ctx.target.node.properties['nat'].get(PUBLIC_IP) 294 | if not public_ip: 295 | service_type = get_vcloud_config().get('service_type') 296 | public_ip = get_public_ip(vca_client, gateway, service_type, ctx) 297 | elif operation == DELETE: 298 | if PUBLIC_IP in ctx.target.instance.runtime_properties: 299 | public_ip = ctx.target.instance.runtime_properties[PUBLIC_IP] 300 | else: 301 | raise cfy_exc.NonRecoverableError( 302 | "Can't obtain public IP from runtime properties") 303 | else: 304 | raise cfy_exc.NonRecoverableError("Unknown operation") 305 | 306 | return public_ip 307 | 308 | 309 | def _get_original_port_for_create( 310 | gateway, rule_type, original_ip, original_port, translated_ip, 311 | translated_port, protocol 312 | ): 313 | """ 314 | return port that can be used in rule, if port have already used 315 | return new port that is next free port after current 316 | """ 317 | nat_rules = gateway.get_nat_rules() 318 | if isinstance( 319 | original_port, basestring) and original_port.lower() == 'any': 320 | if _is_rule_exists( 321 | nat_rules, rule_type, original_ip, 322 | original_port, translated_ip, 323 | translated_port, protocol): 324 | raise cfy_exc.NonRecoverableError( 325 | "The same NAT rule already exsists: " 326 | "original_ip '{0}',translated_ip '{1}', " 327 | "rule type '{2}', protocol '{3}', original_port '{4}, " 328 | "translated_port {5}'".format( 329 | original_ip, translated_ip, rule_type, protocol, 330 | original_port, translated_port)) 331 | else: 332 | return original_port 333 | 334 | # origin port can be string 335 | for port in xrange(int(original_port), utils.MAX_PORT_NUMBER + 1): 336 | if not _is_rule_exists(nat_rules, rule_type, original_ip, 337 | port, translated_ip, 338 | translated_port, protocol): 339 | if port == original_port: 340 | return original_port 341 | else: 342 | ctx.logger.info( 343 | "For IP {} replace original port {} -> {}" 344 | .format(original_ip, original_port, port)) 345 | if (PORT_REPLACEMENT not in 346 | ctx.target.instance.runtime_properties): 347 | ctx.target.instance.runtime_properties[ 348 | PORT_REPLACEMENT] = {} 349 | ctx.target.instance.runtime_properties[ 350 | PORT_REPLACEMENT][ 351 | (original_ip, original_port)] = port 352 | return port 353 | raise cfy_exc.NonRecoverableError( 354 | "Can't create NAT rule because maximum port number was reached") 355 | 356 | 357 | def _get_original_port_for_delete(original_ip, original_port): 358 | """ 359 | check may be we already replaced port by some new free port 360 | """ 361 | if PORT_REPLACEMENT in ctx.target.instance.runtime_properties: 362 | runtime_properties = ctx.target.instance.runtime_properties 363 | port = runtime_properties[PORT_REPLACEMENT].get( 364 | (original_ip, original_port) 365 | ) 366 | return port if port else original_port 367 | else: 368 | return original_port 369 | 370 | 371 | def _is_rule_exists(nat_rules, rule_type, 372 | original_ip, original_port, 373 | translated_ip, translated_port, protocol): 374 | """ 375 | check if we already have some rule with same properties 376 | """ 377 | # gatewayNatRule properties may be None or string 378 | # convert to str, bacause port can be int 379 | cicmp = lambda t: t[1] and (str(t[0]).lower() == str(t[1]).lower()) 380 | for natRule in nat_rules: 381 | gatewayNatRule = natRule.get_GatewayNatRule() 382 | if (all(map(cicmp, [ 383 | (rule_type, natRule.get_RuleType()), 384 | (original_ip, gatewayNatRule.get_OriginalIp()), 385 | (str(original_port), gatewayNatRule.get_OriginalPort()), 386 | (translated_ip, gatewayNatRule.get_TranslatedIp()), 387 | (str(translated_port), gatewayNatRule.get_TranslatedPort()), 388 | (protocol, gatewayNatRule.get_Protocol())]))): 389 | break 390 | else: 391 | return False 392 | return True 393 | -------------------------------------------------------------------------------- /network_plugin/security_group.py: -------------------------------------------------------------------------------- 1 | from cloudify import ctx 2 | from cloudify import exceptions as cfy_exc 3 | from cloudify.decorators import operation 4 | from vcloud_plugin_common import (with_vca_client, get_mandatory, 5 | get_vcloud_config) 6 | from network_plugin import (check_ip, get_vm_ip, save_gateway_configuration, 7 | get_gateway, utils) 8 | 9 | 10 | CREATE_RULE = 1 11 | DELETE_RULE = 2 12 | 13 | ADDRESS_LITERALS = ("any", "internal", "external", "host") 14 | ACTIONS = ("allow", "deny") 15 | 16 | 17 | @operation 18 | @with_vca_client 19 | def create(vca_client, **kwargs): 20 | """ 21 | create firewall rules for node 22 | """ 23 | _rule_operation(CREATE_RULE, vca_client) 24 | 25 | 26 | @operation 27 | @with_vca_client 28 | def delete(vca_client, **kwargs): 29 | """ 30 | drop firewall rules for node 31 | """ 32 | _rule_operation(DELETE_RULE, vca_client) 33 | 34 | 35 | @operation 36 | @with_vca_client 37 | def creation_validation(vca_client, **kwargs): 38 | """ 39 | validate firewall rules for node 40 | """ 41 | getaway = get_gateway( 42 | vca_client, _get_gateway_name(ctx.node.properties) 43 | ) 44 | if not getaway.is_fw_enabled(): 45 | raise cfy_exc.NonRecoverableError( 46 | "Gateway firewall is disabled. Please, enable firewall.") 47 | rules = get_mandatory(ctx.node.properties, 'rules') 48 | for rule in rules: 49 | description = rule.get("description") 50 | if description and not isinstance(description, basestring): 51 | raise cfy_exc.NonRecoverableError( 52 | "Parameter 'description' must be string.") 53 | 54 | source = rule.get("source") 55 | if source: 56 | if not isinstance(source, basestring): 57 | raise cfy_exc.NonRecoverableError( 58 | "Parameter 'source' must be valid IP address string.") 59 | if not _is_literal_ip(source): 60 | check_ip(source) 61 | 62 | utils.check_port(rule.get('source_port')) 63 | 64 | destination = rule.get('destination') 65 | if destination: 66 | if not isinstance(destination, basestring): 67 | raise cfy_exc.NonRecoverableError( 68 | "Parameter 'destination' must be valid IP address string.") 69 | if not _is_literal_ip(destination): 70 | check_ip(destination) 71 | 72 | utils.check_port(rule.get('destination_port')) 73 | 74 | utils.check_protocol(rule.get('protocol')) 75 | 76 | action = get_mandatory(rule, "action") 77 | if (not isinstance(action, basestring) 78 | or action.lower() not in ACTIONS): 79 | raise cfy_exc.NonRecoverableError( 80 | "Action must be on of{0}.".format(ACTIONS)) 81 | 82 | log = rule.get('log_traffic') 83 | if log and not isinstance(log, bool): 84 | raise cfy_exc.NonRecoverableError( 85 | "Parameter 'log_traffic' must be boolean.") 86 | 87 | 88 | def _rule_operation(operation, vca_client): 89 | """ 90 | create/delete firewall rules in gateway for current node 91 | """ 92 | gateway = get_gateway( 93 | vca_client, _get_gateway_name(ctx.target.node.properties)) 94 | for rule in ctx.target.node.properties['rules']: 95 | description = rule.get('description', "Rule added by pyvcloud").strip() 96 | source_ip = rule.get("source", "external") 97 | if not _is_literal_ip(source_ip): 98 | check_ip(source_ip) 99 | elif _is_host_ip(source_ip): 100 | source_ip = get_vm_ip(vca_client, ctx, gateway) 101 | source_port = str(rule.get("source_port", "any")) 102 | dest_ip = rule.get("destination", "external") 103 | if not _is_literal_ip(dest_ip): 104 | check_ip(dest_ip) 105 | elif _is_host_ip(dest_ip): 106 | dest_ip = get_vm_ip(vca_client, ctx, gateway) 107 | dest_port = str(rule.get('destination_port', 'any')) 108 | protocol = rule.get('protocol', 'any').capitalize() 109 | action = rule.get("action", "allow") 110 | log = rule.get('log_traffic', False) 111 | 112 | if operation == CREATE_RULE: 113 | gateway.add_fw_rule( 114 | True, description, action, protocol, dest_port, dest_ip, 115 | source_port, source_ip, log) 116 | ctx.logger.info( 117 | "Firewall rule has been created: {0}".format(description)) 118 | elif operation == DELETE_RULE: 119 | gateway.delete_fw_rule(protocol, dest_port, dest_ip, 120 | source_port, source_ip) 121 | ctx.logger.info( 122 | "Firewall rule has been deleted: {0}".format(description)) 123 | 124 | if not save_gateway_configuration(gateway, vca_client): 125 | return ctx.operation.retry(message='Waiting for gateway.', 126 | retry_after=10) 127 | 128 | 129 | def _get_gateway_name(properties): 130 | """ 131 | return geteway for current node from vcloud config or security 132 | group settings 133 | """ 134 | security_group = properties.get('security_group') 135 | if security_group and 'edge_gateway' in security_group: 136 | getaway_name = security_group.get('edge_gateway') 137 | else: 138 | getaway_name = get_vcloud_config()['edge_gateway'] 139 | return getaway_name 140 | 141 | 142 | def _is_literal_ip(ip): 143 | return ip.lower() in ADDRESS_LITERALS 144 | 145 | 146 | def _is_host_ip(ip): 147 | return ip.lower() == "host" 148 | -------------------------------------------------------------------------------- /network_plugin/utils.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 GigaSpaces Technologies Ltd. All rights reserved 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # * See the License for the specific language governing permissions and 13 | # * limitations under the License. 14 | 15 | 16 | from cloudify import exceptions as cfy_exc 17 | 18 | 19 | # 2 ^ 16 - 1 20 | MAX_PORT_NUMBER = 65535 21 | VALID_PROTOCOLS = ["Tcp", "Udp", "Tcpudp", "Icmp", "Any"] 22 | 23 | 24 | def check_protocol(protocol): 25 | """ 26 | check protocol 27 | """ 28 | if protocol is None: 29 | protocol = 'any' 30 | if not _protocol_is_valid(protocol): 31 | raise cfy_exc.NonRecoverableError( 32 | "Unknown protocol: {0}. Valid protocols are: {1}" 33 | .format(protocol, VALID_PROTOCOLS)) 34 | return protocol 35 | 36 | 37 | def _protocol_is_valid(protocol): 38 | """ 39 | check protocol in list valid protocols 40 | """ 41 | return protocol.capitalize() in VALID_PROTOCOLS 42 | 43 | 44 | def check_port(port): 45 | """ 46 | check port, 1..65535 or 'any' 47 | """ 48 | if port is None: 49 | port = 'any' 50 | if isinstance(port, int): 51 | if 0 < port < MAX_PORT_NUMBER + 1: 52 | return port 53 | else: 54 | raise cfy_exc.NonRecoverableError( 55 | "Invalid 'port' value. " 56 | "Port value must be between 1 and 65535") 57 | elif isinstance(port, basestring): 58 | port = port.lower() 59 | if _port_is_any(port): 60 | return port 61 | raise cfy_exc.NonRecoverableError( 62 | "Parameter 'port' must be integer, or 'any'") 63 | 64 | 65 | def _port_is_any(port): 66 | """ 67 | checks that port is 'any' 68 | """ 69 | return port == 'any' 70 | -------------------------------------------------------------------------------- /plugin.yaml: -------------------------------------------------------------------------------- 1 | plugins: 2 | vcloud: 3 | executor: central_deployment_agent 4 | source: https://github.com/vmware/tosca-vcloud-plugin/archive/master.zip 5 | 6 | node_types: 7 | cloudify.vcloud.nodes.Server: 8 | derived_from: cloudify.nodes.Compute 9 | properties: 10 | use_external_resource: 11 | default: false 12 | resource_id: 13 | default: '' 14 | server: 15 | default: {} 16 | management_network: 17 | default: '' 18 | vcloud_config: 19 | default: {} 20 | interfaces: 21 | cloudify.interfaces.lifecycle: 22 | create: 23 | implementation: vcloud.server_plugin.server.create 24 | inputs: {} 25 | start: 26 | implementation: vcloud.server_plugin.server.start 27 | inputs: {} 28 | stop: 29 | implementation: vcloud.server_plugin.server.stop 30 | inputs: {} 31 | delete: 32 | implementation: vcloud.server_plugin.server.delete 33 | inputs: {} 34 | cloudify.interfaces.validation: 35 | creation: 36 | implementation: vcloud.server_plugin.server.creation_validation 37 | 38 | cloudify.vcloud.nodes.Network: 39 | derived_from: cloudify.nodes.Network 40 | properties: 41 | network: 42 | default: {} 43 | use_external_resource: 44 | default: false 45 | resource_id: 46 | default: '' 47 | vcloud_config: 48 | default: {} 49 | interfaces: 50 | cloudify.interfaces.lifecycle: 51 | create: 52 | implementation: vcloud.network_plugin.network.create 53 | inputs: {} 54 | delete: 55 | implementation: vcloud.network_plugin.network.delete 56 | inputs: {} 57 | cloudify.interfaces.validation: 58 | creation: 59 | implementation: vcloud.network_plugin.network.creation_validation 60 | 61 | cloudify.vcloud.nodes.Port: 62 | derived_from: cloudify.nodes.Port 63 | properties: 64 | port: 65 | default: {} 66 | vcloud_config: 67 | default: {} 68 | interfaces: 69 | cloudify.interfaces.validation: 70 | creation: 71 | implementation: vcloud.network_plugin.port.creation_validation 72 | 73 | cloudify.vcloud.nodes.FloatingIP: 74 | derived_from: cloudify.nodes.VirtualIP 75 | properties: 76 | floatingip: 77 | default: {} 78 | vcloud_config: 79 | default: {} 80 | interfaces: 81 | cloudify.interfaces.validation: 82 | creation: 83 | implementation: vcloud.network_plugin.floatingip.creation_validation 84 | 85 | cloudify.vcloud.nodes.PublicNAT: 86 | derived_from: cloudify.nodes.VirtualIP 87 | properties: 88 | use_external_resource: 89 | default: false 90 | nat: 91 | default: {} 92 | rules: 93 | default: [] 94 | vcloud_config: 95 | default: {} 96 | interfaces: 97 | cloudify.interfaces.validation: 98 | creation: 99 | implementation: vcloud.network_plugin.public_nat.creation_validation 100 | 101 | cloudify.vcloud.nodes.SecurityGroup: 102 | derived_from: cloudify.nodes.SecurityGroup 103 | properties: 104 | security_group: 105 | default: {} 106 | rules: 107 | default: [] 108 | vcloud_config: 109 | default: {} 110 | interfaces: 111 | cloudify.interfaces.validation: 112 | creation: 113 | implementation: vcloud.network_plugin.security_group.creation_validation 114 | 115 | cloudify.vcloud.nodes.KeyPair: 116 | derived_from: cloudify.nodes.Root 117 | properties: 118 | private_key_path: 119 | default: '' 120 | public_key: 121 | default: {} 122 | vcloud_config: 123 | default: {} 124 | interfaces: 125 | cloudify.interfaces.validation: 126 | creation: 127 | implementation: vcloud.network_plugin.keypair.creation_validation 128 | 129 | 130 | cloudify.vcloud.nodes.Volume: 131 | derived_from: cloudify.nodes.Volume 132 | properties: 133 | device_name: 134 | default: '' 135 | volume: 136 | default: {} 137 | use_external_resource: 138 | default: false 139 | resource_id: 140 | default: '' 141 | vcloud_config: 142 | default: {} 143 | interfaces: 144 | cloudify.interfaces.lifecycle: 145 | create: 146 | implementation: vcloud.server_plugin.volume.create_volume 147 | inputs: {} 148 | delete: 149 | implementation: vcloud.server_plugin.volume.delete_volume 150 | inputs: {} 151 | cloudify.interfaces.validation: 152 | creation: 153 | implementation: vcloud.server_plugin.volume.creation_validation 154 | 155 | relationships: 156 | cloudify.vcloud.server_connected_to_floating_ip: 157 | derived_from: cloudify.relationships.connected_to 158 | target_interfaces: 159 | cloudify.interfaces.relationship_lifecycle: 160 | establish: 161 | implementation: vcloud.network_plugin.floatingip.connect_floatingip 162 | inputs: {} 163 | unlink: 164 | implementation: vcloud.network_plugin.floatingip.disconnect_floatingip 165 | inputs: {} 166 | cloudify.vcloud.server_connected_to_network: 167 | derived_from: cloudify.relationships.connected_to 168 | cloudify.vcloud.port_connected_to_network: 169 | derived_from: cloudify.relationships.connected_to 170 | cloudify.vcloud.server_connected_to_port: 171 | derived_from: cloudify.relationships.connected_to 172 | cloudify.vcloud.server_connected_to_security_group: 173 | derived_from: cloudify.relationships.connected_to 174 | target_interfaces: 175 | cloudify.interfaces.relationship_lifecycle: 176 | establish: 177 | implementation: vcloud.network_plugin.security_group.create 178 | inputs: {} 179 | unlink: 180 | implementation: vcloud.network_plugin.security_group.delete 181 | inputs: {} 182 | cloudify.vcloud.net_connected_to_public_nat: 183 | derived_from: cloudify.relationships.connected_to 184 | target_interfaces: 185 | cloudify.interfaces.relationship_lifecycle: 186 | establish: 187 | implementation: vcloud.network_plugin.public_nat.net_connect_to_nat 188 | inputs: {} 189 | unlink: 190 | implementation: vcloud.network_plugin.public_nat.net_disconnect_from_nat 191 | inputs: {} 192 | cloudify.vcloud.server_connected_to_public_nat: 193 | derived_from: cloudify.relationships.connected_to 194 | target_interfaces: 195 | cloudify.interfaces.relationship_lifecycle: 196 | establish: 197 | implementation: vcloud.network_plugin.public_nat.server_connect_to_nat 198 | inputs: {} 199 | unlink: 200 | implementation: vcloud.network_plugin.public_nat.server_disconnect_from_nat 201 | inputs: {} 202 | 203 | cloudify.vcloud.volume_attached_to_server: 204 | derived_from: cloudify.relationships.connected_to 205 | target_interfaces: 206 | cloudify.interfaces.relationship_lifecycle: 207 | establish: 208 | implementation: vcloud.server_plugin.volume.attach_volume 209 | inputs: {} 210 | unlink: 211 | implementation: vcloud.server_plugin.volume.detach_volume 212 | inputs: {} 213 | -------------------------------------------------------------------------------- /server_plugin/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmware-archive/tosca-vcloud-plugin/c2588eec2d4404190018ab0101d3733afd87cfe0/server_plugin/__init__.py -------------------------------------------------------------------------------- /server_plugin/volume.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 GigaSpaces Technologies Ltd. All rights reserved 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # * See the License for the specific language governing permissions and 13 | # * limitations under the License. 14 | 15 | from cloudify import ctx 16 | from cloudify import exceptions as cfy_exc 17 | from cloudify.decorators import operation 18 | from vcloud_plugin_common import (wait_for_task, with_vca_client, 19 | get_vcloud_config, get_mandatory) 20 | from network_plugin import get_vapp_name 21 | 22 | 23 | @operation 24 | @with_vca_client 25 | def create_volume(vca_client, **kwargs): 26 | """ 27 | create new volume, e.g.: 28 | { 29 | 'use_external_resource': False, 30 | 'volume': { 31 | 'name': 'some-other', 32 | 'size': 11 33 | } 34 | } 35 | """ 36 | if ctx.node.properties.get('use_external_resource'): 37 | ctx.logger.info("External resource has been used") 38 | return 39 | vdc_name = get_vcloud_config()['vdc'] 40 | name = ctx.node.properties['volume']['name'] 41 | size = ctx.node.properties['volume']['size'] 42 | size_in_Mb = size * 1024 * 1024 43 | success, disk = vca_client.add_disk(vdc_name, name, size_in_Mb) 44 | if success: 45 | wait_for_task(vca_client, disk.get_Tasks()[0]) 46 | ctx.logger.info("Volume node {} has been created".format(name)) 47 | else: 48 | raise cfy_exc.NonRecoverableError( 49 | "Disk creation error: {0}".format(disk)) 50 | 51 | 52 | @operation 53 | @with_vca_client 54 | def delete_volume(vca_client, **kwargs): 55 | """ 56 | drop volume 57 | """ 58 | if ctx.node.properties.get('use_external_resource'): 59 | ctx.logger.info("External resource has been used") 60 | return 61 | vdc_name = get_vcloud_config()['vdc'] 62 | name = ctx.node.properties['volume']['name'] 63 | success, task = vca_client.delete_disk(vdc_name, name) 64 | if success: 65 | wait_for_task(vca_client, task) 66 | ctx.logger.info("Volume node {} has been deleted".format(name)) 67 | else: 68 | raise cfy_exc.NonRecoverableError( 69 | "Disk deletion error: {0}".format(task)) 70 | 71 | 72 | @operation 73 | @with_vca_client 74 | def creation_validation(vca_client, **kwargs): 75 | """ 76 | check volume description 77 | """ 78 | vdc_name = get_vcloud_config()['vdc'] 79 | disks_names = [ 80 | disk.name for [disk, _vms] in vca_client.get_disks(vdc_name) 81 | ] 82 | if ctx.node.properties.get('use_external_resource'): 83 | resource_id = get_mandatory(ctx.node.properties, 'resource_id') 84 | if resource_id not in disks_names: 85 | raise cfy_exc.NonRecoverableError( 86 | "Disk {} does't exists".format(resource_id)) 87 | else: 88 | volume = get_mandatory(ctx.node.properties, 'volume') 89 | name = get_mandatory(volume, 'name') 90 | if name in disks_names: 91 | raise cfy_exc.NonRecoverableError( 92 | "Disk {} already exists".format(name)) 93 | get_mandatory(volume, 'size') 94 | 95 | 96 | @operation 97 | @with_vca_client 98 | def attach_volume(vca_client, **kwargs): 99 | """ 100 | attach volume 101 | """ 102 | _volume_operation(vca_client, "ATTACH") 103 | 104 | 105 | @operation 106 | @with_vca_client 107 | def detach_volume(vca_client, **kwargs): 108 | """ 109 | detach volume 110 | """ 111 | _volume_operation(vca_client, "DETACH") 112 | 113 | 114 | def _volume_operation(vca_client, operation): 115 | """ 116 | attach/detach volume 117 | """ 118 | vdc_name = get_vcloud_config()['vdc'] 119 | vdc = vca_client.get_vdc(vdc_name) 120 | vmName = get_vapp_name(ctx.target.instance.runtime_properties) 121 | if ctx.source.node.properties.get('use_external_resource'): 122 | volumeName = ctx.source.node.properties['resource_id'] 123 | else: 124 | volumeName = ctx.source.node.properties['volume']['name'] 125 | vapp = vca_client.get_vapp(vdc, vmName) 126 | for ref in vca_client.get_diskRefs(vdc): 127 | if ref.name == volumeName: 128 | if operation == 'ATTACH': 129 | task = vapp.attach_disk_to_vm(vmName, ref) 130 | if task: 131 | wait_for_task(vca_client, task) 132 | ctx.logger.info( 133 | "Volume node {} has been attached".format(volumeName)) 134 | else: 135 | raise cfy_exc.NonRecoverableError( 136 | "Can't attach disk: {0}".format(volumeName)) 137 | 138 | elif operation == 'DETACH': 139 | task = vapp.detach_disk_from_vm(vmName, ref) 140 | if task: 141 | wait_for_task(vca_client, task) 142 | ctx.logger.info( 143 | "Volume node {} has been detached".format(volumeName)) 144 | else: 145 | raise cfy_exc.NonRecoverableError( 146 | "Can't detach disk: {0}".format(volumeName)) 147 | else: 148 | raise cfy_exc.NonRecoverableError( 149 | "Unknown operation {0}".format(operation)) 150 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | ######### 2 | # Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # * See the License for the specific language governing permissions and 14 | # * limitations under the License. 15 | 16 | from setuptools import setup 17 | 18 | setup( 19 | zip_safe=True, 20 | name='cloudify-vcloud-plugin', 21 | version='1.2', 22 | packages=[ 23 | 'vcloud_plugin_common', 24 | 'server_plugin', 25 | 'network_plugin' 26 | ], 27 | license='LICENSE', 28 | description='Cloudify plugin for vmWare vCloud infrastructure.', 29 | install_requires=[ 30 | 'cloudify-plugins-common>=3.2', 31 | 'pyvcloud==13rc10', 32 | 'requests==2.4', 33 | 'IPy==0.81', 34 | 'PyYAML==3.10' 35 | ] 36 | ) 37 | -------------------------------------------------------------------------------- /system_tests/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 GigaSpaces Technologies Ltd. All rights reserved 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # * See the License for the specific language governing permissions and 13 | # * limitations under the License. 14 | 15 | from pkgutil import extend_path 16 | 17 | __path__ = extend_path(__path__, __name__) 18 | -------------------------------------------------------------------------------- /system_tests/vcloud_handler.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 GigaSpaces Technologies Ltd. All rights reserved 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # * See the License for the specific language governing permissions and 13 | # * limitations under the License. 14 | 15 | from cosmo_tester.framework.handlers import ( 16 | BaseHandler, 17 | BaseCloudifyInputsConfigReader) 18 | 19 | 20 | class VcloudCleanupContext(BaseHandler.CleanupContext): 21 | 22 | def __init__(self, context_name, env): 23 | super(VcloudCleanupContext, self).__init__(context_name, env) 24 | 25 | def cleanup(self): 26 | super(VcloudCleanupContext, self).cleanup() 27 | 28 | 29 | class CloudifyVcloudInputsConfigReader(BaseCloudifyInputsConfigReader): 30 | 31 | def __init__(self, cloudify_config, manager_blueprint_path, **kwargs): 32 | super(CloudifyVcloudInputsConfigReader, self).__init__( 33 | cloudify_config, manager_blueprint_path=manager_blueprint_path, 34 | **kwargs) 35 | 36 | @property 37 | def vcloud_username(self): 38 | return self.config['vcloud_username'] 39 | 40 | @property 41 | def vcloud_password(self): 42 | return self.config['vcloud_password'] 43 | 44 | @property 45 | def vcloud_url(self): 46 | return self.config['vcloud_url'] 47 | 48 | @property 49 | def vcloud_service(self): 50 | return self.config['vcloud_service'] 51 | 52 | @property 53 | def vcloud_org(self): 54 | return self.config['vcloud_org'] 55 | 56 | @property 57 | def vcloud_vdc(self): 58 | return self.config['vcloud_vdc'] 59 | 60 | @property 61 | def manager_server_name(self): 62 | return self.config['manager_server_name'] 63 | 64 | @property 65 | def manager_server_catalog(self): 66 | return self.config['manager_server_catalog'] 67 | 68 | @property 69 | def manager_server_template(self): 70 | return self.config['manager_server_template'] 71 | 72 | @property 73 | def management_network_use_existing(self): 74 | return self.config['management_network_use_existing'] 75 | 76 | @property 77 | def management_network_name(self): 78 | return self.config['management_network_name'] 79 | 80 | @property 81 | def edge_gateway(self): 82 | return self.config['edge_gateway'] 83 | 84 | @property 85 | def floating_ip_public_ip(self): 86 | return self.config['floating_ip_public_ip'] 87 | 88 | @property 89 | def manager_private_key_path(self): 90 | return self.config['manager_private_key_path'] 91 | 92 | @property 93 | def agent_private_key_path(self): 94 | return self.config['agent_private_key_path'] 95 | 96 | @property 97 | def manager_public_key(self): 98 | return self.config['manager_public_key'] 99 | 100 | @property 101 | def agent_public_key(self): 102 | return self.config['agent_public_key'] 103 | 104 | @property 105 | def management_port_ip_allocation_mode(self): 106 | return self.config['management_port_ip_allocation_mode'] 107 | 108 | @property 109 | def vcloud_service_type(self): 110 | return self.config['vcloud_service_type'] 111 | 112 | @property 113 | def vcloud_region(self): 114 | return self.config['vcloud_region'] 115 | 116 | 117 | class VcloudHandler(BaseHandler): 118 | 119 | CleanupContext = VcloudCleanupContext 120 | CloudifyConfigReader = CloudifyVcloudInputsConfigReader 121 | 122 | def before_bootstrap(self): 123 | super(VcloudHandler, self).before_bootstrap() 124 | 125 | def after_bootstrap(self, provider_context): 126 | super(VcloudHandler, self).after_bootstrap(provider_context) 127 | 128 | 129 | handler = VcloudHandler 130 | -------------------------------------------------------------------------------- /test-requirements.txt: -------------------------------------------------------------------------------- 1 | mock>=1.0 2 | ipaddress>=1.0 3 | nose-testconfig>=0.9 4 | nose>=1.3 5 | tox>=1.9 6 | coverage 7 | pyflakes 8 | flake8 -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmware-archive/tosca-vcloud-plugin/c2588eec2d4404190018ab0101d3733afd87cfe0/tests/__init__.py -------------------------------------------------------------------------------- /tests/integration/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # * See the License for the specific language governing permissions and 13 | # * limitations under the License. 14 | 15 | from testconfig import config 16 | import mock 17 | import time 18 | import unittest 19 | 20 | from cloudify import mocks as cfy_mocks 21 | from cloudify.exceptions import OperationRetry 22 | from vcloud_plugin_common import Config, VcloudAirClient 23 | 24 | SUBSCRIPTION = 'subscription' 25 | ONDEMAND = 'ondemand' 26 | 27 | 28 | class IntegrationSubscriptionTestConfig(Config): 29 | VCLOUD_CONFIG_PATH_ENV_VAR = 'VCLOUD_INTEGRATION_TEST_CONFIG_PATH' 30 | VCLOUD_CONFIG_PATH_DEFAULT = \ 31 | '~/vcloud_integration_subscription_test_config.yaml' 32 | 33 | 34 | class IntegrationOndemandTestConfig(Config): 35 | VCLOUD_CONFIG_PATH_ENV_VAR = 'VCLOUD_INTEGRATION_TEST_CONFIG_PATH' 36 | VCLOUD_CONFIG_PATH_DEFAULT = \ 37 | '~/vcloud_integration_ondemand_test_config.yaml' 38 | 39 | 40 | class VcloudSubscriptionTestConfig(Config): 41 | VCLOUD_CONFIG_PATH_ENV_VAR = 'VCLOUD_CONFIG_PATH' 42 | VCLOUD_CONFIG_PATH_DEFAULT = '~/vcloud_config_subscription.yaml' 43 | 44 | 45 | class VcloudOndemandTestConfig(Config): 46 | VCLOUD_CONFIG_PATH_ENV_VAR = 'VCLOUD_CONFIG_PATH' 47 | VCLOUD_CONFIG_PATH_DEFAULT = '~/vcloud_config_ondemand.yaml' 48 | 49 | 50 | 51 | class TestCase(unittest.TestCase): 52 | vcloud_config = None # class variable 53 | test_config = None # class variable 54 | 55 | def __init__(self, testname): 56 | super(TestCase, self).__init__(testname) 57 | test_configs = { 58 | SUBSCRIPTION: (VcloudSubscriptionTestConfig().get(), 59 | IntegrationSubscriptionTestConfig().get()), 60 | ONDEMAND: (VcloudOndemandTestConfig().get(), 61 | IntegrationOndemandTestConfig().get())} 62 | if not config: 63 | raise RuntimeError( 64 | "Vcloud Service type not defined." 65 | "To define service type for tests, add one of command line key" 66 | " to nosetest command: --tc=ondemand: --tc=subscription:") 67 | if len(config) != 1: 68 | raise RuntimeError("Config must contain 1 element") 69 | self.service_type = config.keys()[0] 70 | service_config = test_configs.get(self.service_type) 71 | if not service_config: 72 | raise RuntimeError( 73 | "Unknown service_type: {0}. Parameter must one of {1}". 74 | format(self.service_type, (SUBSCRIPTION, ONDEMAND))) 75 | self.vcloud_config = service_config[0] 76 | self.test_config = service_config[1] 77 | 78 | if not self.vcloud_config: 79 | raise RuntimeError("vcloud_config empty") 80 | if not self.test_config: 81 | raise RuntimeError("test_config empty") 82 | 83 | def setUp(self): 84 | print "\nUsed config: {0}".format(self.service_type) 85 | fake_ctx = cfy_mocks.MockCloudifyContext( 86 | node_id='test', 87 | node_name='test', 88 | properties={}) 89 | with mock.patch('vcloud_plugin_common.ctx', fake_ctx): 90 | self.vca_client = VcloudAirClient().get(config=self.vcloud_config) 91 | 92 | def _run_with_retry(self, func, ctx): 93 | 94 | while True: 95 | try: 96 | return func(ctx=ctx) 97 | except OperationRetry as e: 98 | ctx.operation._operation_retry = None 99 | ctx.logger.info(format(str(e))) 100 | time.sleep(e.retry_after) 101 | -------------------------------------------------------------------------------- /tests/integration/run_all_tests.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # * See the License for the specific language governing permissions and 13 | # * limitations under the License. 14 | 15 | import nose 16 | import os 17 | from tests.integration import SUBSCRIPTION, ONDEMAND 18 | 19 | testfiles = [file for file in os.listdir('.') 20 | if file.startswith("test") and file.endswith(".py")] 21 | try: 22 | for service in (SUBSCRIPTION, ONDEMAND): 23 | for test in testfiles: 24 | result = nose.run( 25 | argv=['-x', '-v', '-s', '--tc={0}:'.format(service), test]) 26 | if not result: 27 | raise RuntimeError("Test failed") 28 | except RuntimeError as e: 29 | print e 30 | -------------------------------------------------------------------------------- /tests/integration/test_combined.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # * See the License for the specific language governing permissions and 13 | # * limitations under the License. 14 | 15 | import contextlib 16 | import ipaddress 17 | import mock 18 | import random 19 | import string 20 | import time 21 | import unittest 22 | 23 | from cloudify import mocks as cfy_mocks 24 | 25 | from network_plugin import floatingip, network 26 | from server_plugin import server 27 | 28 | from tests.integration import TestCase 29 | 30 | RANDOM_PREFIX_LENGTH = 5 31 | 32 | 33 | class CombinedTestCase(TestCase): 34 | 35 | def setUp(self): 36 | super(CombinedTestCase, self).setUp() 37 | chars = string.ascii_uppercase + string.digits 38 | self.name_prefix = ('plugin_test_{0}_' 39 | .format(''.join( 40 | random.choice(chars) 41 | for _ in range(RANDOM_PREFIX_LENGTH)))) 42 | 43 | def _setup_network(self): 44 | network_use_existing = \ 45 | self.test_config['combined']['network_use_existing'] 46 | existing_network = self.test_config['combined']['network_name'] 47 | self.network_name = (existing_network if network_use_existing 48 | else self.name_prefix + "network") 49 | self.network_ctx = cfy_mocks.MockCloudifyContext( 50 | node_id=self.network_name, 51 | node_name=self.network_name, 52 | properties={ 53 | "network": self.test_config['network'], 54 | "use_external_resource": network_use_existing, 55 | "resource_id": self.network_name, 56 | "vcloud_config": self.vcloud_config}) 57 | 58 | def _setup_server(self, ip_allocation_mode): 59 | self.server_name = self.name_prefix + 'server' 60 | port_node_context = cfy_mocks.MockNodeContext( 61 | properties={ 62 | 'port': 63 | { 64 | 'network': self.network_name, 65 | 'ip_allocation_mode': ip_allocation_mode, 66 | 'primary_interface': True 67 | } 68 | } 69 | ) 70 | port_relationship = mock.Mock() 71 | port_relationship.target = mock.Mock() 72 | port_relationship.target.node = port_node_context 73 | self.server_ctx = cfy_mocks.MockCloudifyContext( 74 | node_id=self.server_name, 75 | node_name=self.server_name, 76 | properties={ 77 | 'server': self.test_config['server'], 78 | 'management_network': self.network_name, 79 | "vcloud_config": self.vcloud_config 80 | } 81 | ) 82 | self.server_ctx.instance.relationships = [port_relationship] 83 | 84 | def _setup_floating_ip(self): 85 | self.fip_ctx = cfy_mocks.MockCloudifyContext( 86 | node_id='test', 87 | node_name='test', 88 | properties={}, 89 | target=cfy_mocks.MockCloudifyContext( 90 | node_id="target", 91 | properties={'floatingip': self.test_config['floatingip']}), 92 | source=cfy_mocks.MockCloudifyContext( 93 | node_id="source", 94 | properties={'vcloud_config': self.vcloud_config}, 95 | runtime_properties={server.VCLOUD_VAPP_NAME: self.server_name} 96 | ) 97 | ) 98 | 99 | def test_new_server_network_ip_allocation_dhcp(self): 100 | self._setup_network() 101 | 102 | self._setup_server(ip_allocation_mode='dhcp') 103 | 104 | self.addCleanup(self._delete_network) 105 | self._create_network() 106 | 107 | self.addCleanup(self._delete_server) 108 | self._create_server() 109 | self._wait_for_server_configured() 110 | 111 | if self.test_config['combined']['network_use_existing'] is False: 112 | gw_ip = self.network_ctx.node.properties['network']['gateway_ip'] 113 | netmask = self.network_ctx.node.properties['network']['netmask'] 114 | gw_interface = ipaddress.IPv4Interface( 115 | gw_ip + '/' + netmask) 116 | vdc = self.vca_client.get_vdc(self.vcloud_config['org']) 117 | vapp = self.vca_client.get_vapp( 118 | vdc, 119 | self.server_ctx.instance.runtime_properties[ 120 | server.VCLOUD_VAPP_NAME] 121 | ) 122 | nw_connection = server._get_vm_network_connection( 123 | vapp, self.network_name) 124 | self.assertTrue(ipaddress.IPv4Address(unicode(nw_connection['ip'])) 125 | in gw_interface.network, 126 | "vm ip: {0}, expected network: {1}" 127 | .format(nw_connection['ip'], 128 | gw_interface.network)) 129 | 130 | def test_new_server_network_ip_allocation_pool(self): 131 | self._setup_network() 132 | 133 | self._setup_server(ip_allocation_mode='pool') 134 | 135 | self.addCleanup(self._delete_network) 136 | self._create_network() 137 | 138 | self.addCleanup(self._delete_server) 139 | self._create_server() 140 | self._wait_for_server_configured() 141 | 142 | if self.test_config['combined']['network_use_existing'] is False: 143 | gw_ip = self.network_ctx.node.properties['network']['gateway_ip'] 144 | netmask = self.network_ctx.node.properties['network']['netmask'] 145 | gw_interface = ipaddress.IPv4Interface( 146 | gw_ip + '/' + netmask) 147 | vdc = self.vca_client.get_vdc(self.vcloud_config['org']) 148 | vapp = self.vca_client.get_vapp( 149 | vdc, 150 | self.server_ctx.instance.runtime_properties[ 151 | server.VCLOUD_VAPP_NAME] 152 | ) 153 | nw_connection = server._get_vm_network_connection( 154 | vapp, self.network_name) 155 | self.assertTrue(ipaddress.IPv4Address(unicode(nw_connection['ip'])) 156 | in gw_interface.network, 157 | "vm ip: {0}, expected network: {1}" 158 | .format(nw_connection['ip'], 159 | gw_interface.network)) 160 | 161 | def _create_network(self): 162 | with contextlib.nested( 163 | mock.patch('network_plugin.network.ctx', self.network_ctx), 164 | mock.patch('vcloud_plugin_common.ctx', self.network_ctx)): 165 | network.create() 166 | 167 | def _delete_network(self): 168 | with contextlib.nested( 169 | mock.patch('network_plugin.network.ctx', self.network_ctx), 170 | mock.patch('vcloud_plugin_common.ctx', self.network_ctx)): 171 | network.delete() 172 | 173 | def _create_server(self): 174 | with contextlib.nested( 175 | mock.patch('server_plugin.server.ctx', self.server_ctx), 176 | mock.patch('vcloud_plugin_common.ctx', self.server_ctx)): 177 | server.create() 178 | self._run_with_retry(server.start, self.server_ctx) 179 | 180 | def _delete_server(self): 181 | with contextlib.nested( 182 | mock.patch('server_plugin.server.ctx', self.server_ctx), 183 | mock.patch('vcloud_plugin_common.ctx', self.server_ctx)): 184 | server.stop() 185 | server.delete() 186 | 187 | def _wait_for_server_configured(self): 188 | with contextlib.nested( 189 | mock.patch('server_plugin.server.ctx', self.server_ctx), 190 | mock.patch('vcloud_plugin_common.ctx', self.server_ctx)): 191 | num_tries = 10 192 | verified = False 193 | for _ in range(num_tries): 194 | result = server._get_state(self.vca_client) 195 | if result is True: 196 | verified = True 197 | break 198 | time.sleep(10) 199 | self.assertTrue(verified, 200 | "Server configuration wasn't verified") 201 | 202 | def _connect_floating_ip(self): 203 | with contextlib.nested( 204 | mock.patch('network_plugin.floatingip.ctx', self.fip_ctx), 205 | mock.patch('vcloud_plugin_common.ctx', self.fip_ctx)): 206 | floatingip.connect_floatingip() 207 | 208 | def _disconnect_floating_ip(self): 209 | with contextlib.nested( 210 | mock.patch('network_plugin.floatingip.ctx', self.fip_ctx), 211 | mock.patch('vcloud_plugin_common.ctx', self.fip_ctx)): 212 | floatingip.disconnect_floatingip() 213 | 214 | 215 | if __name__ == '__main__': 216 | unittest.main() 217 | -------------------------------------------------------------------------------- /tests/integration/test_network_plugin.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # * See the License for the specific language governing permissions and 13 | # * limitations under the License. 14 | 15 | import os 16 | import mock 17 | from cloudify.mocks import MockCloudifyContext 18 | from network_plugin import (floatingip, network, security_group, public_nat, 19 | keypair, port) 20 | from server_plugin.server import VCLOUD_VAPP_NAME 21 | from network_plugin.network import VCLOUD_NETWORK_NAME 22 | from network_plugin import CheckAssignedExternalIp 23 | from cloudify import exceptions as cfy_exc 24 | from tests.integration import TestCase 25 | 26 | # for skipping test add this before test function: 27 | # @unittest.skip("skip test") 28 | 29 | 30 | class ValidationOperationsTestCase(TestCase): 31 | def setUp(self): 32 | name = "testnode" 33 | self.ctx = MockCloudifyContext( 34 | node_id=name, 35 | node_name=name, 36 | properties={'vcloud_config': self.vcloud_config}) 37 | ctx_patch = mock.patch('vcloud_plugin_common.ctx', self.ctx) 38 | ctx_patch.start() 39 | self.addCleanup(ctx_patch.stop) 40 | super(self.__class__, self).setUp() 41 | 42 | def test_validation(self): 43 | self.ctx.node.properties.update( 44 | {'floatingip': self.test_config['floatingip']}) 45 | with mock.patch('network_plugin.floatingip.ctx', self.ctx): 46 | floatingip.creation_validation() 47 | 48 | self.ctx.node.properties.update( 49 | {'floatingip': self.test_config['floatingip_auto']}) 50 | with mock.patch('network_plugin.floatingip.ctx', self.ctx): 51 | floatingip.creation_validation() 52 | 53 | self.ctx.node.properties.update( 54 | {'private_key_path': os.path.realpath(__file__)}) 55 | with mock.patch('network_plugin.keypair.ctx', self.ctx): 56 | keypair.creation_validation() 57 | 58 | self.ctx.node.properties.update( 59 | {"resource_id": self.test_config['network']['name'], 60 | "network": self.test_config['network'], 61 | "use_external_resource": False}) 62 | with mock.patch('network_plugin.network.ctx', self.ctx): 63 | network.creation_validation() 64 | 65 | self.ctx.node.properties.update( 66 | {'port': { 67 | 'network': self.test_config['management_network'], 68 | 'ip_allocation_mode': 'dhcp', 69 | 'primary_interface': True}}) 70 | with mock.patch('network_plugin.port.ctx', self.ctx): 71 | port.creation_validation() 72 | 73 | self.ctx.node.properties.update( 74 | {"nat": self.test_config['public_nat']['nat'], 75 | "rules": self.test_config['public_nat']['rules_net']}) 76 | with mock.patch('network_plugin.public_nat.ctx', self.ctx): 77 | public_nat.creation_validation() 78 | 79 | self.ctx.node.properties.update(self.test_config['security_group']) 80 | with mock.patch('network_plugin.security_group.ctx', self.ctx): 81 | security_group.creation_validation() 82 | 83 | 84 | class FloatingIPOperationsTestCase(TestCase): 85 | def setUp(self): 86 | name = "testnode" 87 | self.properties = { 88 | 'vcloud_config': self.vcloud_config, 89 | 'floatingip': self.test_config['floatingip'] 90 | } 91 | self.ctx = MockCloudifyContext( 92 | node_id=name, 93 | node_name=name, 94 | properties={}, 95 | target=MockCloudifyContext(node_id="target", 96 | properties=self.properties), 97 | source=MockCloudifyContext( 98 | node_id="source", 99 | properties={'vcloud_config': self.vcloud_config}, 100 | runtime_properties={ 101 | VCLOUD_VAPP_NAME: self.test_config['test_vm']} 102 | ) 103 | ) 104 | ctx_patch1 = mock.patch('network_plugin.floatingip.ctx', self.ctx) 105 | ctx_patch2 = mock.patch('vcloud_plugin_common.ctx', self.ctx) 106 | ctx_patch1.start() 107 | ctx_patch2.start() 108 | self.addCleanup(ctx_patch1.stop) 109 | self.addCleanup(ctx_patch2.stop) 110 | super(self.__class__, self).setUp() 111 | 112 | def tearDown(self): 113 | super(self.__class__, self).tearDown() 114 | 115 | def test_floating_ip_create_delete_with_explicit_ip(self): 116 | self.ctx.target.node.properties['floatingip'].update( 117 | self.test_config['floatingip']) 118 | public_ip = self.ctx.target.node.properties['floatingip']['public_ip'] 119 | CheckAssignedExternalIp(public_ip, self._get_gateway()) 120 | floatingip.connect_floatingip() 121 | floatingip.disconnect_floatingip() 122 | CheckAssignedExternalIp(public_ip, self._get_gateway()) 123 | 124 | def test_floating_ip_create_delete_with_autoget_ip(self): 125 | self.ctx.target.node.properties['floatingip'].update( 126 | self.test_config['floatingip']) 127 | del self.ctx.target.node.properties['floatingip']['public_ip'] 128 | floatingip.connect_floatingip() 129 | public_ip = self.ctx.target.instance.runtime_properties['public_ip'] 130 | self.assertRaises(cfy_exc.NonRecoverableError, 131 | CheckAssignedExternalIp, 132 | public_ip, 133 | self._get_gateway()) 134 | self.assertTrue(public_ip) 135 | floatingip.disconnect_floatingip() 136 | CheckAssignedExternalIp(public_ip, self._get_gateway()) 137 | 138 | def _get_gateway(self): 139 | return self.vca_client.get_gateway( 140 | self.vcloud_config["org"], 141 | self.ctx.target.node.properties['floatingip']['edge_gateway']) 142 | 143 | 144 | class OrgNetworkOperationsTestCase(TestCase): 145 | def setUp(self): 146 | 147 | self.net_name = self.test_config['network']['name'] 148 | self.existing_net_name = self.test_config['test_network_name'] 149 | self.ctx = MockCloudifyContext( 150 | node_id=self.net_name, 151 | node_name=self.net_name, 152 | properties={"resource_id": self.existing_net_name, 153 | "network": self.test_config['network'], 154 | "vcloud_config": self.vcloud_config, 155 | "use_external_resource": False}) 156 | self.org_name = self.vcloud_config["org"] 157 | ctx_patch1 = mock.patch('network_plugin.network.ctx', self.ctx) 158 | ctx_patch2 = mock.patch('vcloud_plugin_common.ctx', self.ctx) 159 | ctx_patch1.start() 160 | ctx_patch2.start() 161 | self.addCleanup(ctx_patch1.stop) 162 | self.addCleanup(ctx_patch2.stop) 163 | super(self.__class__, self).setUp() 164 | 165 | def get_pools(self): 166 | gateway = self.vca_client.get_gateways(self.org_name)[0] 167 | if not gateway: 168 | raise cfy_exc.NonRecoverableError("Gateway not found") 169 | gatewayConfiguration = gateway.me.get_Configuration() 170 | edgeGatewayServiceConfiguration = \ 171 | gatewayConfiguration.get_EdgeGatewayServiceConfiguration() 172 | dhcpService = filter( 173 | lambda service: (service.__class__.__name__ 174 | == "GatewayDhcpServiceType"), 175 | edgeGatewayServiceConfiguration.get_NetworkService())[0] 176 | return dhcpService.get_Pool() 177 | 178 | def tearDown(self): 179 | super(self.__class__, self).tearDown() 180 | 181 | def test_orgnetwork_create_delete(self): 182 | self.assertNotIn(self.net_name, 183 | network._get_network_list(self.vca_client, 184 | self.org_name)) 185 | start_pools = len(self.get_pools()) 186 | network.create() 187 | self.assertIn(self.net_name, 188 | network._get_network_list(self.vca_client, 189 | self.org_name)) 190 | self.assertEqual(start_pools + 1, len(self.get_pools())) 191 | network.delete() 192 | self.assertNotIn(self.net_name, 193 | network._get_network_list(self.vca_client, 194 | self.org_name)) 195 | self.assertEqual(start_pools, len(self.get_pools())) 196 | 197 | 198 | class SecurityGroupOperationsTestCase(TestCase): 199 | def setUp(self): 200 | name = "testnode" 201 | self.ctx = MockCloudifyContext( 202 | node_id=name, 203 | node_name=name, 204 | properties={}, 205 | target=MockCloudifyContext( 206 | node_id="target", 207 | properties=self.test_config['security_group']), 208 | source=MockCloudifyContext( 209 | node_id="source", 210 | properties={'vcloud_config': self.vcloud_config}, 211 | runtime_properties={ 212 | VCLOUD_VAPP_NAME: self.test_config['test_vm']} 213 | ) 214 | ) 215 | ctx_patch1 = mock.patch('network_plugin.security_group.ctx', self.ctx) 216 | ctx_patch2 = mock.patch('vcloud_plugin_common.ctx', self.ctx) 217 | ctx_patch1.start() 218 | ctx_patch2.start() 219 | self.addCleanup(ctx_patch1.stop) 220 | self.addCleanup(ctx_patch2.stop) 221 | self.org_name = self.vcloud_config["org"] 222 | super(self.__class__, self).setUp() 223 | 224 | def tearDown(self): 225 | super(self.__class__, self).tearDown() 226 | 227 | def test_firewall_rules_create_delete(self): 228 | rules = len(self.get_rules()) 229 | security_group.create() 230 | self.assertEqual(rules + 2, len(self.get_rules())) 231 | security_group.delete() 232 | self.assertEqual(rules, len(self.get_rules())) 233 | 234 | def get_rules(self): 235 | gateway = self.vca_client.get_gateways(self.org_name)[0] 236 | if not gateway: 237 | raise cfy_exc.NonRecoverableError("Gateway not found") 238 | gatewayConfiguration = gateway.me.get_Configuration() 239 | edgeGatewayServiceConfiguration = \ 240 | gatewayConfiguration.get_EdgeGatewayServiceConfiguration() 241 | firewallService = filter( 242 | lambda service: (service.__class__.__name__ 243 | == "FirewallServiceType"), 244 | edgeGatewayServiceConfiguration.get_NetworkService())[0] 245 | return firewallService.get_FirewallRule() 246 | 247 | 248 | class PublicNatOperationsTestCase(TestCase): 249 | def setUp(self): 250 | name = "testnode" 251 | self.ctx = MockCloudifyContext( 252 | node_id=name, 253 | node_name=name, 254 | properties={}, 255 | target=MockCloudifyContext( 256 | node_id="target", 257 | properties={ 258 | "nat": self.test_config['public_nat']['nat'], 259 | 'use_external_resource': False, 260 | "rules": {}}), 261 | source=MockCloudifyContext( 262 | node_id="source", 263 | properties={"vcloud_config": self.vcloud_config}, 264 | runtime_properties={ 265 | VCLOUD_VAPP_NAME: 266 | self.test_config['public_nat']['test_vm'], 267 | VCLOUD_NETWORK_NAME: 268 | self.test_config['public_nat']['network_name']} 269 | ) 270 | ) 271 | ctx_patch1 = mock.patch('network_plugin.public_nat.ctx', self.ctx) 272 | ctx_patch2 = mock.patch('vcloud_plugin_common.ctx', self.ctx) 273 | ctx_patch1.start() 274 | ctx_patch2.start() 275 | self.addCleanup(ctx_patch1.stop) 276 | self.addCleanup(ctx_patch2.stop) 277 | super(self.__class__, self).setUp() 278 | 279 | def tearDown(self): 280 | super(self.__class__, self).tearDown() 281 | 282 | def test_public_network_connected_to_nat(self): 283 | self.ctx.target.node.properties['rules'] = \ 284 | self.test_config['public_nat']['rules_net'] 285 | self.ctx.source.node.properties['resource_id'] = \ 286 | self.test_config['public_nat']['network_name'] 287 | rules_count = self.get_rules_count() 288 | public_nat.net_connect_to_nat() 289 | self.assertEqual(rules_count + 1, self.get_rules_count()) 290 | public_nat.net_disconnect_from_nat() 291 | self.assertEqual(rules_count, self.get_rules_count()) 292 | 293 | def test_public_server_connected_to_nat(self): 294 | self.ctx.target.node.properties['rules'] = \ 295 | self.test_config['public_nat']['rules_port'] 296 | rules_count = self.get_rules_count() 297 | public_nat.server_connect_to_nat() 298 | self.assertEqual(rules_count + 3, self.get_rules_count()) 299 | public_nat.server_disconnect_from_nat() 300 | self.assertEqual(rules_count, self.get_rules_count()) 301 | 302 | def get_rules_count(self): 303 | return len(self._get_gateway().get_nat_rules()) 304 | 305 | def _get_gateway(self): 306 | return self.vca_client.get_gateway( 307 | self.vcloud_config["org"], 308 | self.ctx.target.node.properties['nat']['edge_gateway']) 309 | -------------------------------------------------------------------------------- /tests/integration/test_server_plugin.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # * See the License for the specific language governing permissions and 13 | # * limitations under the License. 14 | 15 | import mock 16 | import random 17 | import socket 18 | import string 19 | import time 20 | 21 | from cloudify import exceptions as cfy_exc 22 | from cloudify import mocks as cfy_mocks 23 | 24 | from server_plugin import server 25 | from server_plugin import volume 26 | from tests.integration import TestCase 27 | from cloudify.mocks import MockCloudifyContext 28 | from server_plugin.server import VCLOUD_VAPP_NAME 29 | 30 | RANDOM_PREFIX_LENGTH = 5 31 | 32 | 33 | class ServerNoNetworkTestCase(TestCase): 34 | def setUp(self): 35 | super(ServerNoNetworkTestCase, self).setUp() 36 | chars = string.ascii_uppercase + string.digits 37 | self.name_prefix = ('plugin_test_{0}_' 38 | .format(''.join( 39 | random.choice(chars) 40 | for _ in range(RANDOM_PREFIX_LENGTH))) 41 | ) 42 | server_test_dict = self.test_config['server'] 43 | name = self.name_prefix + 'server' 44 | 45 | self.ctx = cfy_mocks.MockCloudifyContext( 46 | node_id=name, 47 | node_name=name, 48 | properties={ 49 | 'server': 50 | { 51 | 'name': name, 52 | 'catalog': server_test_dict['catalog'], 53 | 'template': server_test_dict['template'], 54 | 'hardware': server_test_dict['hardware'], 55 | 'guest_customization': 56 | server_test_dict.get('guest_customization') 57 | }, 58 | 'management_network': self.test_config['management_network'], 59 | 'vcloud_config': self.vcloud_config 60 | } 61 | ) 62 | self.ctx.node.properties['server']['guest_customization'][ 63 | 'public_keys'] = [self.test_config['manager_keypair'], 64 | self.test_config['agent_keypair']] 65 | self.ctx.instance.relationships = [] 66 | ctx_patch1 = mock.patch('server_plugin.server.ctx', self.ctx) 67 | ctx_patch2 = mock.patch('vcloud_plugin_common.ctx', self.ctx) 68 | ctx_patch1.start() 69 | ctx_patch2.start() 70 | self.addCleanup(ctx_patch1.stop) 71 | self.addCleanup(ctx_patch2.stop) 72 | 73 | def tearDown(self): 74 | try: 75 | server.stop() 76 | except Exception: 77 | pass 78 | try: 79 | server.delete() 80 | except Exception: 81 | pass 82 | super(ServerNoNetworkTestCase, self).tearDown() 83 | 84 | def test_server_creation_validation(self): 85 | success = True 86 | msg = None 87 | try: 88 | server.creation_validation() 89 | except cfy_exc.NonRecoverableError as e: 90 | success = False 91 | msg = e.message 92 | self.assertTrue(success, msg) 93 | 94 | def test_server_creation_validation_catalog_not_found(self): 95 | self.ctx.node.properties['server']['catalog'] = 'fake-catalog' 96 | self.assertRaises(cfy_exc.NonRecoverableError, 97 | server.creation_validation) 98 | 99 | def test_server_creation_validation_template_not_found(self): 100 | self.ctx.node.properties['server']['template'] = 'fake-template' 101 | self.assertRaises(cfy_exc.NonRecoverableError, 102 | server.creation_validation) 103 | 104 | def test_server_creation_validation_parameter_missing(self): 105 | del self.ctx.node.properties['server']['template'] 106 | self.assertRaises(cfy_exc.NonRecoverableError, 107 | server.creation_validation) 108 | 109 | def test_server_create_delete(self): 110 | server.create() 111 | vdc = self.vca_client.get_vdc(self.vcloud_config['org']) 112 | vapp = self.vca_client.get_vapp( 113 | vdc, 114 | self.ctx.node.properties['server']['name']) 115 | self.assertFalse(vapp is None) 116 | self.assertFalse(server._vapp_is_on(vapp)) 117 | self.check_hardware(vapp) 118 | server.delete() 119 | vapp = self.vca_client.get_vapp( 120 | vdc, 121 | self.ctx.node.properties['server']['name']) 122 | self.assertTrue(vapp is None) 123 | 124 | def test_server_stop_start(self): 125 | server.create() 126 | vdc = self.vca_client.get_vdc(self.vcloud_config['org']) 127 | vapp = self.vca_client.get_vapp( 128 | vdc, 129 | self.ctx.node.properties['server']['name']) 130 | self.assertFalse(vapp is None) 131 | self.assertFalse(server._vapp_is_on(vapp)) 132 | 133 | self._run_with_retry(server.start, self.ctx) 134 | vapp = self.vca_client.get_vapp( 135 | vdc, 136 | self.ctx.node.properties['server']['name']) 137 | self.assertTrue(server._vapp_is_on(vapp)) 138 | 139 | server.stop() 140 | vapp = self.vca_client.get_vapp( 141 | vdc, 142 | self.ctx.node.properties['server']['name']) 143 | self.assertFalse(server._vapp_is_on(vapp)) 144 | 145 | self._run_with_retry(server.start, self.ctx) 146 | vapp = self.vca_client.get_vapp( 147 | vdc, 148 | self.ctx.node.properties['server']['name']) 149 | self.assertTrue(server._vapp_is_on(vapp)) 150 | 151 | def check_hardware(self, vapp): 152 | data = vapp.get_vms_details()[0] 153 | hardware = self.test_config['server']['hardware'] 154 | if hardware: 155 | self.assertEqual(data['cpus'], hardware['cpu']) 156 | self.assertEqual(data['memory'] * 1024, hardware['memory']) 157 | 158 | 159 | class ServerWithNetworkTestCase(TestCase): 160 | def setUp(self): 161 | super(ServerWithNetworkTestCase, self).setUp() 162 | chars = string.ascii_uppercase + string.digits 163 | self.name_prefix = ('plugin_test_{0}_' 164 | .format(''.join( 165 | random.choice(chars) 166 | for _ in range(RANDOM_PREFIX_LENGTH))) 167 | ) 168 | 169 | server_test_dict = self.test_config['server'] 170 | name = self.name_prefix + 'server' 171 | self.network_name = self.test_config['management_network'] 172 | 173 | port_node_context = cfy_mocks.MockNodeContext( 174 | properties={ 175 | 'port': 176 | { 177 | 'network': self.network_name, 178 | 'ip_allocation_mode': 'pool', 179 | 'primary_interface': True 180 | } 181 | } 182 | ) 183 | 184 | network_node_context = cfy_mocks.MockNodeContext( 185 | properties={ 186 | 'network': 187 | { 188 | 'name': self.network_name 189 | } 190 | } 191 | ) 192 | 193 | self.port_relationship = mock.Mock() 194 | self.port_relationship.target = mock.Mock() 195 | self.port_relationship.target.node = port_node_context 196 | 197 | self.network_relationship = mock.Mock() 198 | self.network_relationship.target = mock.Mock() 199 | self.network_relationship.target.node = network_node_context 200 | self.properties = { 201 | 'server': 202 | { 203 | 'name': name, 204 | 'catalog': server_test_dict['catalog'], 205 | 'template': server_test_dict['template'] 206 | }, 207 | 'management_network': self.network_name, 208 | 'vcloud_config': self.vcloud_config 209 | } 210 | self.ctx = cfy_mocks.MockCloudifyContext( 211 | node_id=name, 212 | node_name=name, 213 | properties=self.properties 214 | ) 215 | self.ctx.instance.relationships = [] 216 | ctx_patch1 = mock.patch('server_plugin.server.ctx', self.ctx) 217 | ctx_patch2 = mock.patch('vcloud_plugin_common.ctx', self.ctx) 218 | ctx_patch1.start() 219 | ctx_patch2.start() 220 | self.addCleanup(ctx_patch1.stop) 221 | self.addCleanup(ctx_patch2.stop) 222 | 223 | def tearDown(self): 224 | try: 225 | server.stop() 226 | except Exception: 227 | pass 228 | try: 229 | server.delete() 230 | except Exception: 231 | pass 232 | super(ServerWithNetworkTestCase, self).tearDown() 233 | 234 | def test_create_with_port_connection(self): 235 | self.ctx.instance.relationships = [self.port_relationship] 236 | self._create_test() 237 | 238 | def test_create_with_network_connection(self): 239 | self.ctx.instance.relationships = [self.network_relationship] 240 | self._create_test() 241 | 242 | def test_create_without_connections(self): 243 | self.ctx.instance.relationships = [] 244 | self._create_test() 245 | 246 | def _create_test(self): 247 | server.create() 248 | self._run_with_retry(server.start, self.ctx) 249 | vdc = self.vca_client.get_vdc(self.vcloud_config['org']) 250 | vapp = self.vca_client.get_vapp( 251 | vdc, 252 | self.ctx.node.properties['server']['name']) 253 | self.assertFalse(vapp is None) 254 | networks = server._get_vm_network_connections(vapp) 255 | self.assertEqual(1, len(networks)) 256 | self.assertEqual(self.network_name, networks[0]['network_name']) 257 | 258 | def test_get_state(self): 259 | num_tries = 5 260 | verified = False 261 | server.create() 262 | self._run_with_retry(server.start, self.ctx) 263 | for _ in range(num_tries): 264 | result = server._get_state(self.vca_client) 265 | if result is True: 266 | self.assertTrue('ip' in self.ctx.instance.runtime_properties) 267 | self.assertTrue('networks' 268 | in self.ctx.instance.runtime_properties) 269 | self.assertEqual(1, 270 | len(self.ctx.instance. 271 | runtime_properties['networks'].keys())) 272 | self.assertEqual(self.network_name, 273 | self.ctx.instance. 274 | runtime_properties['networks'].keys()[0]) 275 | ip_valid = True 276 | try: 277 | socket.inet_aton( 278 | self.ctx.instance.runtime_properties['ip']) 279 | except socket.error: 280 | ip_valid = False 281 | self.assertTrue(ip_valid) 282 | verified = True 283 | break 284 | time.sleep(2) 285 | self.assertTrue(verified) 286 | 287 | 288 | class VolumeTestCase(TestCase): 289 | def setUp(self): 290 | super(VolumeTestCase, self).setUp() 291 | self.volume_test_dict = self.test_config['volume'] 292 | name = 'volume' 293 | self.properties = { 294 | 'volume': 295 | { 296 | 'name': self.volume_test_dict['name'], 297 | 'size': self.volume_test_dict['size'] 298 | }, 299 | 'use_external_resource': True, 300 | 'resource_id': self.volume_test_dict['name_exists'], 301 | 'vcloud_config': self.vcloud_config 302 | } 303 | self.target = MockCloudifyContext( 304 | node_id="target", 305 | properties={'vcloud_config': self.vcloud_config}, 306 | runtime_properties={ 307 | VCLOUD_VAPP_NAME: self.test_config['test_vm'] 308 | } 309 | ) 310 | self.source = MockCloudifyContext( 311 | node_id="source", properties=self.properties 312 | ) 313 | self.nodectx = cfy_mocks.MockCloudifyContext( 314 | node_id=name, 315 | node_name=name, 316 | properties=self.properties 317 | ) 318 | self.relationctx = cfy_mocks.MockCloudifyContext( 319 | node_id=name, 320 | node_name=name, 321 | target=self.target, 322 | source=self.source 323 | ) 324 | self.ctx = self.nodectx 325 | ctx_patch1 = mock.patch('server_plugin.volume.ctx', self.nodectx) 326 | ctx_patch2 = mock.patch('vcloud_plugin_common.ctx', self.nodectx) 327 | ctx_patch1.start() 328 | ctx_patch2.start() 329 | self.addCleanup(ctx_patch1.stop) 330 | self.addCleanup(ctx_patch2.stop) 331 | 332 | def test_volume(self): 333 | disks_count = lambda: len( 334 | self.vca_client.get_disks(self.vcloud_config['vdc'])) 335 | volume.creation_validation() 336 | disks_before = disks_count() 337 | volume.create_volume() 338 | if self.relationctx.source.node.properties['use_external_resource']: 339 | self.assertEqual(disks_before, disks_count()) 340 | else: 341 | self.assertEqual(disks_before + 1, disks_count()) 342 | self._attach_detach() 343 | volume.delete_volume() 344 | self.assertEqual(disks_before, disks_count()) 345 | 346 | def _attach_detach(self): 347 | def links_count(): 348 | node_properties = self.relationctx.source.node.properties 349 | if node_properties['use_external_resource']: 350 | return [ 351 | len(d[1]) for d in self.vca_client.get_disks( 352 | self.vcloud_config['vdc'] 353 | ) if d[0].name == node_properties['resource_id'] 354 | ][0] 355 | else: 356 | return [ 357 | len(d[1]) for d in self.vca_client.get_disks( 358 | self.vcloud_config['vdc'] 359 | ) if d[0].name == node_properties['volume']['name'] 360 | ][0] 361 | with mock.patch('server_plugin.volume.ctx', self.relationctx): 362 | links_before = links_count() 363 | volume.attach_volume() 364 | self.assertEqual(links_before + 1, links_count()) 365 | volume.detach_volume() 366 | self.assertEqual(links_before, links_count()) 367 | -------------------------------------------------------------------------------- /tests/syntax_check.py: -------------------------------------------------------------------------------- 1 | # Simple syntax check for blueprints and json examples 2 | import yaml 3 | 4 | yaml_files = ['../examples/blueprint.yaml', 5 | '../manager_blueprint/vcloud-manager-blueprint.yaml', 6 | '../plugin.yaml'] 7 | 8 | 9 | for filename in yaml_files: 10 | try: 11 | yaml.load(open(filename)) 12 | except yaml.scanner.ScannerError as e: 13 | print e 14 | except IOError as e: 15 | print e 16 | -------------------------------------------------------------------------------- /tests/unittests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmware-archive/tosca-vcloud-plugin/c2588eec2d4404190018ab0101d3733afd87cfe0/tests/unittests/__init__.py -------------------------------------------------------------------------------- /tests/unittests/test_mock_base.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # * See the License for the specific language governing permissions and 13 | # * limitations under the License. 14 | 15 | import mock 16 | import unittest 17 | from cloudify import mocks as cfy_mocks 18 | from network_plugin import BUSY_MESSAGE, NAT_ROUTED 19 | 20 | 21 | class TestBase(unittest.TestCase): 22 | 23 | ERROR_PLACE = "ERROR_MESSAGE_PLACE_HOLDER" 24 | 25 | def generate_task(self, status): 26 | task = mock.Mock() 27 | error = mock.Mock() 28 | error.get_message = mock.MagicMock(return_value="Some Error") 29 | task.get_Error = mock.MagicMock(return_value=error) 30 | task.get_status = mock.MagicMock(return_value=status) 31 | return task 32 | 33 | def set_services_conf_result(self, gateway, result): 34 | """ 35 | set result for save configuration 36 | """ 37 | task = None 38 | if result: 39 | task = self.generate_task(result) 40 | gateway.save_services_configuration = mock.MagicMock( 41 | return_value=task 42 | ) 43 | 44 | def set_gateway_busy(self, gateway): 45 | message = gateway.response.content 46 | message = message.replace( 47 | self.ERROR_PLACE, BUSY_MESSAGE 48 | ) 49 | gateway.response.content = message 50 | 51 | def prepare_retry(self, ctx): 52 | """ 53 | set fake retry operation 54 | """ 55 | ctx.operation.retry = mock.MagicMock( 56 | return_value=None 57 | ) 58 | 59 | def check_retry_realy_called(self, ctx): 60 | """ 61 | check that we really call retry 62 | """ 63 | ctx.operation.retry.assert_called_with( 64 | message='Waiting for gateway.', 65 | retry_after=10 66 | ) 67 | 68 | def generate_gateway( 69 | self, vdc_name="vdc", vms_networks=None, vdc_networks=None 70 | ): 71 | gate = mock.Mock() 72 | gate.get_dhcp_pools = mock.MagicMock(return_value=[ 73 | self.genarate_pool( 74 | vdc_name, '127.0.0.1', '127.0.0.255' 75 | ) 76 | ]) 77 | gate.add_dhcp_pool = mock.MagicMock(return_value=None) 78 | self.set_services_conf_result(gate, None) 79 | gate.response = mock.Mock() 80 | gate.response.content = (''' 81 | 82 | ''').replace("\n", " ").strip() 88 | # list of interfaces 89 | interfaces = [] 90 | if vms_networks: 91 | for network in vms_networks: 92 | interface = mock.Mock() 93 | interface.get_Name = mock.MagicMock( 94 | return_value=network.get( 95 | 'network_name', 'unknowNet' 96 | ) 97 | ) 98 | interfaces.append(interface) 99 | gate.get_interfaces = mock.MagicMock( 100 | return_value=interfaces 101 | ) 102 | # firewall enabled 103 | gate.is_fw_enabled = mock.MagicMock(return_value=True) 104 | # dont have any nat rules 105 | gate.get_nat_rules = mock.MagicMock(return_value=[]) 106 | # cant deallocate ip 107 | gate.deallocate_public_ip = mock.MagicMock(return_value=None) 108 | # public ips not exist 109 | gate.get_public_ips = mock.MagicMock(return_value=[]) 110 | return gate 111 | 112 | def generate_fake_client_network( 113 | self, fenceMode=None, name="some", start_ip="127.1.1.1", 114 | end_ip="127.1.1.255" 115 | ): 116 | """ 117 | generate network for vca client 118 | """ 119 | network = mock.Mock() 120 | # generate ip 121 | network._ip = mock.Mock() 122 | network._ip.get_StartAddress = mock.MagicMock(return_value=start_ip) 123 | network._ip.get_EndAddress = mock.MagicMock(return_value=end_ip) 124 | # generate ipRange 125 | network._ip_range = mock.Mock() 126 | network._ip_range.IpRanges.IpRange = [network._ip] 127 | # network get_network_configuration 128 | network._network_config = mock.Mock() 129 | network._network_config.get_FenceMode = mock.MagicMock( 130 | return_value=fenceMode 131 | ) 132 | # network scope 133 | network.Configuration.IpScopes.IpScope = [network._ip_range] 134 | # network 135 | network.get_name = mock.MagicMock(return_value=name) 136 | network.get_Configuration = mock.MagicMock( 137 | return_value=network._network_config 138 | ) 139 | return network 140 | 141 | def generate_nat_rule( 142 | self, rule_type, original_ip, original_port, translated_ip, 143 | translated_port, protocol 144 | ): 145 | rule = mock.Mock() 146 | rule.get_OriginalIp = mock.MagicMock(return_value=original_ip) 147 | rule.get_OriginalPort = mock.MagicMock(return_value=original_port) 148 | rule.get_TranslatedIp = mock.MagicMock(return_value=translated_ip) 149 | rule.get_TranslatedPort = mock.MagicMock(return_value=translated_port) 150 | rule.get_Protocol = mock.MagicMock(return_value=protocol) 151 | rule_inlist = mock.Mock() 152 | rule_inlist.get_RuleType = mock.MagicMock(return_value=rule_type) 153 | rule_inlist.get_GatewayNatRule = mock.MagicMock(return_value=rule) 154 | return rule_inlist 155 | 156 | def genarate_pool(self, name, low_ip, high_ip): 157 | pool = mock.Mock() 158 | pool.Network = mock.Mock() 159 | pool.Network.name = name 160 | pool.get_LowIpAddress = mock.MagicMock(return_value=low_ip) 161 | pool.get_HighIpAddress = mock.MagicMock(return_value=high_ip) 162 | # network in pool 163 | network = mock.Mock() 164 | network.get_href = mock.MagicMock( 165 | return_value="_href" + name 166 | ) 167 | pool.get_Network = mock.MagicMock(return_value=network) 168 | return pool 169 | 170 | def set_network_routed_in_client(self, fake_client): 171 | """ 172 | set any network as routed 173 | """ 174 | network = self.generate_fake_client_network(NAT_ROUTED) 175 | fake_client.get_network = mock.MagicMock(return_value=network) 176 | 177 | def generate_fake_client_disk(self, name="some_disk"): 178 | """ 179 | generate fake disk for fake client, 180 | have used in client.get_disks 181 | """ 182 | disk = mock.Mock() 183 | disk.name = name 184 | return disk 185 | 186 | def generate_fake_client_disk_ref(self, name): 187 | """ 188 | generate ref for disk, 189 | have used for client.get_diskRefs 190 | """ 191 | ref = mock.Mock() 192 | ref.name = name 193 | return ref 194 | 195 | def generate_fake_vms_disk(self, name="some_disk"): 196 | """ 197 | generate attached vms for disk, 198 | have used for client.get_disks 199 | """ 200 | vms = mock.Mock() 201 | vms._disk = name 202 | return vms 203 | 204 | def generate_client(self, vms_networks=None, vdc_networks=None): 205 | 206 | def _generate_fake_client_network(vdc_name, network_name): 207 | return self.generate_fake_client_network(network_name) 208 | 209 | def _get_admin_network_href(vdc_name, network_name): 210 | return "_href" + network_name 211 | 212 | def _get_gateway(vdc_name="vdc"): 213 | return self.generate_gateway( 214 | vdc_name, 215 | vms_networks, 216 | vdc_networks 217 | ) 218 | 219 | def _get_gateways(vdc_name): 220 | return [_get_gateway(vdc_name)] 221 | 222 | def _get_vdc(networks): 223 | vdc = mock.Mock() 224 | vdc.AvailableNetworks = mock.Mock() 225 | vdc.AvailableNetworks.Network = [] 226 | if networks: 227 | for net in networks: 228 | networkEntity = mock.Mock() 229 | networkEntity.name = net 230 | vdc.AvailableNetworks.Network.append(networkEntity) 231 | return vdc 232 | 233 | template = mock.Mock() 234 | template.get_name = mock.MagicMock(return_value='secret') 235 | catalogItems = mock.Mock() 236 | catalogItems.get_CatalogItem = mock.MagicMock( 237 | return_value=[template] 238 | ) 239 | catalog = mock.Mock() 240 | catalog.get_name = mock.MagicMock(return_value='public') 241 | catalog.get_CatalogItems = mock.MagicMock( 242 | return_value=catalogItems 243 | ) 244 | client = mock.Mock() 245 | client.get_catalogs = mock.MagicMock(return_value=[catalog]) 246 | client.get_network = _generate_fake_client_network 247 | client.get_networks = mock.MagicMock(return_value=[]) 248 | client.get_admin_network_href = _get_admin_network_href 249 | client._vdc_gateway = _get_gateway() 250 | client.get_gateway = mock.MagicMock( 251 | return_value=client._vdc_gateway 252 | ) 253 | client.get_gateways = _get_gateways 254 | client._vapp = self.generate_vapp(vms_networks) 255 | client.get_vapp = mock.MagicMock(return_value=client._vapp) 256 | client._app_vdc = _get_vdc(vdc_networks) 257 | client.get_vdc = mock.MagicMock(return_value=client._app_vdc) 258 | client.delete_vdc_network = mock.MagicMock( 259 | return_value=(False, None) 260 | ) 261 | client.create_vdc_network = mock.MagicMock( 262 | return_value=(False, None) 263 | ) 264 | # disks for client 265 | client.add_disk = mock.MagicMock( 266 | return_value=(False, None) 267 | ) 268 | client.get_disks = mock.MagicMock(return_value=[]) 269 | client.add_disk = mock.MagicMock( 270 | return_value=(False, None) 271 | ) 272 | client.delete_disk = mock.MagicMock( 273 | return_value=(False, None) 274 | ) 275 | client.get_diskRefs = mock.MagicMock(return_value=[]) 276 | # login authification 277 | client.login = mock.MagicMock( 278 | return_value=False 279 | ) 280 | client.logout = mock.MagicMock( 281 | return_value=False 282 | ) 283 | client.get_instances = mock.MagicMock( 284 | return_value=[] 285 | ) 286 | client.login_to_instance = mock.MagicMock( 287 | return_value=False 288 | ) 289 | client.login_to_org = mock.MagicMock( 290 | return_value=False 291 | ) 292 | return client 293 | 294 | def generate_vca(self): 295 | return mock.MagicMock(return_value=self.generate_client()) 296 | 297 | def generate_vapp(self, vms_networks=None): 298 | 299 | def _get_vms_network_info(): 300 | return [vms_networks] 301 | 302 | vapp = mock.Mock() 303 | vapp.me = mock.Mock() 304 | vapp.get_vms_network_info = _get_vms_network_info 305 | # disk for vapp 306 | vapp.attach_disk_to_vm = mock.MagicMock( 307 | return_value=None 308 | ) 309 | vapp.detach_disk_from_vm = mock.MagicMock( 310 | return_value=None 311 | ) 312 | # mememory/cpu customize 313 | vapp.modify_vm_memory = mock.MagicMock( 314 | return_value=None 315 | ) 316 | vapp.modify_vm_cpu = mock.MagicMock( 317 | return_value=None 318 | ) 319 | return vapp 320 | 321 | def generate_relation_context(self): 322 | source = mock.Mock() 323 | source.node = mock.Mock() 324 | target = mock.Mock() 325 | target.node = mock.Mock() 326 | target.instance.runtime_properties = {} 327 | fake_ctx = cfy_mocks.MockCloudifyContext( 328 | source=source, target=target 329 | ) 330 | return fake_ctx 331 | 332 | def generate_node_context( 333 | self, relation_node_properties=None, properties=None, 334 | runtime_properties=None 335 | ): 336 | 337 | class MockInstanceContext(cfy_mocks.MockNodeInstanceContext): 338 | 339 | self._relationships = None 340 | 341 | @property 342 | def relationships(self): 343 | return self._relationships 344 | 345 | if not properties: 346 | properties = { 347 | 'management_network': '_management_network', 348 | 'vcloud_config': { 349 | 'vdc': 'vdc_name' 350 | } 351 | } 352 | if not runtime_properties: 353 | runtime_properties = { 354 | 'vcloud_vapp_name': 'vapp_name' 355 | } 356 | fake_ctx = cfy_mocks.MockCloudifyContext( 357 | node_id='test', 358 | node_name='test', 359 | properties=properties, 360 | provider_context={}, 361 | runtime_properties=runtime_properties 362 | ) 363 | 364 | fake_ctx._instance = MockInstanceContext( 365 | fake_ctx.instance._id, fake_ctx.instance._runtime_properties 366 | ) 367 | 368 | relationship = self.generate_relation_context() 369 | relationship._target.node.properties = relation_node_properties 370 | fake_ctx.instance._relationships = [relationship] 371 | 372 | return fake_ctx 373 | -------------------------------------------------------------------------------- /tests/unittests/test_mock_network_plugin_keypair.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # * See the License for the specific language governing permissions and 13 | # * limitations under the License. 14 | 15 | import unittest 16 | 17 | from cloudify import exceptions as cfy_exc 18 | from tests.unittests import test_mock_base 19 | from network_plugin import keypair 20 | 21 | 22 | class NetworkPluginKeyPairpMockTestCase(test_mock_base.TestBase): 23 | 24 | def test_creation_validation(self): 25 | # exist keyfile 26 | fake_ctx = self.generate_node_context( 27 | properties={ 28 | 'private_key_path': __file__ 29 | } 30 | ) 31 | keypair.creation_validation(ctx=fake_ctx) 32 | # not exist keyfile 33 | fake_ctx = self.generate_node_context( 34 | properties={ 35 | 'private_key_path': __file__ + ".not_exist" 36 | } 37 | ) 38 | with self.assertRaises(cfy_exc.NonRecoverableError): 39 | keypair.creation_validation(ctx=fake_ctx) 40 | 41 | if __name__ == '__main__': 42 | unittest.main() 43 | -------------------------------------------------------------------------------- /tests/unittests/test_mock_network_plugin_network_subroutes.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # * See the License for the specific language governing permissions and 13 | # * limitations under the License. 14 | 15 | import mock 16 | import unittest 17 | 18 | from cloudify import exceptions as cfy_exc 19 | import test_mock_base 20 | from network_plugin import network 21 | 22 | 23 | class NetworkPluginNetworkSubroutesMockTestCase(test_mock_base.TestBase): 24 | 25 | def test__get_network_list(self): 26 | # check list with one network 27 | fake_client = self.generate_client(vdc_networks=['something']) 28 | self.assertEqual( 29 | ['something'], 30 | network._get_network_list(fake_client, 'vdc_name') 31 | ) 32 | fake_client.get_vdc.assert_called_with('vdc_name') 33 | # can't get vdc 34 | fake_client.get_vdc = mock.MagicMock(return_value=None) 35 | with self.assertRaises(cfy_exc.NonRecoverableError): 36 | network._get_network_list(fake_client, 'vdc_name') 37 | 38 | def test_split_adresses(self): 39 | range_network = network._split_adresses("10.1.1.1-10.1.1.255") 40 | self.assertEqual(range_network.start, '10.1.1.1') 41 | self.assertEqual(range_network.end, '10.1.1.255') 42 | with self.assertRaises(cfy_exc.NonRecoverableError): 43 | network._split_adresses("10.1.1.1") 44 | with self.assertRaises(cfy_exc.NonRecoverableError): 45 | network._split_adresses("10.1.1.255-10.1.1.1") 46 | with self.assertRaises(cfy_exc.NonRecoverableError): 47 | network._split_adresses("my-10") 48 | 49 | def test__dhcp_operation(self): 50 | fake_client = self.generate_client() 51 | # no dhcp 52 | fake_ctx = self.generate_node_context(properties={ 53 | 'network': { 54 | 'edge_gateway': 'gateway' 55 | }, 56 | 'vcloud_config': { 57 | 'vdc': 'vdc_name' 58 | } 59 | }) 60 | with mock.patch('network_plugin.network.ctx', fake_ctx): 61 | with mock.patch('vcloud_plugin_common.ctx', fake_ctx): 62 | network._dhcp_operation( 63 | fake_client, '_management_network', network.ADD_POOL 64 | ) 65 | # wrong dhcp_range 66 | fake_ctx = self.generate_node_context(properties={ 67 | 'network': { 68 | 'dhcp': { 69 | 'dhcp_range': "" 70 | }, 71 | 'edge_gateway': 'gateway' 72 | }, 73 | 'vcloud_config': { 74 | 'vdc': 'vdc_name' 75 | } 76 | }) 77 | with mock.patch('network_plugin.network.ctx', fake_ctx): 78 | with mock.patch('vcloud_plugin_common.ctx', fake_ctx): 79 | with self.assertRaises(cfy_exc.NonRecoverableError): 80 | network._dhcp_operation( 81 | fake_client, '_management_network', network.ADD_POOL 82 | ) 83 | 84 | fake_ctx = self.generate_node_context(properties={ 85 | 'network': { 86 | 'dhcp': { 87 | 'dhcp_range': "10.1.1.1-10.1.1.255" 88 | }, 89 | 'edge_gateway': 'gateway' 90 | }, 91 | 'vcloud_config': { 92 | 'vdc': 'vdc_name' 93 | } 94 | }) 95 | 96 | with mock.patch('network_plugin.network.ctx', fake_ctx): 97 | with mock.patch('vcloud_plugin_common.ctx', fake_ctx): 98 | # returned error/None from server 99 | with self.assertRaises(cfy_exc.NonRecoverableError): 100 | network._dhcp_operation( 101 | fake_client, '_management_network', network.ADD_POOL 102 | ) 103 | fake_client.get_gateway.assert_called_with( 104 | 'vdc_name', 'gateway' 105 | ) 106 | 107 | # returned error/None from server delete 108 | with self.assertRaises(cfy_exc.NonRecoverableError): 109 | network._dhcp_operation( 110 | fake_client, '_management_network', network.DELETE_POOL 111 | ) 112 | 113 | # returned busy, try next time 114 | self.set_gateway_busy(fake_client._vdc_gateway) 115 | self.prepare_retry(fake_ctx) 116 | 117 | network._dhcp_operation( 118 | fake_client, '_management_network', 119 | network.DELETE_POOL 120 | ), None 121 | self.check_retry_realy_called(fake_ctx) 122 | 123 | # no such gateway 124 | fake_client.get_gateway = mock.MagicMock(return_value=None) 125 | with self.assertRaises(cfy_exc.NonRecoverableError): 126 | network._dhcp_operation( 127 | fake_client, '_management_network', network.ADD_POOL 128 | ) 129 | 130 | if __name__ == '__main__': 131 | unittest.main() 132 | -------------------------------------------------------------------------------- /tests/unittests/test_mock_network_plugin_port.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # * See the License for the specific language governing permissions and 13 | # * limitations under the License. 14 | 15 | import mock 16 | import unittest 17 | 18 | from cloudify import exceptions as cfy_exc 19 | from tests.unittests import test_mock_base 20 | from network_plugin import port 21 | 22 | 23 | class NetworkPluginPortMockTestCase(test_mock_base.TestBase): 24 | 25 | def test_creation_validation(self): 26 | fake_client = self.generate_client() 27 | with mock.patch( 28 | 'vcloud_plugin_common.VcloudAirClient.get', 29 | mock.MagicMock(return_value=fake_client) 30 | ): 31 | # no port 32 | fake_ctx = self.generate_node_context( 33 | properties={} 34 | ) 35 | with self.assertRaises(cfy_exc.NonRecoverableError): 36 | port.creation_validation(ctx=fake_ctx) 37 | # port without allocation 38 | fake_ctx = self.generate_node_context( 39 | properties={ 40 | 'port': { 41 | 'some_field': 'some_value' 42 | } 43 | } 44 | ) 45 | port.creation_validation(ctx=fake_ctx) 46 | # wrong allocation mode 47 | fake_ctx = self.generate_node_context( 48 | properties={ 49 | 'port': { 50 | 'ip_allocation_mode': 'realy wrong' 51 | } 52 | } 53 | ) 54 | with self.assertRaises(cfy_exc.NonRecoverableError): 55 | port.creation_validation(ctx=fake_ctx) 56 | # correct allocation 57 | for mode in ['manual', 'dhcp', 'pool']: 58 | fake_ctx = self.generate_node_context( 59 | properties={ 60 | 'port': { 61 | 'ip_allocation_mode': mode 62 | } 63 | } 64 | ) 65 | port.creation_validation(ctx=fake_ctx) 66 | # wrong manual ip 67 | fake_ctx = self.generate_node_context( 68 | properties={ 69 | 'port': { 70 | 'ip_allocation_mode': 'manual', 71 | 'ip_address': 'a.a.a.a' 72 | } 73 | } 74 | ) 75 | with self.assertRaises(cfy_exc.NonRecoverableError): 76 | port.creation_validation(ctx=fake_ctx) 77 | # correct manual ip 78 | fake_ctx = self.generate_node_context( 79 | properties={ 80 | 'port': { 81 | 'ip_allocation_mode': 'manual', 82 | 'ip_address': '1.1.1.1' 83 | } 84 | } 85 | ) 86 | port.creation_validation(ctx=fake_ctx) 87 | 88 | 89 | if __name__ == '__main__': 90 | unittest.main() 91 | -------------------------------------------------------------------------------- /tests/unittests/test_mock_server_plugin_volume.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # * See the License for the specific language governing permissions and 13 | # * limitations under the License. 14 | 15 | import mock 16 | import unittest 17 | 18 | from cloudify import exceptions as cfy_exc 19 | from cloudify import mocks as cfy_mocks 20 | from server_plugin import volume 21 | import vcloud_plugin_common 22 | from tests.unittests import test_mock_base 23 | import network_plugin 24 | 25 | 26 | class ServerPluginServerMockTestCase(test_mock_base.TestBase): 27 | 28 | # vapp name used for tests 29 | VAPPNAME = "some_other" 30 | 31 | def test_creation_validation_external_resource(self): 32 | fake_client = self.generate_client() 33 | fake_ctx = cfy_mocks.MockCloudifyContext( 34 | node_id='test', 35 | node_name='test', 36 | properties={ 37 | 'use_external_resource': True, 38 | 'vcloud_config': { 39 | 'vdc': 'vdc_name' 40 | } 41 | } 42 | ) 43 | # use external without resorse_id 44 | with mock.patch( 45 | 'vcloud_plugin_common.VcloudAirClient.get', 46 | mock.MagicMock(return_value=fake_client) 47 | ): 48 | with self.assertRaises(cfy_exc.NonRecoverableError): 49 | volume.creation_validation(ctx=fake_ctx) 50 | fake_client.get_disks.assert_called_with('vdc_name') 51 | # with resource id, but without disks(no disks for this client) 52 | fake_ctx = cfy_mocks.MockCloudifyContext( 53 | node_id='test', 54 | node_name='test', 55 | properties={ 56 | 'use_external_resource': True, 57 | 'resource_id': 'some', 58 | 'vcloud_config': { 59 | 'vdc': 'vdc_name' 60 | } 61 | } 62 | ) 63 | with mock.patch( 64 | 'vcloud_plugin_common.VcloudAirClient.get', 65 | mock.MagicMock(return_value=fake_client) 66 | ): 67 | with self.assertRaises(cfy_exc.NonRecoverableError): 68 | volume.creation_validation(ctx=fake_ctx) 69 | # good case for external resource 70 | fake_client.get_disks = mock.MagicMock(return_value=[ 71 | [ 72 | self.generate_fake_client_disk('some'), 73 | self.generate_fake_vms_disk('some') 74 | ] 75 | ]) 76 | with mock.patch( 77 | 'vcloud_plugin_common.VcloudAirClient.get', 78 | mock.MagicMock(return_value=fake_client) 79 | ): 80 | volume.creation_validation(ctx=fake_ctx) 81 | 82 | def test_creation_validation_internal(self): 83 | fake_client = self.generate_client() 84 | # internal resource without volume 85 | fake_ctx = cfy_mocks.MockCloudifyContext( 86 | node_id='test', 87 | node_name='test', 88 | properties={ 89 | 'use_external_resource': False, 90 | 'vcloud_config': { 91 | 'vdc': 'vdc_name' 92 | } 93 | } 94 | ) 95 | with mock.patch( 96 | 'vcloud_plugin_common.VcloudAirClient.get', 97 | mock.MagicMock(return_value=fake_client) 98 | ): 99 | with self.assertRaises(cfy_exc.NonRecoverableError): 100 | volume.creation_validation(ctx=fake_ctx) 101 | fake_client.get_disks.assert_called_with('vdc_name') 102 | # internal resourse wit volume and name, 103 | # but already exist such volume 104 | fake_ctx = cfy_mocks.MockCloudifyContext( 105 | node_id='test', 106 | node_name='test', 107 | properties={ 108 | 'use_external_resource': False, 109 | 'volume': { 110 | 'name': 'some' 111 | }, 112 | 'vcloud_config': { 113 | 'vdc': 'vdc_name' 114 | } 115 | } 116 | ) 117 | fake_client.get_disks = mock.MagicMock(return_value=[ 118 | [ 119 | self.generate_fake_client_disk('some'), 120 | self.generate_fake_vms_disk('some') 121 | ] 122 | ]) 123 | with mock.patch( 124 | 'vcloud_plugin_common.VcloudAirClient.get', 125 | mock.MagicMock(return_value=fake_client) 126 | ): 127 | with self.assertRaises(cfy_exc.NonRecoverableError): 128 | volume.creation_validation(ctx=fake_ctx) 129 | # correct name but without size 130 | fake_ctx = cfy_mocks.MockCloudifyContext( 131 | node_id='test', 132 | node_name='test', 133 | properties={ 134 | 'use_external_resource': False, 135 | 'volume': { 136 | 'name': 'some-other' 137 | }, 138 | 'vcloud_config': { 139 | 'vdc': 'vdc_name' 140 | } 141 | } 142 | ) 143 | with mock.patch( 144 | 'vcloud_plugin_common.VcloudAirClient.get', 145 | mock.MagicMock(return_value=fake_client) 146 | ): 147 | with self.assertRaises(cfy_exc.NonRecoverableError): 148 | volume.creation_validation(ctx=fake_ctx) 149 | # good case 150 | fake_ctx = cfy_mocks.MockCloudifyContext( 151 | node_id='test', 152 | node_name='test', 153 | properties={ 154 | 'use_external_resource': False, 155 | 'volume': { 156 | 'name': 'some-other', 157 | 'size': 11 158 | }, 159 | 'vcloud_config': { 160 | 'vdc': 'vdc_name' 161 | } 162 | } 163 | ) 164 | with mock.patch( 165 | 'vcloud_plugin_common.VcloudAirClient.get', 166 | mock.MagicMock(return_value=fake_client) 167 | ): 168 | volume.creation_validation(ctx=fake_ctx) 169 | 170 | def test_delete_volume(self): 171 | fake_client = self.generate_client() 172 | # external resource 173 | fake_ctx = cfy_mocks.MockCloudifyContext( 174 | node_id='test', 175 | node_name='test', 176 | properties={ 177 | 'use_external_resource': True, 178 | 'vcloud_config': { 179 | 'vdc': 'vdc_name' 180 | } 181 | } 182 | ) 183 | with mock.patch( 184 | 'vcloud_plugin_common.VcloudAirClient.get', 185 | mock.MagicMock(return_value=fake_client) 186 | ): 187 | volume.delete_volume(ctx=fake_ctx) 188 | # cant't add disk 189 | fake_ctx = cfy_mocks.MockCloudifyContext( 190 | node_id='test', 191 | node_name='test', 192 | properties={ 193 | 'use_external_resource': False, 194 | 'volume': { 195 | 'name': 'some-other', 196 | 'size': 11 197 | }, 198 | 'vcloud_config': { 199 | 'vdc': 'vdc_name' 200 | } 201 | } 202 | ) 203 | with mock.patch( 204 | 'vcloud_plugin_common.VcloudAirClient.get', 205 | mock.MagicMock(return_value=fake_client) 206 | ): 207 | with self.assertRaises(cfy_exc.NonRecoverableError): 208 | volume.delete_volume(ctx=fake_ctx) 209 | fake_client.delete_disk.assert_called_with( 210 | 'vdc_name', 'some-other' 211 | ) 212 | # positive case 213 | fake_client.delete_disk = mock.MagicMock( 214 | return_value=( 215 | True, self.generate_task( 216 | vcloud_plugin_common.TASK_STATUS_SUCCESS 217 | ) 218 | ) 219 | ) 220 | with mock.patch( 221 | 'vcloud_plugin_common.VcloudAirClient.get', 222 | mock.MagicMock(return_value=fake_client) 223 | ): 224 | volume.delete_volume(ctx=fake_ctx) 225 | 226 | def test_create_volume(self): 227 | fake_client = self.generate_client() 228 | # external resource 229 | fake_ctx = cfy_mocks.MockCloudifyContext( 230 | node_id='test', 231 | node_name='test', 232 | properties={ 233 | 'use_external_resource': True, 234 | 'vcloud_config': { 235 | 'vdc': 'vdc_name' 236 | } 237 | } 238 | ) 239 | with mock.patch( 240 | 'vcloud_plugin_common.VcloudAirClient.get', 241 | mock.MagicMock(return_value=fake_client) 242 | ): 243 | volume.create_volume(ctx=fake_ctx) 244 | # fail on create volume 245 | fake_ctx = cfy_mocks.MockCloudifyContext( 246 | node_id='test', 247 | node_name='test', 248 | properties={ 249 | 'use_external_resource': False, 250 | 'volume': { 251 | 'name': 'some-other', 252 | 'size': 11 253 | }, 254 | 'vcloud_config': { 255 | 'vdc': 'vdc_name' 256 | } 257 | } 258 | ) 259 | with mock.patch( 260 | 'vcloud_plugin_common.VcloudAirClient.get', 261 | mock.MagicMock(return_value=fake_client) 262 | ): 263 | with self.assertRaises(cfy_exc.NonRecoverableError): 264 | volume.create_volume(ctx=fake_ctx) 265 | fake_client.add_disk.assert_called_with( 266 | 'vdc_name', 'some-other', 11534336 267 | ) 268 | # positive case 269 | disk = mock.Mock() 270 | disk.get_Tasks = mock.MagicMock( 271 | return_value=[self.generate_task( 272 | vcloud_plugin_common.TASK_STATUS_SUCCESS 273 | )] 274 | ) 275 | fake_client.add_disk = mock.MagicMock( 276 | return_value=(True, disk) 277 | ) 278 | with mock.patch( 279 | 'vcloud_plugin_common.VcloudAirClient.get', 280 | mock.MagicMock(return_value=fake_client) 281 | ): 282 | volume.create_volume(ctx=fake_ctx) 283 | 284 | def test_volume_operation(self): 285 | fake_ctx, fake_client = self._gen_volume_context_and_client() 286 | 287 | def _run_volume_operation(fake_ctx, fake_client, operation): 288 | with mock.patch( 289 | 'vcloud_plugin_common.ctx', fake_ctx 290 | ): 291 | with mock.patch( 292 | 'server_plugin.volume.ctx', fake_ctx 293 | ): 294 | volume._volume_operation(fake_client, operation) 295 | 296 | # use external resource, no disks 297 | _run_volume_operation(fake_ctx, fake_client, 'ATTACH') 298 | fake_client.get_diskRefs.assert_called_with( 299 | fake_client._app_vdc 300 | ) 301 | # disk exist, can't attach 302 | disk_ref = self.generate_fake_client_disk_ref('some') 303 | fake_client.get_diskRefs = mock.MagicMock(return_value=[ 304 | disk_ref 305 | ]) 306 | with self.assertRaises(cfy_exc.NonRecoverableError): 307 | _run_volume_operation(fake_ctx, fake_client, 'ATTACH') 308 | fake_client._vapp.attach_disk_to_vm.assert_called_with( 309 | self.VAPPNAME, disk_ref 310 | ) 311 | # disk exist, can't detach 312 | with self.assertRaises(cfy_exc.NonRecoverableError): 313 | _run_volume_operation(fake_ctx, fake_client, 'DETACH') 314 | fake_client._vapp.detach_disk_from_vm.assert_called_with( 315 | self.VAPPNAME, disk_ref 316 | ) 317 | # wrong operation 318 | with self.assertRaises(cfy_exc.NonRecoverableError): 319 | _run_volume_operation(fake_ctx, fake_client, 'Wrong') 320 | # disk exist, can attach 321 | fake_client._vapp.attach_disk_to_vm = mock.MagicMock( 322 | return_value=self.generate_task( 323 | vcloud_plugin_common.TASK_STATUS_SUCCESS 324 | ) 325 | ) 326 | _run_volume_operation(fake_ctx, fake_client, 'ATTACH') 327 | # disk exist, can detach 328 | fake_client._vapp.detach_disk_from_vm = mock.MagicMock( 329 | return_value=self.generate_task( 330 | vcloud_plugin_common.TASK_STATUS_SUCCESS 331 | ) 332 | ) 333 | _run_volume_operation(fake_ctx, fake_client, 'DETACH') 334 | # disk exist, use internal resource 335 | fake_ctx._target.node.properties = { 336 | 'volume': { 337 | 'name': 'some' 338 | }, 339 | 'use_external_resource': False 340 | } 341 | _run_volume_operation(fake_ctx, fake_client, 'DETACH') 342 | fake_client._vapp.detach_disk_from_vm.assert_called_with( 343 | 'some_other', disk_ref 344 | ) 345 | 346 | def _gen_volume_context_and_client(self): 347 | fake_client = self.generate_client() 348 | fake_ctx = self.generate_relation_context() 349 | fake_ctx._target.node.properties = { 350 | 'use_external_resource': True 351 | } 352 | fake_ctx._source.node.properties = { 353 | 'volume': { 354 | 'name': 'some' 355 | }, 356 | 'vcloud_config': { 357 | 'vdc': 'vdc_name', 358 | }, 359 | 'resource_id': 'some' 360 | } 361 | fake_ctx._target.instance.runtime_properties = { 362 | network_plugin.VCLOUD_VAPP_NAME: self.VAPPNAME 363 | } 364 | return fake_ctx, fake_client 365 | 366 | def test_attach_volume(self): 367 | """ 368 | use external resource, try to attach but no disks 369 | """ 370 | fake_ctx, fake_client = self._gen_volume_context_and_client() 371 | with mock.patch( 372 | 'vcloud_plugin_common.VcloudAirClient.get', 373 | mock.MagicMock(return_value=fake_client) 374 | ): 375 | volume.attach_volume(ctx=fake_ctx) 376 | 377 | def test_detach_volume(self): 378 | """ 379 | use external resource, try to detach but no disks 380 | """ 381 | fake_ctx, fake_client = self._gen_volume_context_and_client() 382 | with mock.patch( 383 | 'vcloud_plugin_common.VcloudAirClient.get', 384 | mock.MagicMock(return_value=fake_client) 385 | ): 386 | volume.detach_volume(ctx=fake_ctx) 387 | 388 | if __name__ == '__main__': 389 | unittest.main() 390 | -------------------------------------------------------------------------------- /tests/unittests/test_mock_vcloud_plugin_common.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # * See the License for the specific language governing permissions and 13 | # * limitations under the License. 14 | 15 | import mock 16 | import unittest 17 | 18 | from cloudify import exceptions as cfy_exc 19 | from tests.unittests import test_mock_base 20 | import vcloud_plugin_common 21 | 22 | 23 | class VcloudPluginCommonMockTestCase(test_mock_base.TestBase): 24 | """ 25 | test for common vcloud logic 26 | """ 27 | 28 | def test_get_mandatory(self): 29 | # wrong key 30 | with self.assertRaises(cfy_exc.NonRecoverableError): 31 | vcloud_plugin_common.get_mandatory( 32 | {'a': 'b'}, 'c' 33 | ) 34 | 35 | # empty key 36 | with self.assertRaises(cfy_exc.NonRecoverableError): 37 | vcloud_plugin_common.get_mandatory( 38 | {'a': None}, 'a' 39 | ) 40 | # everything fine 41 | self.assertEqual( 42 | vcloud_plugin_common.get_mandatory({ 43 | 'a': 'b' 44 | }, 'a'), 45 | 'b' 46 | ) 47 | 48 | def test_get_vcloud_config(self): 49 | # context.NODE_INSTANCE 50 | fake_ctx = self.generate_node_context( 51 | properties={ 52 | 'vcloud_config': { 53 | 'vdc': 'vdc_name' 54 | } 55 | } 56 | ) 57 | fake_ctx._instance = mock.Mock() 58 | with mock.patch( 59 | 'vcloud_plugin_common.ctx', fake_ctx 60 | ): 61 | self.assertEqual( 62 | vcloud_plugin_common.get_vcloud_config(), 63 | { 64 | 'vdc': 'vdc_name' 65 | } 66 | ) 67 | # context.RELATIONSHIP_INSTANCE 68 | fake_ctx = self.generate_relation_context() 69 | fake_ctx._source.node.properties = { 70 | 'vcloud_config': { 71 | 'vdc': 'vdc_name' 72 | } 73 | } 74 | with mock.patch( 75 | 'vcloud_plugin_common.ctx', fake_ctx 76 | ): 77 | self.assertEqual( 78 | vcloud_plugin_common.get_vcloud_config(), 79 | { 80 | 'vdc': 'vdc_name' 81 | } 82 | ) 83 | # context.DEPLOYMENT 84 | fake_ctx = self.generate_node_context( 85 | properties={ 86 | 'vcloud_config': { 87 | 'vdc': 'vdc_name' 88 | } 89 | } 90 | ) 91 | fake_ctx._source = None 92 | fake_ctx._instance = None 93 | with mock.patch( 94 | 'vcloud_plugin_common.ctx', fake_ctx 95 | ): 96 | with self.assertRaises(cfy_exc.NonRecoverableError): 97 | vcloud_plugin_common.get_vcloud_config() 98 | 99 | def test_transform_resource_name(self): 100 | fake_ctx = self.generate_node_context() 101 | fake_ctx._bootstrap_context = mock.Mock() 102 | fake_ctx._bootstrap_context.resources_prefix = None 103 | # wrong resource name type 104 | with self.assertRaises(ValueError): 105 | vcloud_plugin_common.transform_resource_name(None, fake_ctx) 106 | with self.assertRaises(ValueError): 107 | vcloud_plugin_common.transform_resource_name(11, fake_ctx) 108 | # resource name string 109 | self.assertEqual( 110 | vcloud_plugin_common.transform_resource_name( 111 | 'test', fake_ctx 112 | ), 113 | 'test' 114 | ) 115 | # resource name is dict 116 | self.assertEqual( 117 | vcloud_plugin_common.transform_resource_name( 118 | {'name': 'test'}, fake_ctx 119 | ), 120 | 'test' 121 | ) 122 | # prefix not exist in name 123 | fake_ctx._bootstrap_context.resources_prefix = 'prfx_' 124 | self.assertEqual( 125 | vcloud_plugin_common.transform_resource_name( 126 | 'test', fake_ctx 127 | ), 128 | 'prfx_test' 129 | ) 130 | # prefix exist in name 131 | fake_ctx._bootstrap_context.resources_prefix = 'prfx_' 132 | self.assertEqual( 133 | vcloud_plugin_common.transform_resource_name( 134 | 'prfx_test', fake_ctx 135 | ), 136 | 'prfx_prfx_test' 137 | ) 138 | 139 | def test_is_subscription(self): 140 | # subscription 141 | self.assertTrue( 142 | vcloud_plugin_common.is_subscription( 143 | vcloud_plugin_common.SUBSCRIPTION_SERVICE_TYPE 144 | ) 145 | ) 146 | # ondemand 147 | self.assertFalse( 148 | vcloud_plugin_common.is_subscription( 149 | vcloud_plugin_common.ONDEMAND_SERVICE_TYPE 150 | ) 151 | ) 152 | # None, by default used subscription service, so True 153 | self.assertTrue( 154 | vcloud_plugin_common.is_subscription( 155 | None 156 | ) 157 | ) 158 | 159 | def test_is_ondemand(self): 160 | # subscription 161 | self.assertFalse( 162 | vcloud_plugin_common.is_ondemand( 163 | vcloud_plugin_common.SUBSCRIPTION_SERVICE_TYPE 164 | ) 165 | ) 166 | # ondemand 167 | self.assertTrue( 168 | vcloud_plugin_common.is_ondemand( 169 | vcloud_plugin_common.ONDEMAND_SERVICE_TYPE 170 | ) 171 | ) 172 | # None, by default used subscription service, so False 173 | self.assertFalse( 174 | vcloud_plugin_common.is_ondemand( 175 | None 176 | ) 177 | ) 178 | 179 | def test_wait_for_task(self): 180 | fake_client = self.generate_client() 181 | # error in task 182 | fake_task = self.generate_task( 183 | vcloud_plugin_common.TASK_STATUS_ERROR 184 | ) 185 | with self.assertRaises(cfy_exc.NonRecoverableError): 186 | vcloud_plugin_common.wait_for_task(fake_client, fake_task) 187 | # success in task 188 | fake_task = self.generate_task( 189 | vcloud_plugin_common.TASK_STATUS_SUCCESS 190 | ) 191 | vcloud_plugin_common.wait_for_task(fake_client, fake_task) 192 | # success after wait 193 | fake_task = self.generate_task( 194 | None 195 | ) 196 | fake_task_after_wait = self.generate_task( 197 | vcloud_plugin_common.TASK_STATUS_SUCCESS 198 | ) 199 | sleep = mock.MagicMock(return_value=None) 200 | response = mock.Mock() 201 | response.content = 'Success' 202 | with mock.patch( 203 | 'pyvcloud.schema.vcd.v1_5.schemas.vcloud.taskType.parseString', 204 | mock.MagicMock(return_value=fake_task_after_wait) 205 | ): 206 | with mock.patch( 207 | 'requests.get', mock.MagicMock(return_value=response) 208 | ): 209 | with mock.patch( 210 | 'time.sleep', 211 | sleep 212 | ): 213 | vcloud_plugin_common.wait_for_task( 214 | fake_client, fake_task 215 | ) 216 | 217 | def test_with_vca_client(self): 218 | # context.NODE_INSTANCE 219 | fake_ctx = self.generate_node_context( 220 | properties={ 221 | 'vcloud_config': { 222 | 'vdc': 'vdc_name' 223 | } 224 | } 225 | ) 226 | fake_client = self.generate_client() 227 | with mock.patch( 228 | 'vcloud_plugin_common.ctx', fake_ctx 229 | ): 230 | with mock.patch( 231 | 'vcloud_plugin_common.VcloudAirClient.get', 232 | mock.MagicMock(return_value=fake_client) 233 | ): 234 | @vcloud_plugin_common.with_vca_client 235 | def _some_function(vca_client, **kwargs): 236 | return vca_client 237 | 238 | self.assertEqual( 239 | _some_function(ctx=fake_ctx), 240 | fake_client 241 | ) 242 | # context.DEPLOYMENT 243 | fake_ctx = self.generate_node_context( 244 | properties={ 245 | 'vcloud_config': { 246 | 'vdc': 'vdc_name' 247 | } 248 | } 249 | ) 250 | fake_ctx._source = None 251 | fake_ctx._instance = None 252 | with mock.patch( 253 | 'vcloud_plugin_common.ctx', fake_ctx 254 | ): 255 | with mock.patch( 256 | 'vcloud_plugin_common.VcloudAirClient.get', 257 | mock.MagicMock(return_value=fake_client) 258 | ): 259 | @vcloud_plugin_common.with_vca_client 260 | def _some_function(vca_client, **kwargs): 261 | return vca_client 262 | 263 | with self.assertRaises(cfy_exc.NonRecoverableError): 264 | _some_function(ctx=fake_ctx), 265 | 266 | def test_config(self): 267 | # good case 268 | fake_file = mock.mock_open(read_data="test: test") 269 | with mock.patch( 270 | '__builtin__.open', fake_file 271 | ): 272 | config = vcloud_plugin_common.Config() 273 | self.assertEqual( 274 | config.get(), 275 | {'test': 'test'} 276 | ) 277 | # bad case 278 | mock_for_raise = mock.MagicMock(side_effect=IOError('no file')) 279 | fake_file = mock.mock_open(mock_for_raise) 280 | with mock.patch( 281 | '__builtin__.open', fake_file 282 | ): 283 | config = vcloud_plugin_common.Config() 284 | self.assertEqual( 285 | config.get(), 286 | {} 287 | ) 288 | 289 | if __name__ == '__main__': 290 | unittest.main() 291 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py27-ondemand, py27-subscription, py27-unittests, pep8 3 | minversion = 1.6 4 | skipsdist = True 5 | 6 | [testenv] 7 | usedevelop = True 8 | envdir = .tox/devenv 9 | deps = -rtest-requirements.txt 10 | -rdev-requirements.txt 11 | 12 | [testenv:py27-ondemand] 13 | commands = nosetests -x -s --tc=ondemand: tests/integration {posargs} 14 | 15 | [testenv:py27-subscription] 16 | commands = nosetests -x -s --tc=subscription: tests/integration {posargs} 17 | 18 | [testenv:py27-unittests] 19 | commands = nosetests -x -s tests/unittests --cover-html --with-coverage --cover-package=vcloud_plugin_common --cover-package=network_plugin --cover-package=server_plugin 20 | 21 | [testenv:pep8] 22 | commands= 23 | flake8 network_plugin server_plugin vcloud_plugin_common tests manager_blueprint/scripts 24 | ignore = 25 | exclude=.venv,.tox,dist,*egg,etc,build 26 | filename=*.py 27 | --------------------------------------------------------------------------------