diff --git a/README.md b/README.md index 5821223..2f2b860 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,11 @@ tosca-vcloud-plugin ## Running Integration Tests +First, you must prepare vCloud enviroment. +Integration tests uses ondemand and subscription services. +On each service must be at least one existent network. +Firewall must be disabled, or allow by default. + Create virtual environment and install plugin in dev-mode ``` virtualenv venv && source venv/bin/activate @@ -48,4 +53,7 @@ tox -e ondemand -- \test_network_plugin.py tox -e ondemand -- \test_network_plugin.py:ValidationOperationsTestCase tox -e ondemand -- \test_network_plugin.py:ValidationOperationsTestCase.test_validation -``` \ No newline at end of file +``` + +## Examples +For official blueprint examples using this Cloudify plugin, please see [Cloudify Community Blueprints Examples](https://github.com/cloudify-community/blueprint-examples/). diff --git a/constraints.txt b/constraints.txt new file mode 100644 index 0000000..42a671b --- /dev/null +++ b/constraints.txt @@ -0,0 +1,3 @@ +cloudify-common==4.5.0 +paramiko==2.7.1 +fabric==1.14.1 diff --git a/dev-requirements.txt b/dev-requirements.txt index 55fdc57..8b13789 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,5 +1 @@ --e git+https://github.com/cloudify-cosmo/cloudify-dsl-parser@master#egg=cloudify-dsl-parser==3.2 --e git+https://github.com/cloudify-cosmo/cloudify-rest-client@master#egg=cloudify-rest-client==3.2 --e git+https://github.com/cloudify-cosmo/cloudify-plugins-common@master#egg=cloudify-plugins-common==3.2 -IPy -pyvcloud + diff --git a/examples/blueprint.yaml b/examples/blueprint.yaml index 1e7200e..1099de0 100644 --- a/examples/blueprint.yaml +++ b/examples/blueprint.yaml @@ -1,8 +1,9 @@ -tosca_definitions_version: cloudify_dsl_1_0 +tosca_definitions_version: cloudify_dsl_1_3 imports: - - http://www.getcloudify.org/spec/cloudify/3.2/types.yaml - - https://raw.githubusercontent.com/cloudify-cosmo/tosca-vcloud-plugin/master/plugin.yaml + - https://cloudify.co/spec/cloudify/4.4/types.yaml + # - http://www.getcloudify.org/spec/tosca-vcloud-plugin/1.6.0/plugin.yaml + - ../plugin.yaml node_types: vcloud_configuration: diff --git a/manager_blueprint/README.md b/manager_blueprint/README.md new file mode 100644 index 0000000..b8dfae4 --- /dev/null +++ b/manager_blueprint/README.md @@ -0,0 +1,5 @@ +Manager blueprint moved to https://github.com/cloudify-cosmo/cloudify-manager-blueprints/ + +Please use as manager blueprint such file: vcloud-manager-blueprint.yaml + +Inputs example here: vcloud-manager-blueprint-inputs.yaml diff --git a/manager_blueprint/inputs.yaml.example b/manager_blueprint/inputs.yaml.example deleted file mode 100644 index d4dab0c..0000000 --- a/manager_blueprint/inputs.yaml.example +++ /dev/null @@ -1,22 +0,0 @@ -vcloud_username: some_user -vcloud_password: some_password -vcloud_url: https://iam.vchs.vmware.com -vcloud_service: VDC -vcloud_org: VDC -vcloud_vdc: VDC -vcloud_service_type: ondemand -vcloud_instance: 00000000-0000-0000-0000-000000000000 -edge_gateway: gateway -manager_server_name: some-server -manager_server_catalog: default-catalog -manager_server_template: Ubuntu Server 12.04 LTS (amd64 20150127) -manager_server_public_ip: -management_network_use_existing: true -management_network_public_nat_use_existing: true -management_network_name: some-net -management_network_public_ip: -manager_private_key_path: ~/.ssh/manager.pem -agent_private_key_path: ~/.ssh/manager.pem -manager_public_key: some_key -agent_public_key: some_key -management_port_ip_allocation_mode: pool diff --git a/manager_blueprint/scripts/configure.py b/manager_blueprint/scripts/configure.py deleted file mode 100644 index f83b668..0000000 --- a/manager_blueprint/scripts/configure.py +++ /dev/null @@ -1,71 +0,0 @@ -import tempfile -import json - -import fabric - -import vcloud_plugin_common -from cloudify import ctx - -PROVIDER_CONTEXT_RUNTIME_PROPERTY = 'provider_context' - - -def configure(vcloud_config): - """ - copy configuration to managment host, - install docker - and save current context to .cloudify/context - For now - we have saved only managment network name - """ - _copy_vsphere_configuration_to_manager(vcloud_config) - _install_docker() - _save_context() - - -def _copy_vsphere_configuration_to_manager(vcloud_config): - """ - Copy current config to remote node - """ - tmp = tempfile.mktemp() - with open(tmp, 'w') as f: - json.dump(vcloud_config, f) - fabric.api.put(tmp, - vcloud_plugin_common.Config.VCLOUD_CONFIG_PATH_DEFAULT) - - -def _install_docker(): - """ - install docker from https://get.docker.com/ - """ - distro = fabric.api.run( - 'python -c "import platform; print platform.dist()[0]"') - kernel_version = fabric.api.run( - 'python -c "import platform; print platform.release()"') - if kernel_version.startswith("3.13") and 'Ubuntu' in distro: - fabric.api.run("wget -qO- https://get.docker.com/ | sudo sh") - - -def _save_context(): - """ - save current managment network for use as default network for - all new nodes - """ - resources = dict() - - node_instances = ctx._endpoint.storage.get_node_instances() - nodes_by_id = \ - {node.id: node for node in ctx._endpoint.storage.get_nodes()} - - for node_instance in node_instances: - props = nodes_by_id[node_instance.node_id].properties - - if "management_network" == node_instance.node_id: - resources['int_network'] = { - "name": props.get('resource_id') - } - - provider = { - 'resources': resources - } - - ctx.instance.runtime_properties[PROVIDER_CONTEXT_RUNTIME_PROPERTY] = \ - provider diff --git a/manager_blueprint/scripts/configure_docker.py b/manager_blueprint/scripts/configure_docker.py deleted file mode 100644 index 22aa3bc..0000000 --- a/manager_blueprint/scripts/configure_docker.py +++ /dev/null @@ -1,21 +0,0 @@ -import fabric - - -def configure(vcloud_config): - """ - only update container with vcloud specific packages - """ - _update_container() - - -def _update_container(): - """ install some packeges for future deployments creation """ - # update system to last version - fabric.api.run("sudo docker exec -i -t cfy apt-get " - "update -q -y 2>&1") - fabric.api.run("sudo docker exec -i -t cfy apt-get " - "dist-upgrade -q -y 2>&1") - # install: - fabric.api.run("sudo docker exec -i -t cfy apt-get " - "install gcc python-dev libxml2-dev libxslt-dev " - "zlib1g-dev -q -y 2>&1") diff --git a/manager_blueprint/vcloud-manager-blueprint.yaml b/manager_blueprint/vcloud-manager-blueprint.yaml deleted file mode 100644 index 276b7ff..0000000 --- a/manager_blueprint/vcloud-manager-blueprint.yaml +++ /dev/null @@ -1,490 +0,0 @@ -tosca_definitions_version: cloudify_dsl_1_0 - -imports: - - http://www.getcloudify.org/spec/cloudify/3.2/types.yaml - - https://raw.githubusercontent.com/cloudify-cosmo/tosca-vcloud-plugin/master/plugin.yaml - - http://www.getcloudify.org/spec/fabric-plugin/1.2/plugin.yaml - -inputs: - vcloud_username: - type: string - - vcloud_password: - type: string - - vcloud_token: - type: string - default: '' - - vcloud_url: - type: string - - vcloud_service: - type: string - - vcloud_service_type: - type: string - default: 'subscription' - - vcloud_instance: - type: string - default: '' - description: > - Only required for ondemand service type. - - vcloud_api_version: - type: string - default: '5.6' - - vcloud_org_url: - type: string - default: '' - description: > - Only required if using token based login on a private vcloud - director. This can be obtained by following the vcloud API - example docs. If you are unsure of how to obtain this, you will - need to use password based login. - - vcloud_org: - type: string - - vcloud_vdc: - type: string - - manager_server_name: - type: string - - manager_server_catalog: - type: string - - manager_server_template: - type: string - - manager_server_cpus: - type: string - default: 2 - - manager_server_memory: - type: string - default: 4096 - - management_network_use_existing: - type: boolean - default: false - - management_network_name: - type: string - - management_port_ip_allocation_mode: - type: string - default: pool - - management_port_ip_address: - type: string - default: '' - - management_network_public_nat_use_existing: - type: boolean - default: false - - management_network_public_ip: - type: string - default: '' - - edge_gateway: - type: string - default: gateway - description: > - For 'ondemand' service type, the value of edge_gateway - is always 'gateway' - - manager_server_public_ip: - type: string - default: '' - description: > - For 'ondemand' service type, the value of - floating_ip_public_ip can be empty - - agents_user: - type: string - default: ubuntu - - manager_server_user: - default: ubuntu - type: string - - manager_private_key_path: - default: ~/.ssh/cloudify-manager-kp.pem - type: string - - agent_private_key_path: - default: ~/.ssh/cloudify-agent-kp.pem - type: string - - manager_public_key: - type: string - default: '' - - agent_public_key: - type: string - default: '' - - resources_prefix: - type: string - default: '' - - volume_use_external: - type: boolean - default: false - - volume_external_name: - type: string - default: '' - - volume_name: - type: string - default: 'manager_disk' - - volume_size_Mb: - type: string - default: 10240 - - -node_types: - vcloud_configuration: - derived_from: cloudify.nodes.Root - properties: - vcloud_config: {} - -node_templates: - - node_security_group: - type: cloudify.vcloud.nodes.SecurityGroup - properties: - security_group: - name: nodevcloud_security_group - edge_gateway: { get_input: edge_gateway } - rules: - - source: external - destination: internal - destination_port: 22 - action: allow - description: > - ssh to management node - protocol: TCP - - source: external - destination: internal - destination_port: 80 - action: allow - description: > - http to management node - protocol: TCP - - source: host - destination: any - action: allow - description: > - backward network connection for host updates - protocol: any - - source: external - destination: internal - action: allow - description: > - Allow ping - protocol: ICMP - vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } - - management_port: - type: cloudify.vcloud.nodes.Port - properties: - port: - network: { get_input: management_network_name } - ip_allocation_mode: { get_input: management_port_ip_allocation_mode } - ip_address: { get_input: management_port_ip_address } - primary_interface: true - vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } - relationships: - - target: management_network - type: cloudify.vcloud.port_connected_to_network - - management_network: - type: cloudify.vcloud.nodes.Network - properties: - use_external_resource: { get_input: management_network_use_existing } - resource_id: { get_input: management_network_name } - network: - name: { get_input: management_network_name } - edge_gateway: { get_input: edge_gateway } - static_range: 10.67.79.129-10.67.79.254 - netmask: 255.255.255.0 - gateway_ip: 10.67.79.1 - dns: - - 10.67.79.1 - - 8.8.8.8 - dhcp: - dhcp_range: 10.67.79.2-10.67.79.128 - vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } - relationships: - - target: management_network_nat - type: cloudify.vcloud.net_connected_to_public_nat - - management_network_nat: - type: cloudify.vcloud.nodes.PublicNAT - properties: - use_external_resource: { get_input: management_network_public_nat_use_existing } - nat: - edge_gateway: { get_input: edge_gateway } - public_ip: { get_input: management_network_public_ip } - rules: - - type: SNAT - vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } - - management_server_nat: - type: cloudify.vcloud.nodes.PublicNAT - properties: - nat: - edge_gateway: { get_input: edge_gateway } - public_ip: { get_input: manager_server_public_ip } - rules: - - type: DNAT - protocol: tcp - original_port: 80 - translated_port: 80 - - type: DNAT - protocol: tcp - original_port: 8086 - translated_port: 8086 - - type: DNAT - protocol: tcp - original_port: 443 - translated_port: 443 - - type: DNAT - protocol: tcp - original_port: 22 - translated_port: 22 - vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } - - vcloud_configuration: - type: vcloud_configuration - properties: - vcloud_config: - username: { get_input: vcloud_username } - password: { get_input: vcloud_password } - token: { get_input: vcloud_token } - url: { get_input: vcloud_url } - service: { get_input: vcloud_service } - org: { get_input: vcloud_org } - vdc: { get_input: vcloud_vdc } - service_type: { get_input: vcloud_service_type } - instance: { get_input: vcloud_instance } - api_version: { get_input: vcloud_api_version } - org_url: { get_input: vcloud_org_url } - edge_gateway: { get_input: edge_gateway } - - manager_keypair: - type: cloudify.vcloud.nodes.KeyPair - properties: - private_key_path: { get_input: manager_private_key_path } - public_key: - key: { get_input: manager_public_key } - user: { get_input: manager_server_user } - - agent_keypair: - type: cloudify.vcloud.nodes.KeyPair - properties: - private_key_path: { get_input: agent_private_key_path } - public_key: - key: { get_input: agent_public_key } - user: { get_input: agents_user } - - manager_server: - type: cloudify.vcloud.nodes.Server - properties: - install_agent: false - server: - name: { get_input: manager_server_name } - catalog: { get_input: manager_server_catalog } - template: { get_input: manager_server_template } - guest_customization: - public_keys: - - { get_property: [manager_keypair, public_key] } - - { get_property: [agent_keypair, public_key] } - computer_name: { get_input: manager_server_name } - hardware: - cpu: { get_input: manager_server_cpus } - memory: { get_input: manager_server_memory } - management_network: { get_input: management_network_name } - vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } - relationships: - - target: management_port - type: cloudify.vcloud.server_connected_to_port - - target: management_server_nat - type: cloudify.vcloud.server_connected_to_public_nat - - target: node_security_group - type: cloudify.vcloud.server_connected_to_security_group - - volume: - type: cloudify.vcloud.nodes.Volume - properties: - device_name: /dev/sdb - volume: - name: { get_input: volume_name } - size: { get_input: volume_size_Mb } - use_external_resource: { get_input: volume_use_external } - resource_id: { get_input: volume_external_name } - vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } - relationships: - - type: cloudify.vcloud.volume_attached_to_server - target: manager_server - - manager_data: - type: cloudify.nodes.FileSystem - properties: - fs_type: ext4 - fs_mount_path: /var/lib/docker - interfaces: - cloudify.interfaces.lifecycle: - configure: - implementation: fabric.fabric_plugin.tasks.run_script - inputs: - script_path: https://raw.githubusercontent.com/cloudify-cosmo/cloudify-manager/master/resources/rest-service/cloudify/fs/mkfs.sh - fabric_env: - user: { get_input: manager_server_user } - key_filename: { get_input: manager_private_key_path } - host_string: { get_attribute: [management_server_nat, public_ip] } - relationships: - - type: cloudify.relationships.file_system_depends_on_volume - target: volume - source_interfaces: - cloudify.interfaces.relationship_lifecycle: - preconfigure: - implementation: fabric.fabric_plugin.tasks.run_script - inputs: - script_path: https://raw.githubusercontent.com/cloudify-cosmo/cloudify-manager/master/resources/rest-service/cloudify/fs/fdisk.sh - device_name: { get_attribute: [TARGET, device_name] } - fabric_env: - user: { get_input: manager_server_user } - key_filename: { get_input: manager_private_key_path } - host_string: { get_attribute: [management_server_nat, public_ip] } - - - type: cloudify.relationships.file_system_contained_in_compute - target: manager_server - source_interfaces: - cloudify.interfaces.relationship_lifecycle: - establish: - implementation: fabric.fabric_plugin.tasks.run_script - inputs: - script_path: https://raw.githubusercontent.com/cloudify-cosmo/cloudify-manager/master/resources/rest-service/cloudify/fs/mount-docker.sh - fabric_env: - user: { get_input: manager_server_user } - key_filename: { get_input: manager_private_key_path } - host_string: { get_attribute: [management_server_nat, public_ip] } - unlink: - implementation: fabric.fabric_plugin.tasks.run_script - inputs: - script_path: https://raw.githubusercontent.com/cloudify-cosmo/cloudify-manager/master/resources/rest-service/cloudify/fs/unmount.sh - fabric_env: - user: { get_input: manager_server_user } - key_filename: { get_input: manager_private_key_path } - host_string: { get_attribute: [management_server_nat, public_ip] } - - manager: - type: cloudify.nodes.CloudifyManager - properties: - cloudify_packages: - agents: - ubuntu_agent_url: http://gigaspaces-repository-eu.s3.amazonaws.com/org/cloudify3/3.2.0/ga-RELEASE/cloudify-ubuntu-agent_3.2.0-ga-b200_amd64.deb - centos_agent_url: http://gigaspaces-repository-eu.s3.amazonaws.com/org/cloudify3/3.2.0/ga-RELEASE/cloudify-centos-final-agent_3.2.0-ga-b200_amd64.deb - windows_agent_url: http://gigaspaces-repository-eu.s3.amazonaws.com/org/cloudify3/3.2.0/ga-RELEASE/cloudify-windows-agent_3.2.0-ga-b200_amd64.deb - docker: - docker_url: http://gigaspaces-repository-eu.s3.amazonaws.com/org/cloudify3/3.2.0/ga-RELEASE/cloudify-docker_3.2.0-ga-b200.tar - - cloudify: - resources_prefix: { get_input: resources_prefix } - - cloudify_agent: - min_workers: 0 - max_workers: 5 - remote_execution_port: 22 - user: { get_input: agents_user } - - workflows: - task_retries: -1 # this means forever - task_retry_interval: 30 - - policy_engine: - start_timeout: 30 - - relationships: - - target: manager_server - type: cloudify.relationships.contained_in - - target: manager_data - type: cloudify.relationships.depends_on - - interfaces: - cloudify.interfaces.lifecycle: - create: - implementation: fabric.fabric_plugin.tasks.run_task - inputs: - tasks_file: scripts/configure.py - task_name: configure - task_properties: - vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } - fabric_env: - user: { get_input: manager_server_user } - key_filename: { get_input: manager_private_key_path } - host_string: { get_attribute: [management_server_nat, public_ip] } - configure: - implementation: fabric.fabric_plugin.tasks.run_module_task - inputs: - task_mapping: cloudify_cli.bootstrap.tasks.bootstrap_docker - task_properties: - cloudify_packages: { get_property: [manager, cloudify_packages] } - agent_local_key_path: { get_property: [agent_keypair, private_key_path] } - provider_context: { get_attribute: [manager, provider_context] } - fabric_env: - user: { get_input: manager_server_user } - key_filename: { get_input: manager_private_key_path } - host_string: { get_attribute: [management_server_nat, public_ip] } - start: - implementation: fabric.fabric_plugin.tasks.run_task - inputs: - tasks_file: scripts/configure_docker.py - task_name: configure - task_properties: - vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } - fabric_env: - user: { get_input: manager_server_user } - key_filename: { get_input: manager_private_key_path } - host_string: { get_attribute: [management_server_nat, public_ip] } - stop: - implementation: fabric.fabric_plugin.tasks.run_module_task - inputs: - task_mapping: cloudify_cli.bootstrap.tasks.stop_manager_container - fabric_env: - user: { get_input: manager_server_user } - key_filename: { get_property: [manager_keypair, private_key_path] } - host_string: { get_attribute: [management_server_nat, public_ip] } - delete: - implementation: fabric.fabric_plugin.tasks.run_module_task - inputs: - task_mapping: cloudify_cli.bootstrap.tasks.stop_docker_service - fabric_env: - user: { get_input: manager_server_user } - key_filename: { get_property: [manager_keypair, private_key_path] } - host_string: { get_attribute: [management_server_nat, public_ip] } - cloudify.interfaces.validation: - creation: - implementation: cli.cloudify_cli.bootstrap.tasks.creation_validation - inputs: - cloudify_packages: { get_property: [manager, cloudify_packages] } - - -plugins: - cli: - install: false - executor: central_deployment_agent - - -outputs: - manager_ip: - value: { get_attribute: [management_server_nat, public_ip] } diff --git a/network_plugin/keypair.py b/network_plugin/keypair.py deleted file mode 100644 index 01bd7a5..0000000 --- a/network_plugin/keypair.py +++ /dev/null @@ -1,18 +0,0 @@ -from cloudify import ctx -from cloudify import exceptions as cfy_exc -from cloudify.decorators import operation -import os.path - - -@operation -def creation_validation(**kwargs): - """ - check availability of path used in field private_key_path of - node properties - """ - key = ctx.node.properties.get('private_key_path') - if key: - key_path = os.path.expanduser(key) - if not os.path.isfile(key_path): - raise cfy_exc.NonRecoverableError( - "Private key file {0} is absent".format(key_path)) diff --git a/network_plugin/port.py b/network_plugin/port.py deleted file mode 100644 index d6a5b67..0000000 --- a/network_plugin/port.py +++ /dev/null @@ -1,24 +0,0 @@ -from cloudify import ctx -from cloudify import exceptions as cfy_exc -from cloudify.decorators import operation -from vcloud_plugin_common import with_vca_client, get_mandatory -from network_plugin import check_ip - - -@operation -@with_vca_client -def creation_validation(vca_client, **kwargs): - """ - validate port settings, - ip_allocation_mode must be in 'manual', 'dhcp', 'pool', - and valid ip_address if set - """ - port = get_mandatory(ctx.node.properties, 'port') - ip_allocation_mode = port.get('ip_allocation_mode') - if ip_allocation_mode: - if ip_allocation_mode.lower() not in ['manual', 'dhcp', 'pool']: - raise cfy_exc.NonRecoverableError( - "Unknown allocation mode {0}".format(ip_allocation_mode)) - ip_address = port.get('ip_address') - if ip_address: - check_ip(ip_address) diff --git a/plugin.yaml b/plugin.yaml index 7c2ef43..7eeaed7 100644 --- a/plugin.yaml +++ b/plugin.yaml @@ -1,203 +1,664 @@ plugins: + vcloud: executor: central_deployment_agent - source: https://github.com/cloudify-cosmo/tosca-vcloud-plugin/archive/master.zip + source: https://github.com/cloudify-cosmo/tosca-vcloud-plugin/archive/1.6.1.zip + package_name: tosca-vcloud-plugin + package_version: '1.6.1' + +data_types: + + cloudify.datatypes.vlcoud.config: + properties: + username: + type: string + required: false + description: > + The vCloud account username. + password: + type: string + required: false + description: > + The vCloud account password. + url: + type: string + required: false + description: > + The vCloud URL. + org: + type: string + required: false + description: > + The organization name. Required only for the ondemand and + subscription service types. + instance: + type: string + required: false + description: > + The instance UUID. Required only for the ondemand service type. + vdc: + type: string + required: false + description: > + The virtual datacenter name. + service: + type: string + required: false + description: > + The vCloud service name. + service_type: + type: string + default: subscription + description: > + The service type. Can be subscription, ondemand, vcd or private. + Private is an alias for vcd and both types can be used with a + private vCloud environment without any difference. Defaults to subscription. + api_version: + type: string + default: 5.7 + description: > + The vCloud API version. For Subscription, defaults to 5.6. For + OnDemand, defaults to 5.7. + region: + type: string + required: false + description: > + The region name. Applies to OnDemand. + org_url: + type: string + required: false + description: > + The organization URL. Required only for private service type. + edge_gateway: + type: string + required: false + description: > + The Edge gateway name. + ssl_verify: + type: boolean + default: true + description: > + A boolean flag for disabling the SSL certificate check. + Only applicable for a private cloud service with self-signed + certificates. Defaults to True + + cloudify.datatypes.vlcoud.server_key: + properties: + key: + type: string + required: false + description: > + The public SSH key. + user: + type: string + required: false + description: > + The user name. + + cloudify.datatypes.vlcoud.server_customization: + properties: + public_keys: + type: string + required: false + description: > + The public keys to be injected. A list of key-value configurations. + computer_name: + type: string + required: false + description: > + The VM hostname. + admin_password: + type: string + required: false + description: > + The root password. + pre_script: + type: string + required: false + description: > + A pre-customization script. + post_script: + type: string + required: false + description: > + A pPost-customization script. + script_executor: + type: string + default: /bin/bash + description: > + The script executor. The default is /bin/bash. + + cloudify.datatypes.vlcoud.server_hardware: + properties: + cpu: + type: integer + required: false + description: > + The VM CPU count. + memory: + type: integer + required: false + description: > + The VM memory size, in MB. + + cloudify.datatypes.vlcoud.server: + properties: + name: + type: string + required: false + description: > + The server name. + template: + type: string + required: false + description: > + The vApp template from which the server is spawned. + catalog: + type: string + required: false + description: > + The vApp templates catalog. + guest_customization: + type: cloudify.datatypes.vlcoud.server_customization + required: false + description: > + The guest customization section + hardware: + type: cloudify.datatypes.vlcoud.server_hardware + required: false + description: > + The key-value hardware customization section, including: + + cloudify.datatypes.vlcoud.network_dhcp: + properties: + dhcp_range: + type: string + required: false + description: > + The DHCP pool range. + default_lease: + type: string + required: false + description: > + The default lease in seconds. + max_lease: + type: string + required: false + description: > + The maximum lease, in seconds. + + cloudify.datatypes.vlcoud.network: + properties: + edge_gateway: + type: string + required: false + description: > + The Edge gateway name. + name: + type: string + required: false + description: > + The network name. + static_range: + type: string + required: false + description: > + The static IP allocation pool range. + netmask: + type: string + required: false + description: > + The network netmask. + gateway_ip: + type: string + required: false + description: > + The network gateway. + dns: + required: false + description: > + The list of DNS IP addresses. + dns_suffix: + type: string + required: false + description: > + The DNS suffix. + dhcp: + type: cloudify.datatypes.vlcoud.network_dhcp + description: > + The DHCP settings. + + cloudify.datatypes.vlcoud.port: + properties: + network: + type: string + required: false + description: > + The network name. + ip_allocation_mode: + type: string + required: false + description: > + The IP allocation mode. Can be dhcp, pool or manual. + ip_address: + type: string + required: false + description: > + The IP address if the IP allocation mode is manual. + mac_address: + type: string + required: false + description: > + The interface MAC address. + primary_interface: + type: boolean + default: false + description: > + Specifies whether the interface is the primary interface (true or + false). + + cloudify.datatypes.vlcoud.floatingip: + properties: + edge_gateway: + type: string + required: false + description: > + The vCloud gateway name. + public_ip: + type: string + required: false + description: > + The public IP address. If not specified, the public IP is allocated + from the pool of free public IPs. + + cloudify.datatypes.vlcoud.nat: + properties: + edge_gateway: + type: string + required: false + description: > + The vCloud gateway name. + public_ip: + type: string + required: false + description: > + The public IP. If not specified, the public IP is allocated from + the pool of free public IPs. + + cloudify.datatypes.vlcoud.security_group: + properties: + edge_gateway: + type: string + required: false + description: > + The vCloud gateway name. + + cloudify.datatypes.vlcoud.volume: + properties: + name: + type: string + required: false + description: > + Volume name + size: + type: integer + required: false + description: > + Volume size + + cloudify.datatypes.vlcoud.private_key: + properties: + create_file: + type: string + required: false + description: > + Whether to save the file. Use with auto_generate: true. + + cloudify.datatypes.vlcoud.public_key: + properties: + key: + type: string + required: false + description: > + The SSH public key. + user: + type: string + required: false + description: > + The user name. node_types: + cloudify.vcloud.nodes.Server: derived_from: cloudify.nodes.Compute properties: use_external_resource: + type: boolean default: false + description: > + Use predefined resource resource_id: - default: '' + default: false + description: > + Resource Id server: - default: {} + type: cloudify.datatypes.vlcoud.server management_network: - default: '' + type: string + default: "" + description: > + The management network name. vcloud_config: - default: {} + type: cloudify.datatypes.vlcoud.config interfaces: cloudify.interfaces.lifecycle: create: - implementation: vcloud.server_plugin.server.create + implementation: vcloud.vcloud_server_plugin.server.create + inputs: {} + configure: + implementation: vcloud.vcloud_server_plugin.server.configure inputs: {} start: - implementation: vcloud.server_plugin.server.start + implementation: vcloud.vcloud_server_plugin.server.start inputs: {} stop: - implementation: vcloud.server_plugin.server.stop + implementation: vcloud.vcloud_server_plugin.server.stop inputs: {} delete: - implementation: vcloud.server_plugin.server.delete + implementation: vcloud.vcloud_server_plugin.server.delete inputs: {} cloudify.interfaces.validation: creation: - implementation: vcloud.server_plugin.server.creation_validation + implementation: vcloud.vcloud_server_plugin.server.creation_validation + inputs: {} cloudify.vcloud.nodes.Network: derived_from: cloudify.nodes.Network properties: network: - default: {} + type: cloudify.datatypes.vlcoud.network use_external_resource: + type: boolean default: false + description: > + Use predefined resource resource_id: - default: '' + default: false + description: > + Resource Id vcloud_config: - default: {} + type: cloudify.datatypes.vlcoud.config interfaces: cloudify.interfaces.lifecycle: create: - implementation: vcloud.network_plugin.network.create + implementation: vcloud.vcloud_network_plugin.network.create inputs: {} delete: - implementation: vcloud.network_plugin.network.delete + implementation: vcloud.vcloud_network_plugin.network.delete inputs: {} cloudify.interfaces.validation: creation: - implementation: vcloud.network_plugin.network.creation_validation + implementation: vcloud.vcloud_network_plugin.network.creation_validation + inputs: {} cloudify.vcloud.nodes.Port: derived_from: cloudify.nodes.Port properties: port: - default: {} + type: cloudify.datatypes.vlcoud.port vcloud_config: - default: {} + type: cloudify.datatypes.vlcoud.config interfaces: - cloudify.interfaces.validation: - creation: - implementation: vcloud.network_plugin.port.creation_validation + cloudify.interfaces.lifecycle: + create: + implementation: vcloud.vcloud_network_plugin.port.creation_validation + inputs: + port: + default: { get_property: [SELF, port]} + delete: + implementation: vcloud.vcloud_network_plugin.port.delete + inputs: {} cloudify.vcloud.nodes.FloatingIP: derived_from: cloudify.nodes.VirtualIP properties: floatingip: - default: {} + type: cloudify.datatypes.vlcoud.floatingip vcloud_config: - default: {} + type: cloudify.datatypes.vlcoud.config interfaces: - cloudify.interfaces.validation: - creation: - implementation: vcloud.network_plugin.floatingip.creation_validation + cloudify.interfaces.lifecycle: + create: + implementation: vcloud.vcloud_network_plugin.floatingip.creation_validation + inputs: + floatingip: + default: { get_property: [SELF, floatingip]} cloudify.vcloud.nodes.PublicNAT: derived_from: cloudify.nodes.VirtualIP properties: use_external_resource: + type: boolean default: false + description: > + Use predefined resource nat: - default: {} + type: cloudify.datatypes.vlcoud.nat + description: > + The key-value NAT configuration. rules: default: [] + description: > + The key-value NAT rules configuration: + protocol - The network protocol. Can be tcp, udp or any. + Applies only for DNAT. + original_port - The original port. Applies only for DNAT. + translated_port - The translated port. Applies only for + DNAT. + type - The list of NAT types. Can be SNAT, DNAT or both. vcloud_config: - default: {} + type: cloudify.datatypes.vlcoud.config interfaces: - cloudify.interfaces.validation: - creation: - implementation: vcloud.network_plugin.public_nat.creation_validation + cloudify.interfaces.lifecycle: + create: + implementation: vcloud.vcloud_network_plugin.public_nat.creation_validation + inputs: + use_external_resource: + default: { get_property: [SELF, use_external_resource]} + nat: + default: { get_property: [SELF, nat]} + rules: + default: { get_property: [SELF, rules]} cloudify.vcloud.nodes.SecurityGroup: derived_from: cloudify.nodes.SecurityGroup properties: security_group: - default: {} + type: cloudify.datatypes.vlcoud.security_group + description: > + The key-value SecurityGroup configuration rules: default: [] + description: > + The security group rules. A list of key-value configurations. + protocol - tcp, udp, icmp or any. + source - The source of traffic on which to apply the + firewall. Can be internal, external, host, any, the IP + address or IP range. + source_port - The port number or any. + destination - The destination of traffic on which to apply + the firewall rule. Can be internal, external, host, any, + the IP address or IP range. + destination_port - The port number or any. + action - allow or deny. + log_traffic - Used to capture traffic. true or false. + description - The rule description. vcloud_config: - default: {} + type: cloudify.datatypes.vlcoud.config interfaces: - cloudify.interfaces.validation: - creation: - implementation: vcloud.network_plugin.security_group.creation_validation + cloudify.interfaces.lifecycle: + create: + implementation: vcloud.vcloud_network_plugin.security_group.creation_validation + inputs: + security_group: + default: { get_property: [SELF, security_group]} + rules: + default: { get_property: [SELF, rules]} cloudify.vcloud.nodes.KeyPair: derived_from: cloudify.nodes.Root properties: - private_key_path: - default: '' + auto_generate: + type: boolean + default: false + description: > + Use to auto-generate the key. + private_key: + type: cloudify.datatypes.vlcoud.private_key + description: > + The key-value private key configuration public_key: - default: {} - vcloud_config: - default: {} + type: cloudify.datatypes.vlcoud.public_key + description: > + The key-value public key configuration interfaces: cloudify.interfaces.validation: creation: - implementation: vcloud.network_plugin.keypair.creation_validation - + implementation: vcloud.vcloud_network_plugin.keypair.creation_validation + inputs: {} + cloudify.interfaces.lifecycle: + create: + implementation: vcloud.vcloud_network_plugin.keypair.create + inputs: {} + delete: + implementation: vcloud.vcloud_network_plugin.keypair.delete + inputs: {} cloudify.vcloud.nodes.Volume: derived_from: cloudify.nodes.Volume properties: device_name: + type: string default: '' + description: > + Device name volume: + type: cloudify.datatypes.vlcoud.volume + description: > + The key-value volume configuration default: {} use_external_resource: + type: boolean default: false + description: > + Use predefined resource resource_id: + default: false + description: > + Resource Id + vcloud_config: + type: cloudify.datatypes.vlcoud.config + interfaces: + cloudify.interfaces.lifecycle: + create: + implementation: vcloud.vcloud_storage_plugin.volume.create_volume + inputs: {} + delete: + implementation: vcloud.vcloud_storage_plugin.volume.delete_volume + inputs: {} + cloudify.interfaces.validation: + creation: + implementation: vcloud.vcloud_storage_plugin.volume.creation_validation + inputs: {} + + cloudify.vcloud.nodes.VDC: + derived_from: cloudify.nodes.Root + properties: + name: + type: string default: '' + description: > + VDC name + use_external_resource: + type: boolean + default: false + description: > + Use predefined resource + resource_id: + default: false + description: > + Resource Id vcloud_config: - default: {} + type: cloudify.datatypes.vlcoud.config interfaces: cloudify.interfaces.lifecycle: create: - implementation: vcloud.server_plugin.volume.create_volume + implementation: vcloud.vcloud_server_plugin.vdc.create inputs: {} delete: - implementation: vcloud.server_plugin.volume.delete_volume + implementation: vcloud.vcloud_server_plugin.vdc.delete inputs: {} cloudify.interfaces.validation: creation: - implementation: vcloud.server_plugin.volume.creation_validation + implementation: vcloud.vcloud_server_plugin.vdc.creation_validation + inputs: {} relationships: + cloudify.vcloud.server_connected_to_floating_ip: derived_from: cloudify.relationships.connected_to target_interfaces: cloudify.interfaces.relationship_lifecycle: - establish: - implementation: vcloud.network_plugin.floatingip.connect_floatingip + postconfigure: + implementation: vcloud.vcloud_network_plugin.floatingip.connect_floatingip inputs: {} unlink: - implementation: vcloud.network_plugin.floatingip.disconnect_floatingip + implementation: vcloud.vcloud_network_plugin.floatingip.disconnect_floatingip inputs: {} + cloudify.vcloud.server_connected_to_network: derived_from: cloudify.relationships.connected_to + cloudify.vcloud.port_connected_to_network: derived_from: cloudify.relationships.connected_to + cloudify.vcloud.server_connected_to_port: derived_from: cloudify.relationships.connected_to + cloudify.vcloud.server_connected_to_security_group: derived_from: cloudify.relationships.connected_to target_interfaces: cloudify.interfaces.relationship_lifecycle: - establish: - implementation: vcloud.network_plugin.security_group.create + postconfigure: + implementation: vcloud.vcloud_network_plugin.security_group.create inputs: {} unlink: - implementation: vcloud.network_plugin.security_group.delete + implementation: vcloud.vcloud_network_plugin.security_group.delete inputs: {} + cloudify.vcloud.net_connected_to_public_nat: derived_from: cloudify.relationships.connected_to target_interfaces: cloudify.interfaces.relationship_lifecycle: - establish: - implementation: vcloud.network_plugin.public_nat.net_connect_to_nat + preconfigure: + implementation: vcloud.vcloud_network_plugin.public_nat.net_connect_to_nat_preconfigure + inputs: {} + postconfigure: + implementation: vcloud.vcloud_network_plugin.public_nat.net_connect_to_nat inputs: {} unlink: - implementation: vcloud.network_plugin.public_nat.net_disconnect_from_nat + implementation: vcloud.vcloud_network_plugin.public_nat.net_disconnect_from_nat inputs: {} + cloudify.vcloud.server_connected_to_public_nat: derived_from: cloudify.relationships.connected_to target_interfaces: cloudify.interfaces.relationship_lifecycle: - establish: - implementation: vcloud.network_plugin.public_nat.server_connect_to_nat + postconfigure: + implementation: vcloud.vcloud_network_plugin.public_nat.server_connect_to_nat inputs: {} unlink: - implementation: vcloud.network_plugin.public_nat.server_disconnect_from_nat + implementation: vcloud.vcloud_network_plugin.public_nat.server_disconnect_from_nat inputs: {} cloudify.vcloud.volume_attached_to_server: @@ -205,8 +666,30 @@ relationships: target_interfaces: cloudify.interfaces.relationship_lifecycle: establish: - implementation: vcloud.server_plugin.volume.attach_volume + implementation: vcloud.vcloud_storage_plugin.volume.attach_volume + inputs: {} + unlink: + implementation: vcloud.vcloud_storage_plugin.volume.detach_volume + inputs: {} + + cloudify.vcloud.server_connected_to_keypair: + derived_from: cloudify.relationships.connected_to + target_interfaces: + cloudify.interfaces.relationship_lifecycle: + postconfigure: + implementation: vcloud.vcloud_network_plugin.keypair.server_connect_to_keypair inputs: {} unlink: - implementation: vcloud.server_plugin.volume.detach_volume + implementation: vcloud.vcloud_network_plugin.keypair.server_disconnect_from_keypair + inputs: {} + + cloudify.vcloud.server_connected_to_vdc: + derived_from: cloudify.relationships.connected_to + + cloudify.vcloud.delete_public_key_from_server: + derived_from: cloudify.relationships.connected_to + target_interfaces: + cloudify.interfaces.relationship_lifecycle: + establish: + implementation: vcloud.vcloud_server_plugin.server.remove_keys inputs: {} diff --git a/server_plugin/server.py b/server_plugin/server.py deleted file mode 100644 index 24ba9a0..0000000 --- a/server_plugin/server.py +++ /dev/null @@ -1,582 +0,0 @@ -# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# * See the License for the specific language governing permissions and -# * limitations under the License. - -from cloudify import ctx -from cloudify.decorators import operation -from cloudify import exceptions as cfy_exc - -from vcloud_plugin_common import (get_vcloud_config, - transform_resource_name, - wait_for_task, - with_vca_client, - STATUS_POWERED_ON) - -from network_plugin import (get_network_name, get_network, is_network_exists, - get_vapp_name) - -VCLOUD_VAPP_NAME = 'vcloud_vapp_name' -GUEST_CUSTOMIZATION = 'guest_customization' -HARDWARE = 'hardware' -DEFAULT_EXECUTOR = "/bin/bash" -DEFAULT_USER = "ubuntu" -DEFAULT_HOME = "/home" - - -@operation -@with_vca_client -def creation_validation(vca_client, **kwargs): - """ - validate server settings, look to template in catalog - """ - def get_catalog(catalog_name): - catalogs = vca_client.get_catalogs() - for catalog in catalogs: - if catalog.get_name() == catalog_name: - return catalog - - def get_template(catalog, template_name): - for template in catalog.get_CatalogItems().get_CatalogItem(): - if template.get_name() == template_name: - return template - - if ctx.node.properties.get('use_external_resource'): - if not ctx.node.properties.get('resource_id'): - raise cfy_exc.NonRecoverableError( - "resource_id server properties must be specified" - ) - return - - server_dict = ctx.node.properties['server'] - required_params = ('catalog', 'template') - missed_params = set(required_params) - set(server_dict.keys()) - if len(missed_params) > 0: - raise cfy_exc.NonRecoverableError( - "{0} server properties must be specified" - .format(list(missed_params))) - - catalog = get_catalog(server_dict['catalog']) - if catalog is None: - raise cfy_exc.NonRecoverableError( - "Catalog {0} could not be found".format(server_dict['catalog'])) - - template = get_template(catalog, server_dict['template']) - if template is None: - raise cfy_exc.NonRecoverableError( - "Template {0} could not be found".format(server_dict['template'])) - - -@operation -@with_vca_client -def create(vca_client, **kwargs): - """ - create server by template, - if external_resource set return without creation, - e.g.: - { - 'management_network': '_management_network', - 'server': { - 'template': 'template', - 'catalog': 'catalog', - 'guest_customization': { - 'pre_script': 'pre_script', - 'post_script': 'post_script', - 'public_keys': [{ - 'key': True - }] - } - } - } - """ - config = get_vcloud_config() - server = { - 'name': ctx.instance.id, - } - server.update(ctx.node.properties.get('server', {})) - transform_resource_name(server, ctx) - - if ctx.node.properties.get('use_external_resource'): - res_id = ctx.node.properties['resource_id'] - ctx.instance.runtime_properties[VCLOUD_VAPP_NAME] = res_id - ctx.logger.info( - "External resource {0} has been used".format(res_id)) - else: - _create(vca_client, config, server) - - -def _create(vca_client, config, server): - """ - create server by template, - customize: - * hardware: memmory/cpu - * software: root password, computer internal hostname - connect vm to network - """ - vapp_name = server['name'] - vapp_template = server['template'] - vapp_catalog = server['catalog'] - ctx.logger.info("Creating VApp with parameters: {0}".format(server)) - task = vca_client.create_vapp(config['vdc'], - vapp_name, - vapp_template, - vapp_catalog, - vm_name=vapp_name) - if not task: - raise cfy_exc.NonRecoverableError("Could not create vApp: {0}" - .format(vca_client.response.content)) - wait_for_task(vca_client, task) - - hardware = server.get('hardware') - if hardware: - cpu = hardware.get('cpu') - memory = hardware.get('memory') - _check_hardware(cpu, memory) - vapp = vca_client.get_vapp( - vca_client.get_vdc(config['vdc']), vapp_name - ) - if memory: - task = vapp.modify_vm_memory(vapp_name, memory) - if task: - wait_for_task(vca_client, task) - ctx.logger.info("Customize VM memory: {0}.".format(memory)) - else: - raise cfy_exc.NonRecoverableError( - "Customize VM memory failed: {0}.".format(task) - ) - if cpu: - task = vapp.modify_vm_cpu(vapp_name, cpu) - if task: - wait_for_task(vca_client, task) - ctx.logger.info("Customize VM cpu: {0}.".format(cpu)) - else: - raise cfy_exc.NonRecoverableError( - "Customize VM cpu failed: {0}.".format(task) - ) - - ctx.instance.runtime_properties[VCLOUD_VAPP_NAME] = vapp_name - connections = _create_connections_list(vca_client) - - # we allways have connection to management_network_name - if connections: - for index, connection in enumerate(connections): - vdc = vca_client.get_vdc(config['vdc']) - vapp = vca_client.get_vapp(vdc, vapp_name) - if vapp is None: - raise cfy_exc.NonRecoverableError( - "vApp {0} could not be found".format(vapp_name)) - - network_name = connection.get('network') - network = get_network(vca_client, network_name) - - task = vapp.connect_to_network(network_name, network.get_href()) - if not task: - raise cfy_exc.NonRecoverableError( - "Could not add network {0} to VApp {1}" - .format(network_name, vapp_name)) - wait_for_task(vca_client, task) - - connections_primary_index = None - if connection.get('primary_interface'): - connections_primary_index = index - ip_address = connection.get('ip_address') - mac_address = connection.get('mac_address') - ip_allocation_mode = connection.get('ip_allocation_mode', - 'POOL').upper() - connection_args = { - 'network_name': network_name, - 'connection_index': index, - 'connections_primary_index': connections_primary_index, - 'ip_allocation_mode': ip_allocation_mode, - 'mac_address': mac_address, - 'ip_address': ip_address - } - ctx.logger.info("Connecting network with parameters {0}" - .format(str(connection_args))) - task = vapp.connect_vms(**connection_args) - if task is None: - raise cfy_exc.NonRecoverableError( - "Could not connect vApp {0} to network {1}" - .format(vapp_name, network_name)) - wait_for_task(vca_client, task) - - # customize root password and hostname - custom = server.get(GUEST_CUSTOMIZATION) - if custom: - vdc = vca_client.get_vdc(config['vdc']) - vapp = vca_client.get_vapp(vdc, vapp_name) - script = _build_script(custom) - password = custom.get('admin_password') - computer_name = custom.get('computer_name') - - task = vapp.customize_guest_os( - vapp_name, - customization_script=script, - computer_name=computer_name, - admin_password=password - ) - if task is None: - raise cfy_exc.NonRecoverableError( - "Could not set guest customization parameters") - wait_for_task(vca_client, task) - # This function avialable from API version 5.6 - if vapp.customize_on_next_poweron(): - ctx.logger.info("Customizations successful") - else: - raise cfy_exc.NonRecoverableError( - "Can't run customization in next power on") - - -@operation -@with_vca_client -def start(vca_client, **kwargs): - """ - power on server and wait network connection availability for host - """ - if ctx.node.properties.get('use_external_resource'): - ctx.logger.info('not starting server since an external server is ' - 'being used') - else: - vapp_name = get_vapp_name(ctx.instance.runtime_properties) - config = get_vcloud_config() - vdc = vca_client.get_vdc(config['vdc']) - vapp = vca_client.get_vapp(vdc, vapp_name) - if _vapp_is_on(vapp) is False: - ctx.logger.info("Power-on VApp {0}".format(vapp_name)) - task = vapp.poweron() - if not task: - raise cfy_exc.NonRecoverableError("Could not power-on vApp") - wait_for_task(vca_client, task) - - if not _get_state(vca_client): - return ctx.operation.retry( - message="Waiting for VM's configuration to complete", - retry_after=5) - - -@operation -@with_vca_client -def stop(vca_client, **kwargs): - """ - poweroff server, if external resource - server stay poweroned - """ - if ctx.node.properties.get('use_external_resource'): - ctx.logger.info('not stopping server since an external server is ' - 'being used') - else: - vapp_name = get_vapp_name(ctx.instance.runtime_properties) - config = get_vcloud_config() - vdc = vca_client.get_vdc(config['vdc']) - vapp = vca_client.get_vapp(vdc, vapp_name) - ctx.logger.info("Power-off and undeploy VApp {0}".format(vapp_name)) - task = vapp.undeploy() - if not task: - raise cfy_exc.NonRecoverableError("Could not undeploy vApp") - wait_for_task(vca_client, task) - - -@operation -@with_vca_client -def delete(vca_client, **kwargs): - """ - delete server - """ - if ctx.node.properties.get('use_external_resource'): - ctx.logger.info('not deleting server since an external server is ' - 'being used') - else: - vapp_name = get_vapp_name(ctx.instance.runtime_properties) - config = get_vcloud_config() - vdc = vca_client.get_vdc(config['vdc']) - vapp = vca_client.get_vapp(vdc, vapp_name) - ctx.logger.info("Deleting VApp {0}".format(vapp_name)) - task = vapp.delete() - if not task: - raise cfy_exc.NonRecoverableError("Could not delete vApp") - wait_for_task(vca_client, task) - - del ctx.instance.runtime_properties[VCLOUD_VAPP_NAME] - - -def _get_management_network_from_node(): - """ - get management network name from: - * node properties or - * provider context (bootstrap context) - """ - management_network_name = ctx.node.properties.get('management_network') - if not management_network_name: - resources = ctx.provider_context.get('resources') - if resources and 'int_network' in resources: - management_network_name = resources['int_network'].get('name') - if not management_network_name: - raise cfy_exc.NonRecoverableError( - "Parameter 'managment_network' for Server node is not defined.") - return management_network_name - - -def _get_state(vca_client): - """ - check network connection availability for host - """ - vapp_name = get_vapp_name(ctx.instance.runtime_properties) - config = get_vcloud_config() - vdc = vca_client.get_vdc(config['vdc']) - vapp = vca_client.get_vapp(vdc, vapp_name) - nw_connections = _get_vm_network_connections(vapp) - if len(nw_connections) == 0: - ctx.logger.info("No networks connected") - ctx.instance.runtime_properties['ip'] = None - ctx.instance.runtime_properties['networks'] = {} - return True - management_network_name = _get_management_network_from_node() - - if not all([connection['ip'] for connection in nw_connections]): - ctx.logger.info("Network configuration is not finished yet.") - return False - - ctx.instance.runtime_properties['networks'] = { - connection['network_name']: connection['ip'] - for connection in nw_connections} - - for connection in nw_connections: - if connection['network_name'] == management_network_name: - ctx.logger.info("Management network ip address {0}" - .format(connection['ip'])) - ctx.instance.runtime_properties['ip'] = connection['ip'] - return True - return False - - -def _vapp_is_on(vapp): - """ - server is on - """ - return vapp.me.get_status() == STATUS_POWERED_ON - - -def _get_vm_network_connections(vapp): - """ - get list connected networlks - """ - connections = vapp.get_vms_network_info()[0] - return filter(lambda network: network['is_connected'], connections) - - -def _get_vm_network_connection(vapp, network_name): - """ - return network connection by name - """ - connections = _get_vm_network_connections(vapp) - for connection in connections: - if connection['network_name'] == network_name: - return connection - - -def _build_script(custom): - """ - create customization script - """ - pre_script = custom.get('pre_script', "") - post_script = custom.get('post_script', "") - public_keys = custom.get('public_keys') - if not pre_script and not post_script and not public_keys: - return None - script_executor = custom.get('script_executor', DEFAULT_EXECUTOR) - public_keys_script = _build_public_keys_script(public_keys) - script_template = """#!{0} -echo performing customization tasks with param $1 \ -at `date "+DATE: %Y-%m-%d - TIME: %H:%M:%S"` >> /root/customization.log -if [ "$1" = "precustomization" ]; -then - echo performing precustomization tasks \ - on `date "+DATE: %Y-%m-%d - TIME: %H:%M:%S"` >> /root/customization.log - {1} - {2} -fi -if [ "$1" = "postcustomization" ]; -then - echo performing postcustomization tasks \ - at `date "+DATE: %Y-%m-%d - TIME: %H:%M:%S"` >> /root/customization.log - {3} -fi - """ - script = script_template.format(script_executor, public_keys_script, - pre_script, post_script) - return script - - -def _build_public_keys_script(public_keys): - """ - create script for update ssh keys - """ - key_commands = [] - ssh_dir_template = "{0}/{1}/.ssh" - authorized_keys_template = "{0}/authorized_keys" - add_key_template = "echo '{0}\n' >> {1}" - test_ssh_dir_template = """ - if [ ! -d {1} ];then - mkdir {1} - chown {0}:{0} {1} - chmod 700 {1} - touch {2} - chown {0}:{0} {2} - chmod 600 {2} - fi - """ - for key in public_keys: - public_key = key.get('key') - if not public_key: - continue - user = key.get('user', DEFAULT_USER) - home = key.get('home', DEFAULT_HOME) - ssh_dir = ssh_dir_template.format(home, user) - authorized_keys = authorized_keys_template.format(ssh_dir) - test_ssh_dir = test_ssh_dir_template.format( - user, ssh_dir, authorized_keys) - key_commands.append(test_ssh_dir) - key_commands.append( - add_key_template.format(public_key, authorized_keys)) - return "\n".join(key_commands) - - -def _create_connections_list(vca_client): - """ - return full list connections for node - """ - connections = [] - ports = _get_connected(ctx.instance, 'port') - networks = _get_connected(ctx.instance, 'network') - - management_network_name = _get_management_network_from_node() - - if not is_network_exists(vca_client, management_network_name): - raise cfy_exc.NonRecoverableError( - "Network {0} could not be found".format(management_network_name)) - - # connection by port - for port in ports: - port_properties = port.node.properties['port'] - connections.append( - _create_connection(port_properties['network'], - port_properties.get('ip_address'), - port_properties.get('mac_address'), - port_properties.get('ip_allocation_mode', - 'POOL').upper(), - port_properties.get('primary_interface', False)) - ) - - # connection by networks - for net in networks: - connections.append( - _create_connection(get_network_name(net.node.properties), - None, None, 'POOL')) - - # add managmenty network if not exist in list - if not any([conn['network'] == management_network_name - for conn in connections]): - connections.append(_create_connection(management_network_name, - None, None, 'POOL')) - - primary_iface_set = len(filter(lambda conn: conn.get('primary_interface', - False), - connections)) > 0 - - # check list of connections and set managment network as primary - # in case when we dont have any primary networks - for conn in connections: - network_name = conn['network'] - if (conn['ip_allocation_mode'] == 'DHCP' - and not _isDhcpAvailable(vca_client, network_name)): - raise cfy_exc.NonRecoverableError( - "DHCP for network {0} is not available" - .format(network_name)) - - if primary_iface_set is False: - conn['primary_interface'] = \ - (network_name == management_network_name) - - return connections - - -def _get_connected(instance, prop): - """ - get property from instance relationships - """ - relationships = getattr(instance, 'relationships', None) - if relationships: - return [relationship.target for relationship in relationships - if prop in relationship.target.node.properties] - else: - return [] - - -def _create_connection(network, ip_address, mac_address, ip_allocation_mode, - primary_interface=False): - """ - repack fields to dict - """ - return {'network': network, - 'ip_address': ip_address, - 'mac_address': mac_address, - 'ip_allocation_mode': ip_allocation_mode, - 'primary_interface': primary_interface} - - -def _isDhcpAvailable(vca_client, network_name): - """ - check dhcp availability for network - """ - vdc_name = get_vcloud_config()['vdc'] - network = vca_client.get_network(vdc_name, network_name) - if network.get_Configuration().get_FenceMode() == "bridged": - # NOTE(nmishkin) Can't tell whether bridged networks have DHCP - # so just hope for the best - return True - # TODO: Why not just get the gateway directly from the network? - admin_href = vca_client.get_admin_network_href(vdc_name, network_name) - for gate in vca_client.get_gateways(vdc_name): - for pool in gate.get_dhcp_pools(): - if admin_href == pool.get_Network().get_href(): - return True - return False - - -def _check_hardware(cpu, memory): - """ - check hardware setting - 1 <= cpu <= 64 - 512M <= memmory <= 512G - """ - if cpu is not None: - if isinstance(cpu, int): - if cpu < 1: - raise cfy_exc.NonRecoverableError( - "Too small quantity of CPU's: {0}".format(cpu)) - if cpu > 64: - raise cfy_exc.NonRecoverableError( - "Too many of CPU's: {0}".format(cpu)) - else: - raise cfy_exc.NonRecoverableError( - "Quantity of CPU's must be integer") - - if memory is not None: - if isinstance(memory, int): - if memory < 512: - raise cfy_exc.NonRecoverableError( - "Too small quantity of memory: {0}".format(memory)) - if memory > (512 * 1024): # 512Gb - raise cfy_exc.NonRecoverableError( - "Too many memory: {0}".format(memory)) - else: - raise cfy_exc.NonRecoverableError( - "Quantity of memory must be integer") diff --git a/server_plugin/volume.py b/server_plugin/volume.py deleted file mode 100644 index 8e1fb1b..0000000 --- a/server_plugin/volume.py +++ /dev/null @@ -1,149 +0,0 @@ -# Copyright (c) 2015 GigaSpaces Technologies Ltd. All rights reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# * See the License for the specific language governing permissions and -# * limitations under the License. - -from cloudify import ctx -from cloudify import exceptions as cfy_exc -from cloudify.decorators import operation -from vcloud_plugin_common import (wait_for_task, with_vca_client, - get_vcloud_config, get_mandatory) -from network_plugin import get_vapp_name - - -@operation -@with_vca_client -def create_volume(vca_client, **kwargs): - """ - create new volume, e.g.: - { - 'use_external_resource': False, - 'volume': { - 'name': 'some-other', - 'size': 11 - } - } - """ - if ctx.node.properties.get('use_external_resource'): - ctx.logger.info("External resource has been used") - return - vdc_name = get_vcloud_config()['vdc'] - name = ctx.node.properties['volume']['name'] - size = ctx.node.properties['volume']['size'] - size_in_Mb = size * 1024 * 1024 - success, disk = vca_client.add_disk(vdc_name, name, size_in_Mb) - if success: - wait_for_task(vca_client, disk.get_Tasks()[0]) - ctx.logger.info("Volume node {} has been created".format(name)) - else: - raise cfy_exc.NonRecoverableError( - "Disk creation error: {0}".format(disk)) - - -@operation -@with_vca_client -def delete_volume(vca_client, **kwargs): - """ - drop volume - """ - if ctx.node.properties.get('use_external_resource'): - ctx.logger.info("External resource has been used") - return - vdc_name = get_vcloud_config()['vdc'] - name = ctx.node.properties['volume']['name'] - success, task = vca_client.delete_disk(vdc_name, name) - if success: - wait_for_task(vca_client, task) - ctx.logger.info("Volume node {} has been deleted".format(name)) - else: - raise cfy_exc.NonRecoverableError( - "Disk deletion error: {0}".format(task)) - - -@operation -@with_vca_client -def creation_validation(vca_client, **kwargs): - """ - check volume description - """ - vdc_name = get_vcloud_config()['vdc'] - disks_names = [ - disk.name for [disk, _vms] in vca_client.get_disks(vdc_name) - ] - if ctx.node.properties.get('use_external_resource'): - resource_id = get_mandatory(ctx.node.properties, 'resource_id') - if resource_id not in disks_names: - raise cfy_exc.NonRecoverableError( - "Disk {} does't exists".format(resource_id)) - else: - volume = get_mandatory(ctx.node.properties, 'volume') - name = get_mandatory(volume, 'name') - if name in disks_names: - raise cfy_exc.NonRecoverableError( - "Disk {} already exists".format(name)) - get_mandatory(volume, 'size') - - -@operation -@with_vca_client -def attach_volume(vca_client, **kwargs): - """ - attach volume - """ - _volume_operation(vca_client, "ATTACH") - - -@operation -@with_vca_client -def detach_volume(vca_client, **kwargs): - """ - detach volume - """ - _volume_operation(vca_client, "DETACH") - - -def _volume_operation(vca_client, operation): - """ - attach/detach volume - """ - vdc_name = get_vcloud_config()['vdc'] - vdc = vca_client.get_vdc(vdc_name) - vmName = get_vapp_name(ctx.target.instance.runtime_properties) - if ctx.source.node.properties.get('use_external_resource'): - volumeName = ctx.source.node.properties['resource_id'] - else: - volumeName = ctx.source.node.properties['volume']['name'] - vapp = vca_client.get_vapp(vdc, vmName) - for ref in vca_client.get_diskRefs(vdc): - if ref.name == volumeName: - if operation == 'ATTACH': - task = vapp.attach_disk_to_vm(vmName, ref) - if task: - wait_for_task(vca_client, task) - ctx.logger.info( - "Volume node {} has been attached".format(volumeName)) - else: - raise cfy_exc.NonRecoverableError( - "Can't attach disk: {0}".format(volumeName)) - - elif operation == 'DETACH': - task = vapp.detach_disk_from_vm(vmName, ref) - if task: - wait_for_task(vca_client, task) - ctx.logger.info( - "Volume node {} has been detached".format(volumeName)) - else: - raise cfy_exc.NonRecoverableError( - "Can't detach disk: {0}".format(volumeName)) - else: - raise cfy_exc.NonRecoverableError( - "Unknown operation {0}".format(operation)) diff --git a/setup.py b/setup.py index ee2188d..537b5b2 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,4 @@ -######### -# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved +# Copyright (c) 2015-2020 Cloudify Platform Ltd. All rights reserved # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -9,28 +8,30 @@ # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, -# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# * See the License for the specific language governing permissions and -# * limitations under the License. - +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. from setuptools import setup setup( zip_safe=True, - name='cloudify-vcloud-plugin', - version='1.2', + name='tosca-vcloud-plugin', + version='1.6.1', packages=[ 'vcloud_plugin_common', - 'server_plugin', - 'network_plugin' + 'vcloud_server_plugin', + 'vcloud_storage_plugin', + 'vcloud_network_plugin' ], license='LICENSE', description='Cloudify plugin for vmWare vCloud infrastructure.', install_requires=[ - 'cloudify-plugins-common>=3.2', - 'pyvcloud==13rc10', - 'requests==2.4', - 'IPy==0.81', - 'PyYAML==3.10' + 'cloudify-common>=4.5.0', + 'pyvcloud==18.2.2', + 'IPy==1.00', + 'pycrypto==2.6.1', + # used in volume creation + 'paramiko>=1.18.3', + 'fabric>=1.13.1,<2.0', # 2+ branch has API changes ] ) diff --git a/system_tests/__init__.py b/system_tests/__init__.py index 1da0114..af82fdb 100644 --- a/system_tests/__init__.py +++ b/system_tests/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2015 GigaSpaces Technologies Ltd. All rights reserved +# Copyright (c) 2015-2020 Cloudify Platform Ltd. All rights reserved # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -8,9 +8,9 @@ # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, -# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# * See the License for the specific language governing permissions and -# * limitations under the License. +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. from pkgutil import extend_path diff --git a/system_tests/manager/__init__.py b/system_tests/manager/__init__.py new file mode 100644 index 0000000..8fac388 --- /dev/null +++ b/system_tests/manager/__init__.py @@ -0,0 +1,23 @@ +# Copyright (c) 2015-2020 Cloudify Platform Ltd. All rights reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from cosmo_tester.framework.testenv import bootstrap, teardown + + +def setUp(): + bootstrap() + + +def tearDown(): + teardown() diff --git a/system_tests/manager/nodecellar_test.py b/system_tests/manager/nodecellar_test.py new file mode 100644 index 0000000..5f7e060 --- /dev/null +++ b/system_tests/manager/nodecellar_test.py @@ -0,0 +1,38 @@ +# Copyright (c) 2015-2020 Cloudify Platform Ltd. All rights reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from cosmo_tester.test_suites.test_blueprints import nodecellar_test + + +class VcloudNodeCellarTest(nodecellar_test.NodecellarAppTest): + + def test_vcloud_nodecellar(self): + self._test_nodecellar_impl('vcloud-blueprint.yaml') + + def get_inputs(self): + return { + 'catalog': self.env.public_catalog, + 'template': self.env.ubuntu_precise_template, + 'edge_gateway': self.env.edge_gateway, + 'management_network_name': self.env.management_network_name, + 'agent_public_key': self.env.agent_public_key + } + + @property + def entrypoint_property_name(self): + return 'public_ip' + + @property + def expected_nodes_count(self): + return 9 diff --git a/system_tests/vcloud_handler.py b/system_tests/vcloud_handler.py index 22882c5..e8fea7a 100644 --- a/system_tests/vcloud_handler.py +++ b/system_tests/vcloud_handler.py @@ -1,4 +1,4 @@ -# Copyright (c) 2015 GigaSpaces Technologies Ltd. All rights reserved +# Copyright (c) 2015-2020 Cloudify Platform Ltd. All rights reserved # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -8,13 +8,20 @@ # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, -# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# * See the License for the specific language governing permissions and -# * limitations under the License. +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. from cosmo_tester.framework.handlers import ( BaseHandler, BaseCloudifyInputsConfigReader) +from pyvcloud.schema.vcd.v1_5.schemas.vcloud import taskType +from pyvcloud import vcloudair +import time +import requests + + +TEST_VDC = "systest" class VcloudCleanupContext(BaseHandler.CleanupContext): @@ -22,8 +29,13 @@ class VcloudCleanupContext(BaseHandler.CleanupContext): def __init__(self, context_name, env): super(VcloudCleanupContext, self).__init__(context_name, env) - def cleanup(self): - super(VcloudCleanupContext, self).cleanup() + @classmethod + def clean_all(cls, env): + """ + Cleans *all* resources, including resources that were not + created by the test + """ + super(VcloudCleanupContext, cls).clean_all(env) class CloudifyVcloudInputsConfigReader(BaseCloudifyInputsConfigReader): @@ -59,15 +71,15 @@ def vcloud_vdc(self): @property def manager_server_name(self): - return self.config['manager_server_name'] + return self.config['server_name'] @property def manager_server_catalog(self): - return self.config['manager_server_catalog'] + return self.config['catalog'] @property def manager_server_template(self): - return self.config['manager_server_template'] + return self.config['template'] @property def management_network_use_existing(self): @@ -86,20 +98,20 @@ def floating_ip_public_ip(self): return self.config['floating_ip_public_ip'] @property - def manager_private_key_path(self): - return self.config['manager_private_key_path'] + def ssh_key_filename(self): + return self.config['ssh_key_filename'] @property def agent_private_key_path(self): return self.config['agent_private_key_path'] @property - def manager_public_key(self): - return self.config['manager_public_key'] + def user_public_key(self): + return self.config['user_public_key'] @property def agent_public_key(self): - return self.config['agent_public_key'] + return self.config['user_public_key'] @property def management_port_ip_allocation_mode(self): @@ -113,17 +125,75 @@ def vcloud_service_type(self): def vcloud_region(self): return self.config['vcloud_region'] + @property + def public_catalog(self): + return 'Public Catalog' -class VcloudHandler(BaseHandler): + @property + def ubuntu_precise_template(self): + return 'Ubuntu Server 12.04 LTS (amd64 20150127)' + +class VcloudHandler(BaseHandler): CleanupContext = VcloudCleanupContext CloudifyConfigReader = CloudifyVcloudInputsConfigReader def before_bootstrap(self): super(VcloudHandler, self).before_bootstrap() - - def after_bootstrap(self, provider_context): - super(VcloudHandler, self).after_bootstrap(provider_context) + vca = login(self.env.cloudify_config) + if vca.get_vdc(TEST_VDC): + status, task = vca.delete_vdc(TEST_VDC) + if status: + wait_for_task(vca, task) + else: + raise RuntimeError("Can't delete test VDC") + if vca: + task = vca.create_vdc(TEST_VDC) + wait_for_task(vca, task) + else: + raise RuntimeError("Can't create test VDC") handler = VcloudHandler + + +def login(env): + vca = vcloudair.VCA( + host=env['vcloud_url'], + username=env['vcloud_username'], + service_type=env['vcloud_service_type'], + version="5.7", + verify=False) + logined = (vca.login(env['vcloud_password']) and + vca.login_to_instance(env['vcloud_instance'], env['vcloud_password']) and + vca.login_to_instance(env['vcloud_instance'], None, + vca.vcloud_session.token, vca.vcloud_session.org_url)) + if logined: + return vca + else: + return None + + +def wait_for_task(vca_client, task): + TASK_RECHECK_TIMEOUT = 5 + TASK_STATUS_SUCCESS = 'success' + TASK_STATUS_ERROR = 'error' + + WAIT_TIME_MAX_MINUTES = 30 + MAX_ATTEMPTS = WAIT_TIME_MAX_MINUTES * 60 / TASK_RECHECK_TIMEOUT + status = task.get_status() + for attempt in xrange(MAX_ATTEMPTS): + if status == TASK_STATUS_SUCCESS: + return + if status == TASK_STATUS_ERROR: + error = task.get_Error() + raise RuntimeError( + "Error during task execution: {0}".format(error.get_message())) + time.sleep(TASK_RECHECK_TIMEOUT) + response = requests.get( + task.get_href(), + headers=vca_client.vcloud_session.get_vcloud_headers(), + verify=False) + task = taskType.parseString(response.content, True) + status = task.get_status() + raise RuntimeError("Wait for task timeout.") diff --git a/test-requirements.txt b/test-requirements.txt index 9bf68a2..f1196e7 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -5,4 +5,6 @@ nose>=1.3 tox>=1.9 coverage pyflakes -flake8 \ No newline at end of file +flake8 +fabric==1.14.1 +pylint diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py index c3fcb00..46d0971 100644 --- a/tests/integration/__init__.py +++ b/tests/integration/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved +# Copyright (c) 2014-2020 Cloudify Platform Ltd. All rights reserved # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -8,21 +8,34 @@ # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, -# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# * See the License for the specific language governing permissions and -# * limitations under the License. +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. from testconfig import config import mock -import time import unittest - +import shutil +import os +import yaml +import tempfile +import requests +import time +from functools import wraps +from pyvcloud.schema.vcd.v1_5.schemas.vcloud import taskType from cloudify import mocks as cfy_mocks -from cloudify.exceptions import OperationRetry from vcloud_plugin_common import Config, VcloudAirClient - +import cloudify_cli.commands.local as local_command +import cloudify_cli.logger as logger +from cloudify import exceptions as cfy_exc SUBSCRIPTION = 'subscription' ONDEMAND = 'ondemand' +RANDOM_PREFIX_LENGTH = 5 + + +class Objectview(object): + def __init__(self, d): + self.__dict__ = d class IntegrationSubscriptionTestConfig(Config): @@ -76,22 +89,94 @@ def __init__(self, testname): raise RuntimeError("vcloud_config empty") if not self.test_config: raise RuntimeError("test_config empty") - - def setUp(self): print "\nUsed config: {0}".format(self.service_type) + self.vca_client = self.get_client() + + def get_client(self): fake_ctx = cfy_mocks.MockCloudifyContext( node_id='test', node_name='test', properties={}) with mock.patch('vcloud_plugin_common.ctx', fake_ctx): - self.vca_client = VcloudAirClient().get(config=self.vcloud_config) + vca_client = VcloudAirClient().get(config=self.vcloud_config) + return vca_client - def _run_with_retry(self, func, ctx): - - while True: - try: - return func(ctx=ctx) - except OperationRetry as e: - ctx.operation._operation_retry = None - ctx.logger.info(format(str(e))) - time.sleep(e.retry_after) + def setUp(self): + self.inputs = yaml.load(open('blueprints/inputs.yaml')) + self.inputs.update(self.vcloud_config) + self.tempdir = tempfile.mkdtemp() + self.workdir = os.getcwd() + logger.configure_loggers() + self.failed = True + self.conf = Objectview(self.inputs) + + def tearDown(self): + try: + if self.failed: + self.uninstall() + except Exception as e: + print e + os.chdir(self.workdir) + shutil.rmtree(self.tempdir, True) + + def init(self, blueprint_file): + blueprint = yaml.load(open('blueprints/header.yaml')) + nodes = yaml.load(open('blueprints/{}'.format(blueprint_file))) + blueprint['node_templates'].update(nodes) + with open(os.path.join(self.tempdir, 'inputs.yaml'), 'w') as f: + yaml.dump(self.inputs, f) + with open(os.path.join(self.tempdir, 'blueprint.yaml'), 'w') as f: + yaml.dump(blueprint, f) + os.chdir(self.tempdir) + local_command.init('blueprint.yaml', 'inputs.yaml', False) + + def install(self): + self._execute_command('install') + + def uninstall(self): + self._execute_command('uninstall') + + def _execute_command(self, command): + local_command.execute(command, {}, False, 5, 5, 1) + + +def fail_guard(f): + @wraps(f) + def wrapper(*args, **kargs): + args[0].failed = True + f(*args, **kargs) + args[0].failed = False + return wrapper + + +def wait_for_task(vca_client, task): + """ + check status of current task and make request for recheck + task status in case when we have not well defined state + (not error and not success or by timeout) + """ + WAIT_TIME_MAX_MINUTES = 30 + TASK_RECHECK_TIMEOUT = 5 + TASK_STATUS_SUCCESS = 'success' + TASK_STATUS_ERROR = 'error' + MAX_ATTEMPTS = WAIT_TIME_MAX_MINUTES * 60 / TASK_RECHECK_TIMEOUT + print('Maximun task wait time {0} minutes.'.format(WAIT_TIME_MAX_MINUTES)) + print('Task recheck after {0} seconds.'.format(TASK_RECHECK_TIMEOUT)) + status = task.get_status() + for attempt in range(MAX_ATTEMPTS): + print('Attempt: {0}/{1}.'.format(attempt + 1, MAX_ATTEMPTS)) + if status == TASK_STATUS_SUCCESS: + print('Task completed in {0} seconds' + ''.format(attempt * TASK_RECHECK_TIMEOUT)) + return + if status == TASK_STATUS_ERROR: + error = task.get_Error() + raise cfy_exc.NonRecoverableError( + "Error during task execution: {0}".format(error.get_message())) + time.sleep(TASK_RECHECK_TIMEOUT) + response = requests.get( + task.get_href(), + headers=vca_client.vcloud_session.get_vcloud_headers()) + task = taskType.parseString(response.content, True) + status = task.get_status() + raise cfy_exc.NonRecoverableError("Wait for task timeout.") diff --git a/tests/integration/blueprints/floatingip_connect.yaml b/tests/integration/blueprints/floatingip_connect.yaml new file mode 100644 index 0000000..8f43e5d --- /dev/null +++ b/tests/integration/blueprints/floatingip_connect.yaml @@ -0,0 +1,18 @@ + test_floatingip: + type: cloudify.vcloud.nodes.FloatingIP + properties: + floatingip: + edge_gateway: { get_input: edge_gateway } + + test_server: + type: cloudify.vcloud.nodes.Server + properties: + server: + catalog: { get_input: catalog } + template: { get_input: template } + install_agent: false + management_network: { get_input: network_name } + vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } + relationships: + - target: test_floatingip + type: cloudify.vcloud.server_connected_to_floating_ip diff --git a/tests/integration/blueprints/header.yaml b/tests/integration/blueprints/header.yaml new file mode 100644 index 0000000..2f2009c --- /dev/null +++ b/tests/integration/blueprints/header.yaml @@ -0,0 +1,96 @@ +tosca_definitions_version: cloudify_dsl_1_2 + +imports: + - http://www.getcloudify.org/spec/cloudify/4.4/types.yaml + - plugin:cloudify-vcloud-plugin + - plugin:cloudify-fabric-plugin + +inputs: + username: + type: string + password: + type: string + vdc: + type: string + instance: + type: string + default: '' + service: + type: string + default: '' + org: + type: string + default: '' + url: + type: string + default: '' + service_type: + type: string + default: '' + api_version: + type: string + default: '' + catalog: + type: string + default: '' + template: + type: string + default: '' + network_name: + type: string + default: '' + edge_gateway: + type: string + default: '' + public_ip: + type: string + default: '' + server_name: + type: string + default: '' + server_cpu: + type: integer + default: 1 + server_memory: + type: integer + default: 1024 + ssh_user: + type: string + default: ubuntu + auto_generate_ssh_keys: + type: boolean + default: false + test_vdc_name: + type: string + default: '' + volume_name: + type: string + default: '' + volume_size_Mb: + type: string + default: 1024 + test_network_name: + type: string + default: '' + +node_types: + vcloud_configuration: + derived_from: cloudify.nodes.Root + properties: + vcloud_config: {} + +node_templates: + vcloud_configuration: + type: vcloud_configuration + properties: + vcloud_config: + username: { get_input: username } + password: { get_input: password } + url: { get_input: url } + instance: { get_input: instance } + vdc: { get_input: vdc } + org: { get_input: org } + service: { get_input: service } + service_type: { get_input: service_type } + api_version: { get_input: api_version } + edge_gateway: { get_input: edge_gateway } diff --git a/tests/integration/blueprints/inputs.yaml b/tests/integration/blueprints/inputs.yaml new file mode 100644 index 0000000..57a646b --- /dev/null +++ b/tests/integration/blueprints/inputs.yaml @@ -0,0 +1,5 @@ +volume_name: test_volume +volume_size_Mb: 1024 +server_name: testserver +test_vdc_name: testvdc +test_network_name: newtestnetwork diff --git a/tests/integration/blueprints/keypair_connect.yaml b/tests/integration/blueprints/keypair_connect.yaml new file mode 100644 index 0000000..5a88b94 --- /dev/null +++ b/tests/integration/blueprints/keypair_connect.yaml @@ -0,0 +1,19 @@ + test_keypair: + type: cloudify.vcloud.nodes.KeyPair + properties: + auto_generate: true + private_key: + create_file: true + + test_server: + type: cloudify.vcloud.nodes.Server + properties: + server: + catalog: { get_input: catalog } + template: { get_input: template } + install_agent: false + management_network: { get_input: network_name } + vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } + relationships: + - target: test_keypair + type: cloudify.vcloud.server_connected_to_keypair diff --git a/tests/integration/blueprints/keypair_create.yaml b/tests/integration/blueprints/keypair_create.yaml new file mode 100644 index 0000000..d30a955 --- /dev/null +++ b/tests/integration/blueprints/keypair_create.yaml @@ -0,0 +1,6 @@ + test_keypair: + type: cloudify.vcloud.nodes.KeyPair + properties: + auto_generate: true + private_key: + create_file: true \ No newline at end of file diff --git a/tests/integration/blueprints/network_new.yaml b/tests/integration/blueprints/network_new.yaml new file mode 100644 index 0000000..3bc8a44 --- /dev/null +++ b/tests/integration/blueprints/network_new.yaml @@ -0,0 +1,18 @@ + test_network: + type: cloudify.vcloud.nodes.Network + properties: + network: + edge_gateway: { get_input: edge_gateway } + name: { get_input: test_network_name } + static_range: 10.10.199.2-10.10.199.128 + netmask: 255.255.255.0 + gateway_ip: 10.10.199.1 + dns: + - 8.8.8.8 + - 10.10.199.1 + dns_suffix: testnet + dhcp: + dhcp_range: 10.10.199.129-10.10.199.254 + default_lease: 3600 + max_lease: 7200 + vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } diff --git a/tests/integration/blueprints/network_use_external.yaml b/tests/integration/blueprints/network_use_external.yaml new file mode 100644 index 0000000..a5bb061 --- /dev/null +++ b/tests/integration/blueprints/network_use_external.yaml @@ -0,0 +1,6 @@ + test_network: + type: cloudify.vcloud.nodes.Network + properties: + use_external_resource: true + resource_id: { get_input: network_name } + vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } diff --git a/tests/integration/blueprints/publicnat_to_network.yaml b/tests/integration/blueprints/publicnat_to_network.yaml new file mode 100644 index 0000000..5959920 --- /dev/null +++ b/tests/integration/blueprints/publicnat_to_network.yaml @@ -0,0 +1,18 @@ + test_nat: + type: cloudify.vcloud.nodes.PublicNAT + properties: + nat: + edge_gateway: { get_input: edge_gateway } + rules: + - type: SNAT + vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } + + test_network: + type: cloudify.vcloud.nodes.Network + properties: + use_external_resource: true + resource_id: { get_input: network_name } + vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } + relationships: + - target: test_nat + type: cloudify.vcloud.net_connected_to_public_nat diff --git a/tests/integration/blueprints/publicnat_to_server.yaml b/tests/integration/blueprints/publicnat_to_server.yaml new file mode 100644 index 0000000..acd7569 --- /dev/null +++ b/tests/integration/blueprints/publicnat_to_server.yaml @@ -0,0 +1,33 @@ + test_nat: + type: cloudify.vcloud.nodes.PublicNAT + properties: + nat: + edge_gateway: { get_input: edge_gateway } + rules: + - type: DNAT + protocol: tcp + original_port: 8086 + translated_port: 8086 + - type: DNAT + protocol: tcp + original_port: 443 + translated_port: 443 + - type: DNAT + protocol: tcp + original_port: 22 + translated_port: 22 + vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } + + + test_server: + type: cloudify.vcloud.nodes.Server + properties: + server: + catalog: { get_input: catalog } + template: { get_input: template } + install_agent: false + management_network: { get_input: network_name } + vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } + relationships: + - target: test_nat + type: cloudify.vcloud.server_connected_to_public_nat diff --git a/tests/integration/blueprints/security_group_create.yaml b/tests/integration/blueprints/security_group_create.yaml new file mode 100644 index 0000000..151285a --- /dev/null +++ b/tests/integration/blueprints/security_group_create.yaml @@ -0,0 +1,41 @@ + test_security_group: + type: cloudify.vcloud.nodes.SecurityGroup + properties: + security_group: + name: nodevcloud_security_group + edge_gateway: { get_input: edge_gateway } + rules: + - source: external + destination: internal + destination_port: 22 + action: allow + description: > + ssh between external net and managment node + protocol: TCP + - source: external + destination: internal + destination_port: 80 + action: allow + description: > + http to management node + protocol: TCP + - source: external + destination: internal + action: allow + description: > + Allow ping + protocol: ICMP + vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } + + test_server: + type: cloudify.vcloud.nodes.Server + properties: + server: + catalog: { get_input: catalog } + template: { get_input: template } + install_agent: false + management_network: { get_input: network_name } + vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } + relationships: + - target: test_security_group + type: cloudify.vcloud.server_connected_to_security_group diff --git a/tests/integration/blueprints/server_remove_keys.yaml b/tests/integration/blueprints/server_remove_keys.yaml new file mode 100644 index 0000000..785d851 --- /dev/null +++ b/tests/integration/blueprints/server_remove_keys.yaml @@ -0,0 +1,27 @@ + test_keypair: + type: cloudify.vcloud.nodes.KeyPair + properties: + auto_generate: true + private_key: + create_file: true + + test_server: + type: cloudify.vcloud.nodes.Server + properties: + server: + catalog: { get_input: catalog } + template: { get_input: template } + install_agent: false + management_network: { get_input: network_name } + vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } + relationships: + - target: test_keypair + type: cloudify.vcloud.server_connected_to_keypair + + test_service: + type: cloudify.nodes.ApplicationServer + relationships: + - type: cloudify.relationships.contained_in + target: test_server + - type: cloudify.vcloud.delete_public_key_from_server + target: test_server diff --git a/tests/integration/blueprints/server_to_many_ports.yaml b/tests/integration/blueprints/server_to_many_ports.yaml new file mode 100644 index 0000000..c777909 --- /dev/null +++ b/tests/integration/blueprints/server_to_many_ports.yaml @@ -0,0 +1,94 @@ + test_port1: + type: cloudify.vcloud.nodes.Port + properties: + port: + network: test_network1 + ip_allocation_mode: POOL + primary_interface: true + nic_order: 1 + vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } + relationships: + - target: test_network1 + type: cloudify.vcloud.port_connected_to_network + + + test_port2: + type: cloudify.vcloud.nodes.Port + properties: + port: + network: test_network2 + ip_allocation_mode: POOL + primary_interface: false + nic_order: 2 + vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } + relationships: + - target: test_network2 + type: cloudify.vcloud.port_connected_to_network + + + test_port3: + type: cloudify.vcloud.nodes.Port + properties: + port: + network: test_network3 + ip_allocation_mode: POOL + primary_interface: false + nic_order: 3 + vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } + relationships: + - target: test_network3 + type: cloudify.vcloud.port_connected_to_network + + + test_network1: + type: cloudify.vcloud.nodes.Network + properties: + network: + edge_gateway: { get_input: edge_gateway } + name: test_network1 + static_range: 10.11.199.2-10.11.199.128 + netmask: 255.255.255.0 + gateway_ip: 10.11.199.1 + vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } + + + test_network2: + type: cloudify.vcloud.nodes.Network + properties: + network: + edge_gateway: { get_input: edge_gateway } + name: test_network2 + static_range: 10.12.199.2-10.12.199.128 + netmask: 255.255.255.0 + gateway_ip: 10.12.199.1 + vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } + + + test_network3: + type: cloudify.vcloud.nodes.Network + properties: + network: + edge_gateway: { get_input: edge_gateway } + name: test_network3 + static_range: 10.13.199.2-10.13.199.128 + netmask: 255.255.255.0 + gateway_ip: 10.13.199.1 + vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } + + + test_server: + type: cloudify.vcloud.nodes.Server + properties: + server: + catalog: { get_input: catalog } + template: { get_input: template } + install_agent: false + management_network: { get_input: network_name } + vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } + relationships: + - target: test_port1 + type: cloudify.vcloud.server_connected_to_port + - target: test_port2 + type: cloudify.vcloud.server_connected_to_port + - target: test_port3 + type: cloudify.vcloud.server_connected_to_port diff --git a/tests/integration/blueprints/server_to_network.yaml b/tests/integration/blueprints/server_to_network.yaml new file mode 100644 index 0000000..b175315 --- /dev/null +++ b/tests/integration/blueprints/server_to_network.yaml @@ -0,0 +1,20 @@ + test_network: + type: cloudify.vcloud.nodes.Network + properties: + use_external_resource: true + resource_id: { get_input: network_name } + vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } + + + test_server: + type: cloudify.vcloud.nodes.Server + properties: + server: + catalog: { get_input: catalog } + template: { get_input: template } + install_agent: false + management_network: { get_input: network_name } + vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } + relationships: + - target: test_network + type: cloudify.vcloud.server_connected_to_network diff --git a/tests/integration/blueprints/server_to_port.yaml b/tests/integration/blueprints/server_to_port.yaml new file mode 100644 index 0000000..2a8e245 --- /dev/null +++ b/tests/integration/blueprints/server_to_port.yaml @@ -0,0 +1,33 @@ + test_port: + type: cloudify.vcloud.nodes.Port + properties: + port: + network: { get_input: network_name } + ip_allocation_mode: POOL + primary_interface: true + vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } + relationships: + - target: test_network + type: cloudify.vcloud.port_connected_to_network + + + test_network: + type: cloudify.vcloud.nodes.Network + properties: + use_external_resource: true + resource_id: { get_input: network_name } + vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } + + + test_server: + type: cloudify.vcloud.nodes.Server + properties: + server: + catalog: { get_input: catalog } + template: { get_input: template } + install_agent: false + management_network: { get_input: network_name } + vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } + relationships: + - target: test_port + type: cloudify.vcloud.server_connected_to_port diff --git a/tests/integration/blueprints/server_use_external.yaml b/tests/integration/blueprints/server_use_external.yaml new file mode 100644 index 0000000..cb40284 --- /dev/null +++ b/tests/integration/blueprints/server_use_external.yaml @@ -0,0 +1,8 @@ + test_server: + type: cloudify.vcloud.nodes.Server + properties: + use_external_resource: true + resource_id: { get_input: server_name } + install_agent: false + management_network: { get_input: network_name } + vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } diff --git a/tests/integration/blueprints/server_use_interface_inputs.yaml b/tests/integration/blueprints/server_use_interface_inputs.yaml new file mode 100644 index 0000000..11217a7 --- /dev/null +++ b/tests/integration/blueprints/server_use_interface_inputs.yaml @@ -0,0 +1,15 @@ + test_server: + type: cloudify.vcloud.nodes.Server + properties: + server: + template: { get_input: template } + install_agent: false + vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } + interfaces: + cloudify.interfaces.lifecycle: + create: + inputs: + properties: + management_network: { get_input: network_name } + server: + catalog: { get_input: catalog } \ No newline at end of file diff --git a/tests/integration/blueprints/server_with_name.yaml b/tests/integration/blueprints/server_with_name.yaml new file mode 100644 index 0000000..d300428 --- /dev/null +++ b/tests/integration/blueprints/server_with_name.yaml @@ -0,0 +1,15 @@ + example_server: + type: cloudify.vcloud.nodes.Server + properties: + server: + name: { get_input: server_name } + catalog: { get_input: catalog } + template: { get_input: template } + hardware: + cpu: { get_input: server_cpu } + memory: { get_input: server_memory } + guest_customization: + computer_name: { get_input: server_name } + install_agent: false + management_network: { get_input: network_name } + vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } diff --git a/tests/integration/blueprints/server_without_name.yaml b/tests/integration/blueprints/server_without_name.yaml new file mode 100644 index 0000000..29a63c4 --- /dev/null +++ b/tests/integration/blueprints/server_without_name.yaml @@ -0,0 +1,9 @@ + test_server: + type: cloudify.vcloud.nodes.Server + properties: + server: + catalog: { get_input: catalog } + template: { get_input: template } + install_agent: false + management_network: { get_input: network_name } + vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } diff --git a/tests/integration/blueprints/vdc_new.yaml b/tests/integration/blueprints/vdc_new.yaml new file mode 100644 index 0000000..00b7257 --- /dev/null +++ b/tests/integration/blueprints/vdc_new.yaml @@ -0,0 +1,5 @@ +test_vdc: + type: cloudify.vcloud.nodes.VDC + properties: + name: { get_input: test_vdc_name } + vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } diff --git a/tests/integration/blueprints/vdc_use_external.yaml b/tests/integration/blueprints/vdc_use_external.yaml new file mode 100644 index 0000000..e398f2e --- /dev/null +++ b/tests/integration/blueprints/vdc_use_external.yaml @@ -0,0 +1,6 @@ +test_vdc: + type: cloudify.vcloud.nodes.VDC + properties: + use_external_resource: true + resource_id: { get_input: test_vdc_name } + vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } diff --git a/tests/integration/blueprints/volume_attach.yaml b/tests/integration/blueprints/volume_attach.yaml new file mode 100644 index 0000000..b04de96 --- /dev/null +++ b/tests/integration/blueprints/volume_attach.yaml @@ -0,0 +1,29 @@ + test_floatingip: + type: cloudify.vcloud.nodes.FloatingIP + properties: + floatingip: + edge_gateway: { get_input: edge_gateway } + + test_server: + type: cloudify.vcloud.nodes.Server + properties: + server: + catalog: { get_input: catalog } + template: { get_input: template } + install_agent: false + management_network: { get_input: network_name } + vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } + relationships: + - target: test_floatingip + type: cloudify.vcloud.server_connected_to_floating_ip + + test_volume: + type: cloudify.vcloud.nodes.Volume + properties: + volume: + name: { get_input: volume_name } + size: { get_input: volume_size_Mb } + vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } + relationships: + - type: cloudify.vcloud.volume_attached_to_server + target: test_server diff --git a/tests/integration/blueprints/volume_new.yaml b/tests/integration/blueprints/volume_new.yaml new file mode 100644 index 0000000..63ffacd --- /dev/null +++ b/tests/integration/blueprints/volume_new.yaml @@ -0,0 +1,7 @@ + volume: + type: cloudify.vcloud.nodes.Volume + properties: + volume: + name: { get_input: volume_name } + size: { get_input: volume_size_Mb } + vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } diff --git a/tests/integration/blueprints/volume_use_external.yaml b/tests/integration/blueprints/volume_use_external.yaml new file mode 100644 index 0000000..2fdca53 --- /dev/null +++ b/tests/integration/blueprints/volume_use_external.yaml @@ -0,0 +1,6 @@ + volume: + type: cloudify.vcloud.nodes.Volume + properties: + use_external_resource: true + resource_id: { get_input: volume_name } + vcloud_config: { get_property: [vcloud_configuration, vcloud_config] } diff --git a/tests/integration/run_all_tests.py b/tests/integration/run_all_tests.py deleted file mode 100644 index 158932e..0000000 --- a/tests/integration/run_all_tests.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# * See the License for the specific language governing permissions and -# * limitations under the License. - -import nose -import os -from tests.integration import SUBSCRIPTION, ONDEMAND - -testfiles = [file for file in os.listdir('.') - if file.startswith("test") and file.endswith(".py")] -try: - for service in (SUBSCRIPTION, ONDEMAND): - for test in testfiles: - result = nose.run( - argv=['-x', '-v', '-s', '--tc={0}:'.format(service), test]) - if not result: - raise RuntimeError("Test failed") -except RuntimeError as e: - print e diff --git a/tests/integration/test_combined.py b/tests/integration/test_combined.py deleted file mode 100644 index 4e2548e..0000000 --- a/tests/integration/test_combined.py +++ /dev/null @@ -1,216 +0,0 @@ -# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# * See the License for the specific language governing permissions and -# * limitations under the License. - -import contextlib -import ipaddress -import mock -import random -import string -import time -import unittest - -from cloudify import mocks as cfy_mocks - -from network_plugin import floatingip, network -from server_plugin import server - -from tests.integration import TestCase - -RANDOM_PREFIX_LENGTH = 5 - - -class CombinedTestCase(TestCase): - - def setUp(self): - super(CombinedTestCase, self).setUp() - chars = string.ascii_uppercase + string.digits - self.name_prefix = ('plugin_test_{0}_' - .format(''.join( - random.choice(chars) - for _ in range(RANDOM_PREFIX_LENGTH)))) - - def _setup_network(self): - network_use_existing = \ - self.test_config['combined']['network_use_existing'] - existing_network = self.test_config['combined']['network_name'] - self.network_name = (existing_network if network_use_existing - else self.name_prefix + "network") - self.network_ctx = cfy_mocks.MockCloudifyContext( - node_id=self.network_name, - node_name=self.network_name, - properties={ - "network": self.test_config['network'], - "use_external_resource": network_use_existing, - "resource_id": self.network_name, - "vcloud_config": self.vcloud_config}) - - def _setup_server(self, ip_allocation_mode): - self.server_name = self.name_prefix + 'server' - port_node_context = cfy_mocks.MockNodeContext( - properties={ - 'port': - { - 'network': self.network_name, - 'ip_allocation_mode': ip_allocation_mode, - 'primary_interface': True - } - } - ) - port_relationship = mock.Mock() - port_relationship.target = mock.Mock() - port_relationship.target.node = port_node_context - self.server_ctx = cfy_mocks.MockCloudifyContext( - node_id=self.server_name, - node_name=self.server_name, - properties={ - 'server': self.test_config['server'], - 'management_network': self.network_name, - "vcloud_config": self.vcloud_config - } - ) - self.server_ctx.instance.relationships = [port_relationship] - - def _setup_floating_ip(self): - self.fip_ctx = cfy_mocks.MockCloudifyContext( - node_id='test', - node_name='test', - properties={}, - target=cfy_mocks.MockCloudifyContext( - node_id="target", - properties={'floatingip': self.test_config['floatingip']}), - source=cfy_mocks.MockCloudifyContext( - node_id="source", - properties={'vcloud_config': self.vcloud_config}, - runtime_properties={server.VCLOUD_VAPP_NAME: self.server_name} - ) - ) - - def test_new_server_network_ip_allocation_dhcp(self): - self._setup_network() - - self._setup_server(ip_allocation_mode='dhcp') - - self.addCleanup(self._delete_network) - self._create_network() - - self.addCleanup(self._delete_server) - self._create_server() - self._wait_for_server_configured() - - if self.test_config['combined']['network_use_existing'] is False: - gw_ip = self.network_ctx.node.properties['network']['gateway_ip'] - netmask = self.network_ctx.node.properties['network']['netmask'] - gw_interface = ipaddress.IPv4Interface( - gw_ip + '/' + netmask) - vdc = self.vca_client.get_vdc(self.vcloud_config['org']) - vapp = self.vca_client.get_vapp( - vdc, - self.server_ctx.instance.runtime_properties[ - server.VCLOUD_VAPP_NAME] - ) - nw_connection = server._get_vm_network_connection( - vapp, self.network_name) - self.assertTrue(ipaddress.IPv4Address(unicode(nw_connection['ip'])) - in gw_interface.network, - "vm ip: {0}, expected network: {1}" - .format(nw_connection['ip'], - gw_interface.network)) - - def test_new_server_network_ip_allocation_pool(self): - self._setup_network() - - self._setup_server(ip_allocation_mode='pool') - - self.addCleanup(self._delete_network) - self._create_network() - - self.addCleanup(self._delete_server) - self._create_server() - self._wait_for_server_configured() - - if self.test_config['combined']['network_use_existing'] is False: - gw_ip = self.network_ctx.node.properties['network']['gateway_ip'] - netmask = self.network_ctx.node.properties['network']['netmask'] - gw_interface = ipaddress.IPv4Interface( - gw_ip + '/' + netmask) - vdc = self.vca_client.get_vdc(self.vcloud_config['org']) - vapp = self.vca_client.get_vapp( - vdc, - self.server_ctx.instance.runtime_properties[ - server.VCLOUD_VAPP_NAME] - ) - nw_connection = server._get_vm_network_connection( - vapp, self.network_name) - self.assertTrue(ipaddress.IPv4Address(unicode(nw_connection['ip'])) - in gw_interface.network, - "vm ip: {0}, expected network: {1}" - .format(nw_connection['ip'], - gw_interface.network)) - - def _create_network(self): - with contextlib.nested( - mock.patch('network_plugin.network.ctx', self.network_ctx), - mock.patch('vcloud_plugin_common.ctx', self.network_ctx)): - network.create() - - def _delete_network(self): - with contextlib.nested( - mock.patch('network_plugin.network.ctx', self.network_ctx), - mock.patch('vcloud_plugin_common.ctx', self.network_ctx)): - network.delete() - - def _create_server(self): - with contextlib.nested( - mock.patch('server_plugin.server.ctx', self.server_ctx), - mock.patch('vcloud_plugin_common.ctx', self.server_ctx)): - server.create() - self._run_with_retry(server.start, self.server_ctx) - - def _delete_server(self): - with contextlib.nested( - mock.patch('server_plugin.server.ctx', self.server_ctx), - mock.patch('vcloud_plugin_common.ctx', self.server_ctx)): - server.stop() - server.delete() - - def _wait_for_server_configured(self): - with contextlib.nested( - mock.patch('server_plugin.server.ctx', self.server_ctx), - mock.patch('vcloud_plugin_common.ctx', self.server_ctx)): - num_tries = 10 - verified = False - for _ in range(num_tries): - result = server._get_state(self.vca_client) - if result is True: - verified = True - break - time.sleep(10) - self.assertTrue(verified, - "Server configuration wasn't verified") - - def _connect_floating_ip(self): - with contextlib.nested( - mock.patch('network_plugin.floatingip.ctx', self.fip_ctx), - mock.patch('vcloud_plugin_common.ctx', self.fip_ctx)): - floatingip.connect_floatingip() - - def _disconnect_floating_ip(self): - with contextlib.nested( - mock.patch('network_plugin.floatingip.ctx', self.fip_ctx), - mock.patch('vcloud_plugin_common.ctx', self.fip_ctx)): - floatingip.disconnect_floatingip() - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/integration/test_network_plugin.py b/tests/integration/test_network_plugin.py index d18ecbc..6f909ce 100644 --- a/tests/integration/test_network_plugin.py +++ b/tests/integration/test_network_plugin.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved +# Copyright (c) 2014-2020 Cloudify Platform Ltd. All rights reserved # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -8,301 +8,81 @@ # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, -# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# * See the License for the specific language governing permissions and -# * limitations under the License. +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. -import os -import mock -from cloudify.mocks import MockCloudifyContext -from network_plugin import (floatingip, network, security_group, public_nat, - keypair, port) -from server_plugin.server import VCLOUD_VAPP_NAME -from network_plugin.network import VCLOUD_NETWORK_NAME -from network_plugin import CheckAssignedExternalIp -from cloudify import exceptions as cfy_exc -from tests.integration import TestCase +from tests.integration import TestCase, fail_guard -# for skipping test add this before test function: -# @unittest.skip("skip test") - -class ValidationOperationsTestCase(TestCase): +class FloatingIpTestCase(TestCase): def setUp(self): - name = "testnode" - self.ctx = MockCloudifyContext( - node_id=name, - node_name=name, - properties={'vcloud_config': self.vcloud_config}) - ctx_patch = mock.patch('vcloud_plugin_common.ctx', self.ctx) - ctx_patch.start() - self.addCleanup(ctx_patch.stop) super(self.__class__, self).setUp() - def test_validation(self): - self.ctx.node.properties.update( - {'floatingip': self.test_config['floatingip']}) - with mock.patch('network_plugin.floatingip.ctx', self.ctx): - floatingip.creation_validation() - - self.ctx.node.properties.update( - {'floatingip': self.test_config['floatingip_auto']}) - with mock.patch('network_plugin.floatingip.ctx', self.ctx): - floatingip.creation_validation() - - self.ctx.node.properties.update( - {'private_key_path': os.path.realpath(__file__)}) - with mock.patch('network_plugin.keypair.ctx', self.ctx): - keypair.creation_validation() - - self.ctx.node.properties.update( - {"resource_id": self.test_config['network']['name'], - "network": self.test_config['network'], - "use_external_resource": False}) - with mock.patch('network_plugin.network.ctx', self.ctx): - network.creation_validation() + @fail_guard + def test_connect(self): + self.init('floatingip_connect.yaml') + self.install() + self.uninstall() - self.ctx.node.properties.update( - {'port': { - 'network': self.test_config['management_network'], - 'ip_allocation_mode': 'dhcp', - 'primary_interface': True}}) - with mock.patch('network_plugin.port.ctx', self.ctx): - port.creation_validation() - self.ctx.node.properties.update( - {"nat": self.test_config['public_nat']['nat'], - "rules": self.test_config['public_nat']['rules_net']}) - with mock.patch('network_plugin.public_nat.ctx', self.ctx): - public_nat.creation_validation() - - self.ctx.node.properties.update(self.test_config['security_group']) - with mock.patch('network_plugin.security_group.ctx', self.ctx): - security_group.creation_validation() - - -class FloatingIPOperationsTestCase(TestCase): +class KeypairTestCase(TestCase): def setUp(self): - name = "testnode" - self.properties = { - 'vcloud_config': self.vcloud_config, - 'floatingip': self.test_config['floatingip'] - } - self.ctx = MockCloudifyContext( - node_id=name, - node_name=name, - properties={}, - target=MockCloudifyContext(node_id="target", - properties=self.properties), - source=MockCloudifyContext( - node_id="source", - properties={'vcloud_config': self.vcloud_config}, - runtime_properties={ - VCLOUD_VAPP_NAME: self.test_config['test_vm']} - ) - ) - ctx_patch1 = mock.patch('network_plugin.floatingip.ctx', self.ctx) - ctx_patch2 = mock.patch('vcloud_plugin_common.ctx', self.ctx) - ctx_patch1.start() - ctx_patch2.start() - self.addCleanup(ctx_patch1.stop) - self.addCleanup(ctx_patch2.stop) super(self.__class__, self).setUp() - def tearDown(self): - super(self.__class__, self).tearDown() - - def test_floating_ip_create_delete_with_explicit_ip(self): - self.ctx.target.node.properties['floatingip'].update( - self.test_config['floatingip']) - public_ip = self.ctx.target.node.properties['floatingip']['public_ip'] - CheckAssignedExternalIp(public_ip, self._get_gateway()) - floatingip.connect_floatingip() - floatingip.disconnect_floatingip() - CheckAssignedExternalIp(public_ip, self._get_gateway()) + @fail_guard + def test_create(self): + self.init('keypair_create.yaml') + self.install() + self.uninstall() - def test_floating_ip_create_delete_with_autoget_ip(self): - self.ctx.target.node.properties['floatingip'].update( - self.test_config['floatingip']) - del self.ctx.target.node.properties['floatingip']['public_ip'] - floatingip.connect_floatingip() - public_ip = self.ctx.target.instance.runtime_properties['public_ip'] - self.assertRaises(cfy_exc.NonRecoverableError, - CheckAssignedExternalIp, - public_ip, - self._get_gateway()) - self.assertTrue(public_ip) - floatingip.disconnect_floatingip() - CheckAssignedExternalIp(public_ip, self._get_gateway()) + @fail_guard + def test_connect(self): + self.init('keypair_connect.yaml') + self.install() + self.uninstall() - def _get_gateway(self): - return self.vca_client.get_gateway( - self.vcloud_config["org"], - self.ctx.target.node.properties['floatingip']['edge_gateway']) - -class OrgNetworkOperationsTestCase(TestCase): +class NetworkTestCase(TestCase): def setUp(self): - - self.net_name = self.test_config['network']['name'] - self.existing_net_name = self.test_config['test_network_name'] - self.ctx = MockCloudifyContext( - node_id=self.net_name, - node_name=self.net_name, - properties={"resource_id": self.existing_net_name, - "network": self.test_config['network'], - "vcloud_config": self.vcloud_config, - "use_external_resource": False}) - self.org_name = self.vcloud_config["org"] - ctx_patch1 = mock.patch('network_plugin.network.ctx', self.ctx) - ctx_patch2 = mock.patch('vcloud_plugin_common.ctx', self.ctx) - ctx_patch1.start() - ctx_patch2.start() - self.addCleanup(ctx_patch1.stop) - self.addCleanup(ctx_patch2.stop) super(self.__class__, self).setUp() - def get_pools(self): - gateway = self.vca_client.get_gateways(self.org_name)[0] - if not gateway: - raise cfy_exc.NonRecoverableError("Gateway not found") - gatewayConfiguration = gateway.me.get_Configuration() - edgeGatewayServiceConfiguration = \ - gatewayConfiguration.get_EdgeGatewayServiceConfiguration() - dhcpService = filter( - lambda service: (service.__class__.__name__ - == "GatewayDhcpServiceType"), - edgeGatewayServiceConfiguration.get_NetworkService())[0] - return dhcpService.get_Pool() + @fail_guard + def test_network_new(self): + self.init('network_new.yaml') + self.install() + self.uninstall() - def tearDown(self): - super(self.__class__, self).tearDown() + @fail_guard + def test_network_use_external(self): + self.init('network_use_external.yaml') + self.install() + self.uninstall() - def test_orgnetwork_create_delete(self): - self.assertNotIn(self.net_name, - network._get_network_list(self.vca_client, - self.org_name)) - start_pools = len(self.get_pools()) - network.create() - self.assertIn(self.net_name, - network._get_network_list(self.vca_client, - self.org_name)) - self.assertEqual(start_pools + 1, len(self.get_pools())) - network.delete() - self.assertNotIn(self.net_name, - network._get_network_list(self.vca_client, - self.org_name)) - self.assertEqual(start_pools, len(self.get_pools())) - -class SecurityGroupOperationsTestCase(TestCase): +class PublicNatTestCase(TestCase): def setUp(self): - name = "testnode" - self.ctx = MockCloudifyContext( - node_id=name, - node_name=name, - properties={}, - target=MockCloudifyContext( - node_id="target", - properties=self.test_config['security_group']), - source=MockCloudifyContext( - node_id="source", - properties={'vcloud_config': self.vcloud_config}, - runtime_properties={ - VCLOUD_VAPP_NAME: self.test_config['test_vm']} - ) - ) - ctx_patch1 = mock.patch('network_plugin.security_group.ctx', self.ctx) - ctx_patch2 = mock.patch('vcloud_plugin_common.ctx', self.ctx) - ctx_patch1.start() - ctx_patch2.start() - self.addCleanup(ctx_patch1.stop) - self.addCleanup(ctx_patch2.stop) - self.org_name = self.vcloud_config["org"] super(self.__class__, self).setUp() - def tearDown(self): - super(self.__class__, self).tearDown() - - def test_firewall_rules_create_delete(self): - rules = len(self.get_rules()) - security_group.create() - self.assertEqual(rules + 2, len(self.get_rules())) - security_group.delete() - self.assertEqual(rules, len(self.get_rules())) + @fail_guard + def test_connect_to_network(self): + self.init('publicnat_to_network.yaml') + self.install() + self.uninstall() - def get_rules(self): - gateway = self.vca_client.get_gateways(self.org_name)[0] - if not gateway: - raise cfy_exc.NonRecoverableError("Gateway not found") - gatewayConfiguration = gateway.me.get_Configuration() - edgeGatewayServiceConfiguration = \ - gatewayConfiguration.get_EdgeGatewayServiceConfiguration() - firewallService = filter( - lambda service: (service.__class__.__name__ - == "FirewallServiceType"), - edgeGatewayServiceConfiguration.get_NetworkService())[0] - return firewallService.get_FirewallRule() + @fail_guard + def test_connect_to_server(self): + self.init('publicnat_to_server.yaml') + self.install() + self.uninstall() -class PublicNatOperationsTestCase(TestCase): +class SecurityGroupTestCase(TestCase): def setUp(self): - name = "testnode" - self.ctx = MockCloudifyContext( - node_id=name, - node_name=name, - properties={}, - target=MockCloudifyContext( - node_id="target", - properties={ - "nat": self.test_config['public_nat']['nat'], - 'use_external_resource': False, - "rules": {}}), - source=MockCloudifyContext( - node_id="source", - properties={"vcloud_config": self.vcloud_config}, - runtime_properties={ - VCLOUD_VAPP_NAME: - self.test_config['public_nat']['test_vm'], - VCLOUD_NETWORK_NAME: - self.test_config['public_nat']['network_name']} - ) - ) - ctx_patch1 = mock.patch('network_plugin.public_nat.ctx', self.ctx) - ctx_patch2 = mock.patch('vcloud_plugin_common.ctx', self.ctx) - ctx_patch1.start() - ctx_patch2.start() - self.addCleanup(ctx_patch1.stop) - self.addCleanup(ctx_patch2.stop) super(self.__class__, self).setUp() - def tearDown(self): - super(self.__class__, self).tearDown() - - def test_public_network_connected_to_nat(self): - self.ctx.target.node.properties['rules'] = \ - self.test_config['public_nat']['rules_net'] - self.ctx.source.node.properties['resource_id'] = \ - self.test_config['public_nat']['network_name'] - rules_count = self.get_rules_count() - public_nat.net_connect_to_nat() - self.assertEqual(rules_count + 1, self.get_rules_count()) - public_nat.net_disconnect_from_nat() - self.assertEqual(rules_count, self.get_rules_count()) - - def test_public_server_connected_to_nat(self): - self.ctx.target.node.properties['rules'] = \ - self.test_config['public_nat']['rules_port'] - rules_count = self.get_rules_count() - public_nat.server_connect_to_nat() - self.assertEqual(rules_count + 3, self.get_rules_count()) - public_nat.server_disconnect_from_nat() - self.assertEqual(rules_count, self.get_rules_count()) - - def get_rules_count(self): - return len(self._get_gateway().get_nat_rules()) - - def _get_gateway(self): - return self.vca_client.get_gateway( - self.vcloud_config["org"], - self.ctx.target.node.properties['nat']['edge_gateway']) + @fail_guard + def test_create(self): + self.init('security_group_create.yaml') + self.install() + self.uninstall() diff --git a/tests/integration/test_server_plugin.py b/tests/integration/test_server_plugin.py index 2e41d3c..27471af 100644 --- a/tests/integration/test_server_plugin.py +++ b/tests/integration/test_server_plugin.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved +# Copyright (c) 2014-2020 Cloudify Platform Ltd. All rights reserved # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -8,359 +8,103 @@ # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, -# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# * See the License for the specific language governing permissions and -# * limitations under the License. +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. -import mock -import random -import socket -import string -import time +from tests.integration import TestCase, wait_for_task, fail_guard -from cloudify import exceptions as cfy_exc -from cloudify import mocks as cfy_mocks -from server_plugin import server -from server_plugin import volume -from tests.integration import TestCase -from cloudify.mocks import MockCloudifyContext -from server_plugin.server import VCLOUD_VAPP_NAME - -RANDOM_PREFIX_LENGTH = 5 - - -class ServerNoNetworkTestCase(TestCase): - def setUp(self): - super(ServerNoNetworkTestCase, self).setUp() - chars = string.ascii_uppercase + string.digits - self.name_prefix = ('plugin_test_{0}_' - .format(''.join( - random.choice(chars) - for _ in range(RANDOM_PREFIX_LENGTH))) - ) - server_test_dict = self.test_config['server'] - name = self.name_prefix + 'server' - - self.ctx = cfy_mocks.MockCloudifyContext( - node_id=name, - node_name=name, - properties={ - 'server': - { - 'name': name, - 'catalog': server_test_dict['catalog'], - 'template': server_test_dict['template'], - 'hardware': server_test_dict['hardware'], - 'guest_customization': - server_test_dict.get('guest_customization') - }, - 'management_network': self.test_config['management_network'], - 'vcloud_config': self.vcloud_config - } - ) - self.ctx.node.properties['server']['guest_customization'][ - 'public_keys'] = [self.test_config['manager_keypair'], - self.test_config['agent_keypair']] - self.ctx.instance.relationships = [] - ctx_patch1 = mock.patch('server_plugin.server.ctx', self.ctx) - ctx_patch2 = mock.patch('vcloud_plugin_common.ctx', self.ctx) - ctx_patch1.start() - ctx_patch2.start() - self.addCleanup(ctx_patch1.stop) - self.addCleanup(ctx_patch2.stop) - - def tearDown(self): - try: - server.stop() - except Exception: - pass - try: - server.delete() - except Exception: - pass - super(ServerNoNetworkTestCase, self).tearDown() - - def test_server_creation_validation(self): - success = True - msg = None - try: - server.creation_validation() - except cfy_exc.NonRecoverableError as e: - success = False - msg = e.message - self.assertTrue(success, msg) - - def test_server_creation_validation_catalog_not_found(self): - self.ctx.node.properties['server']['catalog'] = 'fake-catalog' - self.assertRaises(cfy_exc.NonRecoverableError, - server.creation_validation) - - def test_server_creation_validation_template_not_found(self): - self.ctx.node.properties['server']['template'] = 'fake-template' - self.assertRaises(cfy_exc.NonRecoverableError, - server.creation_validation) - - def test_server_creation_validation_parameter_missing(self): - del self.ctx.node.properties['server']['template'] - self.assertRaises(cfy_exc.NonRecoverableError, - server.creation_validation) - - def test_server_create_delete(self): - server.create() - vdc = self.vca_client.get_vdc(self.vcloud_config['org']) - vapp = self.vca_client.get_vapp( - vdc, - self.ctx.node.properties['server']['name']) - self.assertFalse(vapp is None) - self.assertFalse(server._vapp_is_on(vapp)) - self.check_hardware(vapp) - server.delete() - vapp = self.vca_client.get_vapp( - vdc, - self.ctx.node.properties['server']['name']) - self.assertTrue(vapp is None) - - def test_server_stop_start(self): - server.create() - vdc = self.vca_client.get_vdc(self.vcloud_config['org']) - vapp = self.vca_client.get_vapp( - vdc, - self.ctx.node.properties['server']['name']) - self.assertFalse(vapp is None) - self.assertFalse(server._vapp_is_on(vapp)) - - self._run_with_retry(server.start, self.ctx) - vapp = self.vca_client.get_vapp( - vdc, - self.ctx.node.properties['server']['name']) - self.assertTrue(server._vapp_is_on(vapp)) - - server.stop() - vapp = self.vca_client.get_vapp( - vdc, - self.ctx.node.properties['server']['name']) - self.assertFalse(server._vapp_is_on(vapp)) - - self._run_with_retry(server.start, self.ctx) - vapp = self.vca_client.get_vapp( - vdc, - self.ctx.node.properties['server']['name']) - self.assertTrue(server._vapp_is_on(vapp)) - - def check_hardware(self, vapp): - data = vapp.get_vms_details()[0] - hardware = self.test_config['server']['hardware'] - if hardware: - self.assertEqual(data['cpus'], hardware['cpu']) - self.assertEqual(data['memory'] * 1024, hardware['memory']) - - -class ServerWithNetworkTestCase(TestCase): +class ServerTestCase(TestCase): def setUp(self): - super(ServerWithNetworkTestCase, self).setUp() - chars = string.ascii_uppercase + string.digits - self.name_prefix = ('plugin_test_{0}_' - .format(''.join( - random.choice(chars) - for _ in range(RANDOM_PREFIX_LENGTH))) - ) - - server_test_dict = self.test_config['server'] - name = self.name_prefix + 'server' - self.network_name = self.test_config['management_network'] - - port_node_context = cfy_mocks.MockNodeContext( - properties={ - 'port': - { - 'network': self.network_name, - 'ip_allocation_mode': 'pool', - 'primary_interface': True - } - } - ) - - network_node_context = cfy_mocks.MockNodeContext( - properties={ - 'network': - { - 'name': self.network_name - } - } - ) - - self.port_relationship = mock.Mock() - self.port_relationship.target = mock.Mock() - self.port_relationship.target.node = port_node_context - - self.network_relationship = mock.Mock() - self.network_relationship.target = mock.Mock() - self.network_relationship.target.node = network_node_context - self.properties = { - 'server': - { - 'name': name, - 'catalog': server_test_dict['catalog'], - 'template': server_test_dict['template'] - }, - 'management_network': self.network_name, - 'vcloud_config': self.vcloud_config - } - self.ctx = cfy_mocks.MockCloudifyContext( - node_id=name, - node_name=name, - properties=self.properties - ) - self.ctx.instance.relationships = [] - ctx_patch1 = mock.patch('server_plugin.server.ctx', self.ctx) - ctx_patch2 = mock.patch('vcloud_plugin_common.ctx', self.ctx) - ctx_patch1.start() - ctx_patch2.start() - self.addCleanup(ctx_patch1.stop) - self.addCleanup(ctx_patch2.stop) - - def tearDown(self): - try: - server.stop() - except Exception: - pass + super(ServerTestCase, self).setUp() + + @fail_guard + def test_with_name(self): + self.init('server_with_name.yaml') + self.install() + self.uninstall() + + @fail_guard + def test_without_name(self): + self.init('server_without_name.yaml') + self.install() + self.uninstall() + + @fail_guard + def test_use_external(self): + task = self.vca_client.create_vapp( + self.conf.vdc, self.conf.server_name, self.conf.template, + self.conf.catalog, network_name=self.conf.network_name, + vm_name=self.conf.server_name, deploy='false', poweron='false') + if task: + wait_for_task(self.vca_client, task) + else: + raise Exception("Can't create vm") try: - server.delete() - except Exception: - pass - super(ServerWithNetworkTestCase, self).tearDown() - - def test_create_with_port_connection(self): - self.ctx.instance.relationships = [self.port_relationship] - self._create_test() - - def test_create_with_network_connection(self): - self.ctx.instance.relationships = [self.network_relationship] - self._create_test() - - def test_create_without_connections(self): - self.ctx.instance.relationships = [] - self._create_test() - - def _create_test(self): - server.create() - self._run_with_retry(server.start, self.ctx) - vdc = self.vca_client.get_vdc(self.vcloud_config['org']) - vapp = self.vca_client.get_vapp( - vdc, - self.ctx.node.properties['server']['name']) - self.assertFalse(vapp is None) - networks = server._get_vm_network_connections(vapp) - self.assertEqual(1, len(networks)) - self.assertEqual(self.network_name, networks[0]['network_name']) - - def test_get_state(self): - num_tries = 5 - verified = False - server.create() - self._run_with_retry(server.start, self.ctx) - for _ in range(num_tries): - result = server._get_state(self.vca_client) - if result is True: - self.assertTrue('ip' in self.ctx.instance.runtime_properties) - self.assertTrue('networks' - in self.ctx.instance.runtime_properties) - self.assertEqual(1, - len(self.ctx.instance. - runtime_properties['networks'].keys())) - self.assertEqual(self.network_name, - self.ctx.instance. - runtime_properties['networks'].keys()[0]) - ip_valid = True - try: - socket.inet_aton( - self.ctx.instance.runtime_properties['ip']) - except socket.error: - ip_valid = False - self.assertTrue(ip_valid) - verified = True - break - time.sleep(2) - self.assertTrue(verified) - - -class VolumeTestCase(TestCase): + self.init('server_use_external.yaml') + self.install() + self.uninstall() + finally: + self.vca_client.delete_vapp(self.conf.vdc, self.conf.server_name) + self.failed = False + + @fail_guard + def test_interface_inputs(self): + self.init('server_use_interface_inputs.yaml') + self.install() + self.uninstall() + + @fail_guard + def test_connect_to_network(self): + self.init('server_to_network.yaml') + self.install() + self.uninstall() + + @fail_guard + def test_connect_to_port(self): + self.init('server_to_port.yaml') + self.install() + self.uninstall() + + @fail_guard + def test_connect_to_many_ports(self): + self.init('server_to_many_ports.yaml') + self.install() + self.uninstall() + + @fail_guard + def test_remove_keys(self): + self.init('server_remove_keys.yaml') + self.install() + self.uninstall() + + +class VdcTestCase(TestCase): def setUp(self): - super(VolumeTestCase, self).setUp() - self.volume_test_dict = self.test_config['volume'] - name = 'volume' - self.properties = { - 'volume': - { - 'name': self.volume_test_dict['name'], - 'size': self.volume_test_dict['size'] - }, - 'use_external_resource': True, - 'resource_id': self.volume_test_dict['name_exists'], - 'vcloud_config': self.vcloud_config - } - self.target = MockCloudifyContext( - node_id="target", - properties={'vcloud_config': self.vcloud_config}, - runtime_properties={ - VCLOUD_VAPP_NAME: self.test_config['test_vm'] - } - ) - self.source = MockCloudifyContext( - node_id="source", properties=self.properties - ) - self.nodectx = cfy_mocks.MockCloudifyContext( - node_id=name, - node_name=name, - properties=self.properties - ) - self.relationctx = cfy_mocks.MockCloudifyContext( - node_id=name, - node_name=name, - target=self.target, - source=self.source - ) - self.ctx = self.nodectx - ctx_patch1 = mock.patch('server_plugin.volume.ctx', self.nodectx) - ctx_patch2 = mock.patch('vcloud_plugin_common.ctx', self.nodectx) - ctx_patch1.start() - ctx_patch2.start() - self.addCleanup(ctx_patch1.stop) - self.addCleanup(ctx_patch2.stop) - - def test_volume(self): - disks_count = lambda: len( - self.vca_client.get_disks(self.vcloud_config['vdc'])) - volume.creation_validation() - disks_before = disks_count() - volume.create_volume() - if self.relationctx.source.node.properties['use_external_resource']: - self.assertEqual(disks_before, disks_count()) + super(VdcTestCase, self).setUp() + + @fail_guard + def test_new(self): + if self.service_type == 'subscription': + print 'Testing only in ondemand service' + return + self.init('vdc_new.yaml') + self.install() + self.uninstall() + + @fail_guard + def test_use_external(self): + task = self.vca_client.create_vdc(self.conf.test_vdc_name) + if task: + wait_for_task(self.vca_client, task) else: - self.assertEqual(disks_before + 1, disks_count()) - self._attach_detach() - volume.delete_volume() - self.assertEqual(disks_before, disks_count()) - - def _attach_detach(self): - def links_count(): - node_properties = self.relationctx.source.node.properties - if node_properties['use_external_resource']: - return [ - len(d[1]) for d in self.vca_client.get_disks( - self.vcloud_config['vdc'] - ) if d[0].name == node_properties['resource_id'] - ][0] - else: - return [ - len(d[1]) for d in self.vca_client.get_disks( - self.vcloud_config['vdc'] - ) if d[0].name == node_properties['volume']['name'] - ][0] - with mock.patch('server_plugin.volume.ctx', self.relationctx): - links_before = links_count() - volume.attach_volume() - self.assertEqual(links_before + 1, links_count()) - volume.detach_volume() - self.assertEqual(links_before, links_count()) + raise Exception("Can't create vdc") + self.init('vdc_use_external.yaml') + self.install() + self.uninstall() + self.vca_client = self.get_client() + result, task = self.vca_client.delete_vdc(self.conf.test_vdc_name) + if not result: + raise Exception(task) + wait_for_task(self.vca_client, task) diff --git a/tests/integration/test_storage_plugin.py b/tests/integration/test_storage_plugin.py new file mode 100644 index 0000000..2ab21d2 --- /dev/null +++ b/tests/integration/test_storage_plugin.py @@ -0,0 +1,45 @@ +# Copyright (c) 2014-2020 Cloudify Platform Ltd. All rights reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from tests.integration import TestCase, wait_for_task, fail_guard + + +class VolumeTestCase(TestCase): + def setUp(self): + super(VolumeTestCase, self).setUp() + + @fail_guard + def test_new(self): + self.init('volume_new.yaml') + self.install() + self.uninstall() + + @fail_guard + def test_use_external(self): + status, disk = self.vca_client.add_disk( + self.conf.vdc, self.conf.volume_name, self.conf.volume_size_Mb) + self.init('volume_use_external.yaml') + self.install() + self.uninstall() + self.vca_client = self.get_client() + status, task = self.vca_client.delete_disk( + self.conf.vdc, self.conf.volume_name) + if status: + wait_for_task(self.vca_client, task) + + @fail_guard + def test_attach(self): + self.init('volume_attach.yaml') + self.install() + self.uninstall() diff --git a/tests/syntax_check.py b/tests/syntax_check.py index 56db7b9..2dcfa9a 100644 --- a/tests/syntax_check.py +++ b/tests/syntax_check.py @@ -1,8 +1,21 @@ +# Copyright (c) 2015-2020 Cloudify Platform Ltd. All rights reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + # Simple syntax check for blueprints and json examples import yaml yaml_files = ['../examples/blueprint.yaml', - '../manager_blueprint/vcloud-manager-blueprint.yaml', '../plugin.yaml'] diff --git a/tests/unittests/test_mock_base.py b/tests/unittests/test_mock_base.py index 6de4569..148dfc7 100644 --- a/tests/unittests/test_mock_base.py +++ b/tests/unittests/test_mock_base.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved +# Copyright (c) 2014-2020 Cloudify Platform Ltd. All rights reserved # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -8,14 +8,39 @@ # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, -# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# * See the License for the specific language governing permissions and -# * limitations under the License. +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. import mock import unittest from cloudify import mocks as cfy_mocks -from network_plugin import BUSY_MESSAGE, NAT_ROUTED +import vcloud_network_plugin +from cloudify.state import current_ctx +vcloud_network_plugin.GATEWAY_TRY_COUNT = 2 +vcloud_network_plugin.GATEWAY_TIMEOUT = 1 + + +class MockToscaCloudifyContext(cfy_mocks.MockCloudifyContext): + """updated mock for use with tosca""" + + _local = False + + @property + def local(self): + return self._local + + _internal = None + + @property + def internal(self): + return self._internal + + _nodes = None + + @property + def nodes(self): + return self._nodes class TestBase(unittest.TestCase): @@ -44,7 +69,7 @@ def set_services_conf_result(self, gateway, result): def set_gateway_busy(self, gateway): message = gateway.response.content message = message.replace( - self.ERROR_PLACE, BUSY_MESSAGE + self.ERROR_PLACE, vcloud_network_plugin.BUSY_MESSAGE ) gateway.response.content = message @@ -56,14 +81,27 @@ def prepare_retry(self, ctx): return_value=None ) - def check_retry_realy_called(self, ctx): + def check_retry_realy_called(self, ctx, message=None, timeout=None): """ check that we really call retry """ + if not message: + message = 'Waiting for gateway.' + if not timeout: + timeout = vcloud_network_plugin.GATEWAY_TIMEOUT ctx.operation.retry.assert_called_with( - message='Waiting for gateway.', - retry_after=10 + message=message, + retry_after=timeout + ) + + def prepere_gatway_busy_retry(self, fake_client, fake_ctx): + """any operation for save gateway settings will return False""" + gateway = fake_client._vdc_gateway + self.set_gateway_busy(gateway) + self.set_services_conf_result( + fake_client._vdc_gateway, None ) + self.prepare_retry(fake_ctx) def generate_gateway( self, vdc_name="vdc", vms_networks=None, vdc_networks=None @@ -107,6 +145,7 @@ def generate_gateway( gate.deallocate_public_ip = mock.MagicMock(return_value=None) # public ips not exist gate.get_public_ips = mock.MagicMock(return_value=[]) + gate.is_busy = mock.MagicMock(return_value=False) return gate def generate_fake_client_network( @@ -171,7 +210,9 @@ def set_network_routed_in_client(self, fake_client): """ set any network as routed """ - network = self.generate_fake_client_network(NAT_ROUTED) + network = self.generate_fake_client_network( + vcloud_network_plugin.NAT_ROUTED + ) fake_client.get_network = mock.MagicMock(return_value=network) def generate_fake_client_disk(self, name="some_disk"): @@ -297,7 +338,10 @@ def generate_vca(self): def generate_vapp(self, vms_networks=None): def _get_vms_network_info(): - return [vms_networks] + if vms_networks: + return [vms_networks] + else: + return [[]] vapp = mock.Mock() vapp.me = mock.Mock() @@ -316,32 +360,38 @@ def _get_vms_network_info(): vapp.modify_vm_cpu = mock.MagicMock( return_value=None ) + vapp.modify_vm_name = mock.MagicMock( + return_value=None + ) + return vapp def generate_relation_context(self): source = mock.Mock() source.node = mock.Mock() + source.node.properties = {} target = mock.Mock() target.node = mock.Mock() + target.node.properties = {} target.instance.runtime_properties = {} - fake_ctx = cfy_mocks.MockCloudifyContext( + fake_ctx = MockToscaCloudifyContext( source=source, target=target ) return fake_ctx + def generate_relation_context_with_current_ctx(self): + # generate new relation context with save such context + # to current context that requed by 3.4 cloudify common plugin + # changes + fake_ctx = self.generate_relation_context() + current_ctx.set(fake_ctx) + return fake_ctx + def generate_node_context( self, relation_node_properties=None, properties=None, runtime_properties=None ): - class MockInstanceContext(cfy_mocks.MockNodeInstanceContext): - - self._relationships = None - - @property - def relationships(self): - return self._relationships - if not properties: properties = { 'management_network': '_management_network', @@ -353,7 +403,7 @@ def relationships(self): runtime_properties = { 'vcloud_vapp_name': 'vapp_name' } - fake_ctx = cfy_mocks.MockCloudifyContext( + fake_ctx = MockToscaCloudifyContext( node_id='test', node_name='test', properties=properties, @@ -361,8 +411,8 @@ def relationships(self): runtime_properties=runtime_properties ) - fake_ctx._instance = MockInstanceContext( - fake_ctx.instance._id, fake_ctx.instance._runtime_properties + fake_ctx._instance = cfy_mocks.MockNodeInstanceContext( + fake_ctx.instance.id, fake_ctx.instance.runtime_properties ) relationship = self.generate_relation_context() @@ -370,3 +420,20 @@ def relationships(self): fake_ctx.instance._relationships = [relationship] return fake_ctx + + def generate_node_context_with_current_ctx( + self, relation_node_properties=None, properties=None, + runtime_properties=None + ): + # generate new node context with save such context + # to current context that requed by 3.4 cloudify common plugin + # changes + fake_ctx = self.generate_node_context( + relation_node_properties, properties, + runtime_properties + ) + current_ctx.set(fake_ctx) + return fake_ctx + + def tearDown(self): + current_ctx.clear() diff --git a/tests/unittests/test_mock_network_plugin.py b/tests/unittests/test_mock_network_plugin.py index 0d7b1ad..e83038a 100644 --- a/tests/unittests/test_mock_network_plugin.py +++ b/tests/unittests/test_mock_network_plugin.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved +# Copyright (c) 2014-2020 Cloudify Platform Ltd. All rights reserved # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -8,9 +8,9 @@ # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, -# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# * See the License for the specific language governing permissions and -# * limitations under the License. +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. import mock import unittest @@ -18,8 +18,8 @@ from cloudify import exceptions as cfy_exc from tests.unittests import test_mock_base -import network_plugin -from network_plugin import utils +import vcloud_network_plugin +from vcloud_network_plugin import utils import vcloud_plugin_common @@ -38,12 +38,12 @@ def test_get_vm_ip(self): } } fake_ctx._source.instance.runtime_properties = { - network_plugin.VCLOUD_VAPP_NAME: "name" + vcloud_network_plugin.VCLOUD_VAPP_NAME: "name" } # empty connections/no connection name with mock.patch('vcloud_plugin_common.ctx', fake_ctx): with self.assertRaises(cfy_exc.NonRecoverableError): - network_plugin.get_vm_ip( + vcloud_network_plugin.get_vm_ip( fake_client, fake_ctx, fake_client._vdc_gateway ) vms_networks = [{ @@ -56,14 +56,14 @@ def test_get_vm_ip(self): # not routed with mock.patch('vcloud_plugin_common.ctx', fake_ctx): with self.assertRaises(cfy_exc.NonRecoverableError): - network_plugin.get_vm_ip( + vcloud_network_plugin.get_vm_ip( fake_client, fake_ctx, fake_client._vdc_gateway ) # routed self.set_network_routed_in_client(fake_client) with mock.patch('vcloud_plugin_common.ctx', fake_ctx): self.assertEqual( - network_plugin.get_vm_ip( + vcloud_network_plugin.get_vm_ip( fake_client, fake_ctx, fake_client._vdc_gateway ), '1.1.1.1' @@ -74,14 +74,14 @@ def test_get_vm_ip(self): ) with mock.patch('vcloud_plugin_common.ctx', fake_ctx): with self.assertRaises(cfy_exc.NonRecoverableError): - network_plugin.get_vm_ip( + vcloud_network_plugin.get_vm_ip( fake_client, fake_ctx, fake_client._vdc_gateway ) # no vapp fake_client.get_vapp = mock.MagicMock(return_value=None) with mock.patch('vcloud_plugin_common.ctx', fake_ctx): with self.assertRaises(cfy_exc.NonRecoverableError): - network_plugin.get_vm_ip( + vcloud_network_plugin.get_vm_ip( fake_client, fake_ctx, fake_client._vdc_gateway ) @@ -92,7 +92,7 @@ def test_collectAssignedIps(self): """ # empty gateway self.assertEqual( - network_plugin.collectAssignedIps(None), + vcloud_network_plugin.collectAssignedIps(None), set([]) ) # snat @@ -104,9 +104,9 @@ def test_collectAssignedIps(self): return_value=[rule_inlist] ) self.assertEqual( - network_plugin.collectAssignedIps(gateway), + vcloud_network_plugin.collectAssignedIps(gateway), set( - [network_plugin.AssignedIPs( + [vcloud_network_plugin.AssignedIPs( external='internal', internal='external' )] ) @@ -120,9 +120,9 @@ def test_collectAssignedIps(self): rule_inlist ]) self.assertEqual( - network_plugin.collectAssignedIps(gateway), + vcloud_network_plugin.collectAssignedIps(gateway), set( - [network_plugin.AssignedIPs( + [vcloud_network_plugin.AssignedIPs( external='external', internal='internal' )] ) @@ -142,7 +142,7 @@ def test_getFreeIP(self): ) gateway.get_nat_rules = mock.MagicMock(return_value=[rule_inlist]) self.assertEqual( - network_plugin.getFreeIP(gateway), + vcloud_network_plugin.getFreeIP(gateway), '10.18.1.2' ) # no free ips @@ -150,7 +150,7 @@ def test_getFreeIP(self): '10.18.1.1' ]) with self.assertRaises(cfy_exc.NonRecoverableError): - network_plugin.getFreeIP(gateway) + vcloud_network_plugin.getFreeIP(gateway) def test_del_ondemand_public_ip(self): """ @@ -161,10 +161,12 @@ def test_del_ondemand_public_ip(self): fake_ctx = self.generate_node_context() # can't deallocate ip gateway.deallocate_public_ip = mock.MagicMock(return_value=None) - with self.assertRaises(cfy_exc.NonRecoverableError): - network_plugin.del_ondemand_public_ip( - fake_client, gateway, '127.0.0.1', fake_ctx - ) + with mock.patch( + 'vcloud_network_plugin.wait_for_gateway', mock.MagicMock() + ): + with self.assertRaises(cfy_exc.NonRecoverableError): + vcloud_network_plugin.del_ondemand_public_ip( + fake_client, gateway, '127.0.0.1', fake_ctx) gateway.deallocate_public_ip.assert_called_with('127.0.0.1') # successfully dropped public ip gateway.deallocate_public_ip = mock.MagicMock( @@ -172,9 +174,12 @@ def test_del_ondemand_public_ip(self): vcloud_plugin_common.TASK_STATUS_SUCCESS ) ) - network_plugin.del_ondemand_public_ip( - fake_client, gateway, '127.0.0.1', fake_ctx - ) + with mock.patch('vcloud_plugin_common.ctx', fake_ctx): + with mock.patch( + 'vcloud_network_plugin.wait_for_gateway', mock.MagicMock() + ): + vcloud_network_plugin.del_ondemand_public_ip( + fake_client, gateway, '127.0.0.1', fake_ctx) def test_save_gateway_configuration(self): """ @@ -183,41 +188,39 @@ def test_save_gateway_configuration(self): """ gateway = self.generate_gateway() fake_client = self.generate_client() + fake_ctx = self.generate_node_context() # cant save configuration - error in first call self.set_services_conf_result( gateway, None ) + fake_ctx = self.generate_node_context() with self.assertRaises(cfy_exc.NonRecoverableError): - network_plugin.save_gateway_configuration( - gateway, fake_client - ) + vcloud_network_plugin.save_gateway_configuration( + gateway, fake_client, fake_ctx) # error in status self.set_services_conf_result( gateway, vcloud_plugin_common.TASK_STATUS_ERROR ) with self.assertRaises(cfy_exc.NonRecoverableError): - network_plugin.save_gateway_configuration( - gateway, fake_client - ) + with mock.patch('vcloud_plugin_common.ctx', fake_ctx): + vcloud_network_plugin.save_gateway_configuration( + gateway, fake_client, fake_ctx) # everything fine self.set_services_conf_result( gateway, vcloud_plugin_common.TASK_STATUS_SUCCESS ) - self.assertTrue( - network_plugin.save_gateway_configuration( - gateway, fake_client - ) - ) + with mock.patch('vcloud_plugin_common.ctx', fake_ctx): + self.assertTrue( + vcloud_network_plugin.save_gateway_configuration( + gateway, fake_client, fake_ctx)) # server busy self.set_services_conf_result( gateway, None ) self.set_gateway_busy(gateway) self.assertFalse( - network_plugin.save_gateway_configuration( - gateway, fake_client - ) - ) + vcloud_network_plugin.save_gateway_configuration( + gateway, fake_client, fake_ctx)) def test_is_network_routed(self): """ @@ -237,7 +240,7 @@ def test_is_network_routed(self): network = self.generate_fake_client_network("not_routed") fake_client.get_network = mock.MagicMock(return_value=network) self.assertFalse( - network_plugin.is_network_routed( + vcloud_network_plugin.is_network_routed( fake_client, 'network_name', fake_client._vdc_gateway ) @@ -245,14 +248,14 @@ def test_is_network_routed(self): # nat routed self.set_network_routed_in_client(fake_client) self.assertTrue( - network_plugin.is_network_routed( + vcloud_network_plugin.is_network_routed( fake_client, 'network_name', fake_client._vdc_gateway ) ) # nat routed but for other network self.assertFalse( - network_plugin.is_network_routed( + vcloud_network_plugin.is_network_routed( fake_client, 'other_network_name', fake_client._vdc_gateway ) @@ -263,13 +266,13 @@ def test_get_vapp_name(self): check get vapp name """ self.assertEqual( - network_plugin.get_vapp_name({ - network_plugin.VCLOUD_VAPP_NAME: "name" + vcloud_network_plugin.get_vapp_name({ + vcloud_network_plugin.VCLOUD_VAPP_NAME: "name" }), "name" ) with self.assertRaises(cfy_exc.NonRecoverableError): - network_plugin.get_vapp_name({ + vcloud_network_plugin.get_vapp_name({ "aa": "aaa" }) @@ -281,7 +284,7 @@ def test_check_port(self): utils.check_port(10) # port int to big with self.assertRaises(cfy_exc.NonRecoverableError): - utils.check_port(utils.MAX_PORT_NUMBER+1) + utils.check_port(utils.MAX_PORT_NUMBER + 1) # port any utils.check_port('any') # port not any and not int @@ -303,12 +306,12 @@ def test_CheckAssignedExternalIp(self): return_value=[rule_inlist] ) # free ip - network_plugin.CheckAssignedExternalIp( + vcloud_network_plugin.CheckAssignedExternalIp( '10.10.1.2', gateway ) # assigned ip with self.assertRaises(cfy_exc.NonRecoverableError): - network_plugin.CheckAssignedExternalIp( + vcloud_network_plugin.CheckAssignedExternalIp( '10.1.1.1', gateway ) @@ -327,12 +330,12 @@ def test_CheckAssignedInternalIp(self): return_value=[rule_inlist] ) # free ip - network_plugin.CheckAssignedInternalIp( + vcloud_network_plugin.CheckAssignedInternalIp( '123.1.1.2', gateway ) # assigned ip with self.assertRaises(cfy_exc.NonRecoverableError): - network_plugin.CheckAssignedInternalIp( + vcloud_network_plugin.CheckAssignedInternalIp( '123.1.1.1', gateway ) @@ -345,7 +348,7 @@ def test_get_gateway(self): fake_ctx = self.generate_node_context() with mock.patch('vcloud_plugin_common.ctx', fake_ctx): self.assertEqual( - network_plugin.get_gateway( + vcloud_network_plugin.get_gateway( fake_client, 'test name' ), fake_client._vdc_gateway @@ -359,7 +362,7 @@ def test_get_gateway(self): fake_client.get_gateway = mock.MagicMock(return_value=None) with mock.patch('vcloud_plugin_common.ctx', fake_ctx): with self.assertRaises(cfy_exc.NonRecoverableError): - network_plugin.get_gateway( + vcloud_network_plugin.get_gateway( fake_client, 'test name' ) @@ -378,7 +381,7 @@ def test_get_network(self): ) with mock.patch('vcloud_plugin_common.ctx', fake_ctx): self.assertEqual( - network_plugin.get_network( + vcloud_network_plugin.get_network( fake_client, 'test name' ), fake_network @@ -392,7 +395,7 @@ def test_get_network(self): fake_client.get_network = mock.MagicMock(return_value=None) with mock.patch('vcloud_plugin_common.ctx', fake_ctx): with self.assertRaises(cfy_exc.NonRecoverableError): - network_plugin.get_network( + vcloud_network_plugin.get_network( fake_client, 'test name' ) # worse case = nework == None @@ -400,7 +403,7 @@ def test_get_network(self): fake_ctx = self.generate_node_context() with mock.patch('vcloud_plugin_common.ctx', fake_ctx): with self.assertRaises(cfy_exc.NonRecoverableError): - network_plugin.get_network( + vcloud_network_plugin.get_network( fake_client, None ) @@ -410,12 +413,12 @@ def test_get_network_name(self): """ # external without resource_id with self.assertRaises(cfy_exc.NonRecoverableError): - network_plugin.get_network_name({ + vcloud_network_plugin.get_network_name({ 'use_external_resource': True }) # exteranal with resource_id self.assertEqual( - network_plugin.get_network_name({ + vcloud_network_plugin.get_network_name({ 'use_external_resource': True, 'resource_id': 'some_text' }), @@ -423,17 +426,17 @@ def test_get_network_name(self): ) # internal, without network with self.assertRaises(cfy_exc.NonRecoverableError): - network_plugin.get_network_name({}) + vcloud_network_plugin.get_network_name({}) # network without name with self.assertRaises(cfy_exc.NonRecoverableError): - network_plugin.get_network_name({ + vcloud_network_plugin.get_network_name({ 'network': { 'name': None } }) # good case self.assertEqual( - network_plugin.get_network_name({ + vcloud_network_plugin.get_network_name({ 'network': { 'name': 'good_text' } @@ -466,7 +469,7 @@ def test_get_ondemand_public_ip(self): ) with mock.patch('vcloud_plugin_common.ctx', fake_ctx): with self.assertRaises(cfy_exc.NonRecoverableError): - network_plugin.get_ondemand_public_ip( + vcloud_network_plugin.get_ondemand_public_ip( fake_client, fake_client._vdc_gateway, fake_ctx ) # success allocate ips, but empty list of ips @@ -477,7 +480,7 @@ def test_get_ondemand_public_ip(self): ) with mock.patch('vcloud_plugin_common.ctx', fake_ctx): with self.assertRaises(cfy_exc.NonRecoverableError): - network_plugin.get_ondemand_public_ip( + vcloud_network_plugin.get_ondemand_public_ip( fake_client, fake_client._vdc_gateway, fake_ctx ) # exist some new ip @@ -490,7 +493,7 @@ def test_get_ondemand_public_ip(self): ) with mock.patch('vcloud_plugin_common.ctx', fake_ctx): self.assertEqual( - network_plugin.get_ondemand_public_ip( + vcloud_network_plugin.get_ondemand_public_ip( fake_client, fake_client._vdc_gateway, fake_ctx ), '1.1.1.1' @@ -513,7 +516,7 @@ def test_get_public_ip_subscription(self): fake_ctx = self.generate_node_context() # for subscription we dont use client self.assertEqual( - network_plugin.get_public_ip( + vcloud_network_plugin.get_public_ip( None, gateway, vcloud_plugin_common.SUBSCRIPTION_SERVICE_TYPE, fake_ctx ), @@ -544,7 +547,7 @@ def test_get_public_ip_ondemand(self): ) with mock.patch('vcloud_plugin_common.ctx', fake_ctx): self.assertEqual( - network_plugin.get_public_ip( + vcloud_network_plugin.get_public_ip( fake_client, fake_client._vdc_gateway, vcloud_plugin_common.ONDEMAND_SERVICE_TYPE, fake_ctx ), @@ -557,13 +560,13 @@ def test_check_ip(self): """ # wrong type with self.assertRaises(cfy_exc.NonRecoverableError): - network_plugin.check_ip({'wrong': None}) + vcloud_network_plugin.check_ip({'wrong': None}) # wrong value with self.assertRaises(cfy_exc.NonRecoverableError): - network_plugin.check_ip("1.1.1.400") + vcloud_network_plugin.check_ip("1.1.1.400") # good case self.assertEqual( - network_plugin.check_ip("1.1.1.40"), + vcloud_network_plugin.check_ip("1.1.1.40"), "1.1.1.40" ) @@ -573,11 +576,11 @@ def test_is_valid_ip_range(self): """ # wrong range self.assertFalse( - network_plugin.is_valid_ip_range("1.1.1.50", "1.1.1.40") + vcloud_network_plugin.is_valid_ip_range("1.1.1.50", "1.1.1.40") ) # good case self.assertTrue( - network_plugin.is_valid_ip_range("1.1.1.40", "1.1.1.50") + vcloud_network_plugin.is_valid_ip_range("1.1.1.40", "1.1.1.50") ) def test_is_network_exists(self): @@ -592,7 +595,7 @@ def test_is_network_exists(self): ) with mock.patch('vcloud_plugin_common.ctx', fake_ctx): self.assertTrue( - network_plugin.is_network_exists(fake_client, 'test') + vcloud_network_plugin.is_network_exists(fake_client, 'test') ) fake_client.get_network.assert_called_with('vdc_name', 'test') # network not exist @@ -603,7 +606,7 @@ def test_is_network_exists(self): ) with mock.patch('vcloud_plugin_common.ctx', fake_ctx): self.assertFalse( - network_plugin.is_network_exists(fake_client, 'test') + vcloud_network_plugin.is_network_exists(fake_client, 'test') ) def test_is_ips_in_same_subnet(self): @@ -612,13 +615,13 @@ def test_is_ips_in_same_subnet(self): """ # ips from several networks self.assertFalse( - network_plugin.is_ips_in_same_subnet( + vcloud_network_plugin.is_ips_in_same_subnet( ['123.11.1.1', '123.11.3.1'], 24 ) ) # ips from same network self.assertTrue( - network_plugin.is_ips_in_same_subnet( + vcloud_network_plugin.is_ips_in_same_subnet( ['123.11.1.1', '123.11.1.1'], 24 ) ) @@ -630,14 +633,14 @@ def test_is_separate_ranges(self): IPRange = collections.namedtuple('IPRange', 'start end') # positive case self.assertTrue( - network_plugin.is_separate_ranges( + vcloud_network_plugin.is_separate_ranges( IPRange(start='1.1.1.1', end='1.1.1.11'), IPRange(start='1.1.1.12', end='1.1.1.23') ) ) # negative case self.assertFalse( - network_plugin.is_separate_ranges( + vcloud_network_plugin.is_separate_ranges( IPRange(start='1.1.1.1', end='1.1.1.15'), IPRange(start='1.1.1.9', end='1.1.1.23') ) diff --git a/tests/unittests/test_mock_network_plugin_floatingip.py b/tests/unittests/test_mock_network_plugin_floatingip.py index 34fcca5..7aa338c 100644 --- a/tests/unittests/test_mock_network_plugin_floatingip.py +++ b/tests/unittests/test_mock_network_plugin_floatingip.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved +# Copyright (c) 2014-2020 Cloudify Platform Ltd. All rights reserved # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -8,74 +8,74 @@ # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, -# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# * See the License for the specific language governing permissions and -# * limitations under the License. +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. import mock import unittest from tests.unittests import test_mock_base -from network_plugin import floatingip +from vcloud_network_plugin import floatingip from cloudify import exceptions as cfy_exc -import network_plugin +import vcloud_network_plugin import vcloud_plugin_common class NetworkPluginFloatingIpMockTestCase(test_mock_base.TestBase): def test_add_nat_rule_snat(self): - fake_ctx = self.generate_node_context() - with mock.patch('network_plugin.floatingip.ctx', fake_ctx): - gateway = mock.Mock() - gateway._add_nat_rule = mock.MagicMock(return_value=None) - floatingip._add_nat_rule( - gateway, 'SNAT', 'internal', 'external' - ) - gateway.add_nat_rule.assert_called_with( - 'SNAT', 'internal', 'any', 'external', 'any', 'any' - ) + fake_ctx = self.generate_node_context_with_current_ctx() + + gateway = mock.Mock() + gateway._add_nat_rule = mock.MagicMock(return_value=None) + floatingip._add_nat_rule( + fake_ctx, gateway, 'SNAT', 'internal', 'external' + ) + gateway.add_nat_rule.assert_called_with( + 'SNAT', 'internal', 'any', 'external', 'any', 'any' + ) def test_add_nat_rule_dnat(self): - fake_ctx = self.generate_node_context() - with mock.patch('network_plugin.floatingip.ctx', fake_ctx): - gateway = mock.Mock() - gateway._add_nat_rule = mock.MagicMock(return_value=None) - floatingip._add_nat_rule( - gateway, 'DNAT', 'internal', 'external' - ) - gateway.add_nat_rule.assert_called_with( - 'DNAT', 'internal', 'any', 'external', 'any', 'any' - ) + fake_ctx = self.generate_node_context_with_current_ctx() + + gateway = mock.Mock() + gateway._add_nat_rule = mock.MagicMock(return_value=None) + floatingip._add_nat_rule( + fake_ctx, gateway, 'DNAT', 'internal', 'external' + ) + gateway.add_nat_rule.assert_called_with( + 'DNAT', 'internal', 'any', 'external', 'any', 'any' + ) def test_del_nat_rule_snat(self): - fake_ctx = self.generate_node_context() - with mock.patch('network_plugin.floatingip.ctx', fake_ctx): - gateway = mock.Mock() - gateway.del_nat_rule = mock.MagicMock(return_value=None) - floatingip._del_nat_rule( - gateway, 'SNAT', 'internal', 'external' - ) - gateway.del_nat_rule.assert_called_with( - 'SNAT', 'internal', 'any', 'external', 'any', 'any' - ) + fake_ctx = self.generate_node_context_with_current_ctx() + + gateway = mock.Mock() + gateway.del_nat_rule = mock.MagicMock(return_value=None) + floatingip._del_nat_rule( + fake_ctx, gateway, 'SNAT', 'internal', 'external' + ) + gateway.del_nat_rule.assert_called_with( + 'SNAT', 'internal', 'any', 'external', 'any', 'any' + ) def test_del_nat_rule_dnat(self): - fake_ctx = self.generate_node_context() - with mock.patch('network_plugin.floatingip.ctx', fake_ctx): - gateway = mock.Mock() - gateway.del_nat_rule = mock.MagicMock(return_value=None) - floatingip._del_nat_rule( - gateway, 'DNAT', 'internal', 'external' - ) - gateway.del_nat_rule.assert_called_with( - 'DNAT', 'internal', 'any', 'external', 'any', 'any' - ) + fake_ctx = self.generate_node_context_with_current_ctx() + + gateway = mock.Mock() + gateway.del_nat_rule = mock.MagicMock(return_value=None) + floatingip._del_nat_rule( + fake_ctx, gateway, 'DNAT', 'internal', 'external' + ) + gateway.del_nat_rule.assert_called_with( + 'DNAT', 'internal', 'any', 'external', 'any', 'any' + ) def test_creation_validation(self): fake_client = self.generate_client() # no floating_ip - fake_ctx = self.generate_node_context( + fake_ctx = self.generate_node_context_with_current_ctx( properties={ 'vcloud_config': { 'vdc': 'vdc_name' @@ -87,9 +87,9 @@ def test_creation_validation(self): mock.MagicMock(return_value=fake_client) ): with self.assertRaises(cfy_exc.NonRecoverableError): - floatingip.creation_validation(ctx=fake_ctx) + floatingip.creation_validation(ctx=fake_ctx, vca_client=None) # no edge gateway - fake_ctx = self.generate_node_context( + fake_ctx = self.generate_node_context_with_current_ctx( properties={ 'vcloud_config': { 'vdc': 'vdc_name' @@ -104,16 +104,16 @@ def test_creation_validation(self): mock.MagicMock(return_value=fake_client) ): with self.assertRaises(cfy_exc.NonRecoverableError): - floatingip.creation_validation(ctx=fake_ctx) + floatingip.creation_validation(ctx=fake_ctx, vca_client=None) # with edge gateway, but wrong ip - fake_ctx = self.generate_node_context( + fake_ctx = self.generate_node_context_with_current_ctx( properties={ 'vcloud_config': { 'vdc': 'vdc_name' }, 'floatingip': { 'edge_gateway': 'gateway', - network_plugin.PUBLIC_IP: 'some' + vcloud_network_plugin.PUBLIC_IP: 'some' } } ) @@ -122,17 +122,20 @@ def test_creation_validation(self): mock.MagicMock(return_value=fake_client) ): with self.assertRaises(cfy_exc.NonRecoverableError): - floatingip.creation_validation(ctx=fake_ctx) + floatingip.creation_validation(ctx=fake_ctx, vca_client=None) # with edge gateway, ip from pool - fake_ctx = self.generate_node_context(properties={ - 'vcloud_config': { - 'vdc': 'vdc_name' - }, - 'floatingip': { - 'edge_gateway': 'gateway', - 'service_type': vcloud_plugin_common.ONDEMAND_SERVICE_TYPE + fake_ctx = self.generate_node_context_with_current_ctx( + properties={ + 'vcloud_config': { + 'vdc': 'vdc_name' + }, + 'floatingip': { + 'edge_gateway': 'gateway', + 'service_type': + vcloud_plugin_common.ONDEMAND_SERVICE_TYPE + } } - }) + ) fake_client._vdc_gateway.get_public_ips = mock.MagicMock( return_value=['10.18.1.1'] ) @@ -140,18 +143,21 @@ def test_creation_validation(self): 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): - floatingip.creation_validation(ctx=fake_ctx) + floatingip.creation_validation(ctx=fake_ctx, vca_client=None) # with some free ip - fake_ctx = self.generate_node_context(properties={ - 'vcloud_config': { - 'vdc': 'vdc_name' - }, - 'floatingip': { - 'edge_gateway': 'gateway', - network_plugin.PUBLIC_IP: '10.10.1.2', - 'service_type': vcloud_plugin_common.SUBSCRIPTION_SERVICE_TYPE + fake_ctx = self.generate_node_context_with_current_ctx( + properties={ + 'vcloud_config': { + 'vdc': 'vdc_name' + }, + 'floatingip': { + 'edge_gateway': 'gateway', + vcloud_network_plugin.PUBLIC_IP: '10.10.1.2', + 'service_type': + vcloud_plugin_common.SUBSCRIPTION_SERVICE_TYPE + } } - }) + ) fake_client._vdc_gateway.get_public_ips = mock.MagicMock(return_value=[ '10.1.1.1', '10.1.1.2' ]) @@ -165,7 +171,7 @@ def test_creation_validation(self): 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): - floatingip.creation_validation(ctx=fake_ctx) + floatingip.creation_validation(ctx=fake_ctx, vca_client=None) def generate_client_and_context_floating_ip( self, service_type=vcloud_plugin_common.ONDEMAND_SERVICE_TYPE @@ -184,7 +190,7 @@ def generate_client_and_context_floating_ip( vcloud_plugin_common.TASK_STATUS_SUCCESS ) # ctx - fake_ctx = self.generate_relation_context() + fake_ctx = self.generate_relation_context_with_current_ctx() fake_ctx._source.node.properties = { 'vcloud_config': { 'service_type': service_type, @@ -214,12 +220,9 @@ def test_floatingip_operation_delete(self): with mock.patch( 'vcloud_plugin_common.ctx', fake_ctx ): - with mock.patch( - 'network_plugin.floatingip.ctx', fake_ctx - ): - floatingip._floatingip_operation( - network_plugin.DELETE, fake_client, fake_ctx - ) + floatingip._floatingip_operation( + vcloud_network_plugin.DELETE, fake_client, ctx=fake_ctx + ) # busy in save with ip in node_properties fake_client, fake_ctx = self.generate_client_and_context_floating_ip() self.set_services_conf_result( @@ -230,19 +233,15 @@ def test_floatingip_operation_delete(self): fake_ctx._target.node.properties = { 'floatingip': { 'edge_gateway': 'gateway', - network_plugin.PUBLIC_IP: '10.10.1.2' + vcloud_network_plugin.PUBLIC_IP: '10.10.1.2' } } with mock.patch( 'vcloud_plugin_common.ctx', fake_ctx ): - with mock.patch( - 'network_plugin.floatingip.ctx', fake_ctx - ): - floatingip._floatingip_operation( - network_plugin.DELETE, fake_client, fake_ctx - ) - self.check_retry_realy_called(fake_ctx) + self.assertFalse(floatingip._floatingip_operation( + vcloud_network_plugin.DELETE, fake_client, ctx=fake_ctx + )) # busy in save with ip in runtime_properties fake_client, fake_ctx = self.generate_client_and_context_floating_ip() self.set_services_conf_result( @@ -256,36 +255,29 @@ def test_floatingip_operation_delete(self): } } fake_ctx._target.instance.runtime_properties = { - network_plugin.PUBLIC_IP: '10.10.1.2' + vcloud_network_plugin.PUBLIC_IP: '10.10.1.2' } with mock.patch( 'vcloud_plugin_common.ctx', fake_ctx ): - with mock.patch( - 'network_plugin.floatingip.ctx', fake_ctx - ): - floatingip._floatingip_operation( - network_plugin.DELETE, fake_client, fake_ctx - ) - self.check_retry_realy_called(fake_ctx) + self.assertFalse(floatingip._floatingip_operation( + vcloud_network_plugin.DELETE, fake_client, ctx=fake_ctx + )) # unknow operation fake_client, fake_ctx = self.generate_client_and_context_floating_ip() fake_ctx._target.node.properties = { 'floatingip': { 'edge_gateway': 'gateway', - network_plugin.PUBLIC_IP: '10.10.1.2' + vcloud_network_plugin.PUBLIC_IP: '10.10.1.2' } } with mock.patch( 'vcloud_plugin_common.ctx', fake_ctx ): - with mock.patch( - 'network_plugin.floatingip.ctx', fake_ctx - ): - with self.assertRaises(cfy_exc.NonRecoverableError): - floatingip._floatingip_operation( - "unknow", fake_client, fake_ctx - ) + with self.assertRaises(cfy_exc.NonRecoverableError): + floatingip._floatingip_operation( + "unknow", fake_client, ctx=fake_ctx + ) # delete to end, ondemand fake_client, fake_ctx = self.generate_client_and_context_floating_ip() fake_ctx._target.node.properties = { @@ -294,7 +286,13 @@ def test_floatingip_operation_delete(self): } } fake_ctx._target.instance.runtime_properties = { - network_plugin.PUBLIC_IP: '10.10.1.2' + vcloud_network_plugin.PUBLIC_IP: '10.10.1.2' + + } + fake_ctx._source.instance.runtime_properties = { + 'vcloud_vapp_name': 'vapp', + 'ssh_port': 22, + 'ssh_public_ip': '1.2.3.1' } fake_client._vdc_gateway.deallocate_public_ip = mock.MagicMock( return_value=self.generate_task( @@ -304,15 +302,12 @@ def test_floatingip_operation_delete(self): with mock.patch( 'vcloud_plugin_common.ctx', fake_ctx ): - with mock.patch( - 'network_plugin.floatingip.ctx', fake_ctx - ): - floatingip._floatingip_operation( - network_plugin.DELETE, fake_client, fake_ctx - ) + floatingip._floatingip_operation( + vcloud_network_plugin.DELETE, fake_client, ctx=fake_ctx + ) runtime_properties = fake_ctx._target.instance.runtime_properties self.assertFalse( - network_plugin.PUBLIC_IP in runtime_properties + vcloud_network_plugin.PUBLIC_IP in runtime_properties ) # delete to end, subscription fake_client, fake_ctx = self.generate_client_and_context_floating_ip( @@ -324,7 +319,12 @@ def test_floatingip_operation_delete(self): } } fake_ctx._target.instance.runtime_properties = { - network_plugin.PUBLIC_IP: '10.10.1.2' + vcloud_network_plugin.PUBLIC_IP: '10.10.1.2' + } + fake_ctx._source.instance.runtime_properties = { + 'vcloud_vapp_name': 'vapp', + 'ssh_port': 22, + 'ssh_public_ip': '1.2.3.1' } fake_client._vdc_gateway.deallocate_public_ip = mock.MagicMock( return_value=self.generate_task( @@ -334,15 +334,12 @@ def test_floatingip_operation_delete(self): with mock.patch( 'vcloud_plugin_common.ctx', fake_ctx ): - with mock.patch( - 'network_plugin.floatingip.ctx', fake_ctx - ): - floatingip._floatingip_operation( - network_plugin.DELETE, fake_client, fake_ctx - ) + floatingip._floatingip_operation( + vcloud_network_plugin.DELETE, fake_client, ctx=fake_ctx + ) runtime_properties = fake_ctx._target.instance.runtime_properties self.assertFalse( - network_plugin.PUBLIC_IP in runtime_properties + vcloud_network_plugin.PUBLIC_IP in runtime_properties ) def test_disconnect_floatingip(self): @@ -353,24 +350,50 @@ def test_disconnect_floatingip(self): } } fake_ctx._target.instance.runtime_properties = { - network_plugin.PUBLIC_IP: '10.10.1.2' + vcloud_network_plugin.PUBLIC_IP: '10.10.1.2' + } + fake_ctx._source.instance.runtime_properties = { + 'vcloud_vapp_name': 'vapp', + 'ssh_port': 22, + 'ssh_public_ip': '1.2.3.1' + } + fake_ctx._source.node.properties = { + 'vcloud_config': { + 'edge_gateway': 'gateway', + 'vdc': 'vdc' + } } + fake_client._vdc_gateway.deallocate_public_ip = mock.MagicMock( return_value=self.generate_task( vcloud_plugin_common.TASK_STATUS_SUCCESS ) ) + # successfull with mock.patch( 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): - floatingip.disconnect_floatingip( - ctx=fake_ctx - ) + floatingip.disconnect_floatingip(ctx=fake_ctx, vca_client=None) runtime_properties = fake_ctx._target.instance.runtime_properties self.assertFalse( - network_plugin.PUBLIC_IP in runtime_properties + vcloud_network_plugin.PUBLIC_IP in runtime_properties ) + runtime_properties = fake_ctx._source.instance.runtime_properties + self.assertFalse( + vcloud_network_plugin.SSH_PORT in runtime_properties + ) + # with retry + fake_ctx._target.instance.runtime_properties = { + vcloud_network_plugin.PUBLIC_IP: '10.10.1.2' + } + with mock.patch( + 'vcloud_plugin_common.VcloudAirClient.get', + mock.MagicMock(return_value=fake_client) + ): + self.prepere_gatway_busy_retry(fake_client, fake_ctx) + floatingip.disconnect_floatingip(ctx=fake_ctx, vca_client=None) + self.check_retry_realy_called(fake_ctx) def test_connect_floatingip(self): """ @@ -382,31 +405,48 @@ def test_connect_floatingip(self): fake_ctx._target.node.properties = { 'floatingip': { 'edge_gateway': 'gateway', - network_plugin.PUBLIC_IP: '10.10.2.3' + vcloud_network_plugin.PUBLIC_IP: '10.10.2.3' + }} + fake_ctx._source.node.properties = { + 'vcloud_config': + { + 'edge_gateway': 'gateway', + 'vdc': 'vdc' } } fake_ctx._target.instance.runtime_properties = {} + fake_ctx._source.instance.runtime_properties = { + 'vcloud_vapp_name': 'vapp' + } fake_client._vdc_gateway.get_public_ips = mock.MagicMock(return_value=[ '10.18.1.1', '10.10.2.3' ]) fake_client._vdc_gateway.get_nat_rules = mock.MagicMock( return_value=[] ) + + # successfull with mock.patch( 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): - floatingip.connect_floatingip( - ctx=fake_ctx - ) + floatingip.connect_floatingip(ctx=fake_ctx, vca_client=None) runtime_properties = fake_ctx._target.instance.runtime_properties self.assertTrue( - network_plugin.PUBLIC_IP in runtime_properties + vcloud_network_plugin.PUBLIC_IP in runtime_properties ) self.assertEqual( - runtime_properties.get(network_plugin.PUBLIC_IP), + runtime_properties.get(vcloud_network_plugin.PUBLIC_IP), '10.10.2.3' ) + # with retry + with mock.patch( + 'vcloud_plugin_common.VcloudAirClient.get', + mock.MagicMock(return_value=fake_client) + ): + self.prepere_gatway_busy_retry(fake_client, fake_ctx) + floatingip.connect_floatingip(ctx=fake_ctx, vca_client=None) + self.check_retry_realy_called(fake_ctx) def test_floatingip_operation_create(self): """ @@ -422,6 +462,9 @@ def test_floatingip_operation_create(self): } } fake_ctx._target.instance.runtime_properties = {} + fake_ctx._source.instance.runtime_properties = { + 'vcloud_vapp_name': 'vapp' + } fake_client._vdc_gateway.get_public_ips = mock.MagicMock(return_value=[ '10.18.1.1' ]) @@ -431,18 +474,15 @@ def test_floatingip_operation_create(self): with mock.patch( 'vcloud_plugin_common.ctx', fake_ctx ): - with mock.patch( - 'network_plugin.floatingip.ctx', fake_ctx - ): - floatingip._floatingip_operation( - network_plugin.CREATE, fake_client, fake_ctx - ) + floatingip._floatingip_operation( + vcloud_network_plugin.CREATE, fake_client, ctx=fake_ctx + ) runtime_properties = fake_ctx._target.instance.runtime_properties self.assertTrue( - network_plugin.PUBLIC_IP in runtime_properties + vcloud_network_plugin.PUBLIC_IP in runtime_properties ) self.assertEqual( - runtime_properties.get(network_plugin.PUBLIC_IP), + runtime_properties.get(vcloud_network_plugin.PUBLIC_IP), '10.18.1.1' ) # with already explicitly defined ip @@ -452,10 +492,13 @@ def test_floatingip_operation_create(self): fake_ctx._target.node.properties = { 'floatingip': { 'edge_gateway': 'gateway', - network_plugin.PUBLIC_IP: '10.10.2.3' + vcloud_network_plugin.PUBLIC_IP: '10.10.2.3' } } fake_ctx._target.instance.runtime_properties = {} + fake_ctx._source.instance.runtime_properties = { + 'vcloud_vapp_name': 'vapp' + } fake_client._vdc_gateway.get_public_ips = mock.MagicMock(return_value=[ '10.18.1.1', '10.10.2.3' ]) @@ -465,20 +508,18 @@ def test_floatingip_operation_create(self): with mock.patch( 'vcloud_plugin_common.ctx', fake_ctx ): - with mock.patch( - 'network_plugin.floatingip.ctx', fake_ctx - ): - floatingip._floatingip_operation( - network_plugin.CREATE, fake_client, fake_ctx - ) + floatingip._floatingip_operation( + vcloud_network_plugin.CREATE, fake_client, ctx=fake_ctx + ) runtime_properties = fake_ctx._target.instance.runtime_properties self.assertTrue( - network_plugin.PUBLIC_IP in runtime_properties + vcloud_network_plugin.PUBLIC_IP in runtime_properties ) self.assertEqual( - runtime_properties.get(network_plugin.PUBLIC_IP), + runtime_properties.get(vcloud_network_plugin.PUBLIC_IP), '10.10.2.3' ) + if __name__ == '__main__': unittest.main() diff --git a/tests/unittests/test_mock_network_plugin_keypair.py b/tests/unittests/test_mock_network_plugin_keypair.py index e95ae26..66eb016 100644 --- a/tests/unittests/test_mock_network_plugin_keypair.py +++ b/tests/unittests/test_mock_network_plugin_keypair.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved +# Copyright (c) 2014-2020 Cloudify Platform Ltd. All rights reserved # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -8,35 +8,224 @@ # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, -# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# * See the License for the specific language governing permissions and -# * limitations under the License. +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. import unittest - +import mock from cloudify import exceptions as cfy_exc from tests.unittests import test_mock_base -from network_plugin import keypair +from vcloud_network_plugin import keypair class NetworkPluginKeyPairpMockTestCase(test_mock_base.TestBase): def test_creation_validation(self): # exist keyfile - fake_ctx = self.generate_node_context( + fake_ctx = self.generate_node_context_with_current_ctx( properties={ - 'private_key_path': __file__ + 'private_key': {'path': __file__} } ) keypair.creation_validation(ctx=fake_ctx) # not exist keyfile - fake_ctx = self.generate_node_context( + fake_ctx = self.generate_node_context_with_current_ctx( properties={ - 'private_key_path': __file__ + ".not_exist" + 'private_key': {'path': __file__ + ".not_exist"} } ) with self.assertRaises(cfy_exc.NonRecoverableError): keypair.creation_validation(ctx=fake_ctx) + def test_create(self): + patcher1 = mock.patch('vcloud_network_plugin.keypair._save_key_file', + mock.MagicMock()) + patcher2 = mock.patch('vcloud_network_plugin.keypair._generate_pair', + mock.MagicMock(return_value=('public', + 'private'))) + patcher3 = mock.patch('vcloud_network_plugin.keypair._create_path', + mock.MagicMock(return_value=( + '~/.ssh/test_private.key'))) + patcher1.start() + patcher2.start() + patcher3.start() + + fake_ctx = self.generate_node_context_with_current_ctx( + properties={'auto_generate': True, + 'private_key': {'create_file': True}}) + keypair.create(ctx=fake_ctx) + prop = fake_ctx.instance.runtime_properties + self.assertEqual('~/.ssh/test_private.key', + prop['private_key']['path']) + self.assertEqual('private', prop['private_key']['key']) + self.assertEqual('public', prop['public_key']['key']) + + fake_ctx = self.generate_node_context_with_current_ctx( + properties={'auto_generate': False, + 'private_key': {'key': 'private', + 'create_file': True}}) + keypair.create(ctx=fake_ctx) + prop = fake_ctx.instance.runtime_properties + self.assertEqual('~/.ssh/test_private.key', + prop['private_key']['path']) + mock.patch.stopall() + + def _delete_subfunc(self, fake_ctx): + """run delete and check private/public key properties""" + prop = fake_ctx.instance.runtime_properties + self.assertTrue('path' in prop['private_key']) + keypair.delete(ctx=fake_ctx) + self.assertFalse('private_key' in prop) + self.assertFalse('public_key' in prop) + + def test_delete(self): + patcher = mock.patch('vcloud_network_plugin.keypair._delete_key_file', + mock.MagicMock()) + patcher.start() + + # autogen keys + fake_ctx = self.generate_node_context_with_current_ctx( + properties={'auto_generate': True}, + runtime_properties={'private_key': {'path': 'path', + 'key': 'private'}, + 'public_key': {'key': 'public'}}) + prop = fake_ctx.instance.runtime_properties + self.assertTrue('key' in prop['private_key']) + self.assertTrue('key' in prop['public_key']) + + self._delete_subfunc(fake_ctx) + + # autogen keys with keyfile delete + fake_ctx = self.generate_node_context_with_current_ctx( + properties={ + 'auto_generate': True, + keypair.PRIVATE_KEY: { + keypair.CREATE_PRIVATE_KEY_FILE: 'something' + } + }, + runtime_properties={'private_key': {'path': 'path', + 'key': 'private'}, + 'public_key': {'key': 'public'}}) + prop = fake_ctx.instance.runtime_properties + self.assertTrue('key' in prop['private_key']) + self.assertTrue('key' in prop['public_key']) + + self._delete_subfunc(fake_ctx) + + # use keys generated by user + fake_ctx = self.generate_node_context_with_current_ctx( + properties={'auto_generate': False, + keypair.PRIVATE_KEY: {'key': 'private'}}, + runtime_properties={'private_key': {'path': 'path'}, + 'public_key': {}}) + self._delete_subfunc(fake_ctx) + + # use keys generated by user with key file delete + fake_ctx = self.generate_node_context_with_current_ctx( + properties={'auto_generate': False, + keypair.PRIVATE_KEY: { + 'key': 'private', + keypair.CREATE_PRIVATE_KEY_FILE: 'something' + }}, + runtime_properties={'private_key': {'path': 'path'}, + 'public_key': {}}) + self._delete_subfunc(fake_ctx) + + mock.patch.stopall() + + def test_generate_pair(self): + public, private = keypair._generate_pair() + self.assertTrue(public) + self.assertTrue(private) + + def test_create_path(self): + fake_ctx = mock.MagicMock() + fake_ctx._local = True + fake_ctx.instance = mock.MagicMock() + fake_ctx.instance.id = 'id' + fake_ctx._context = {} + fake_ctx._context['storage'] = mock.MagicMock() + fake_ctx._context['storage']._storage_dir = 'storage_dir' + patcher = mock.patch('vcloud_network_plugin.keypair.os.environ', + {'VIRTUALENV': '/path/to/dir'}) + patcher.start() + path = keypair._create_path(ctx=fake_ctx) + self.assertEqual('storage_dir/id_private.key', path) + + fake_ctx._local = False + path = keypair._create_path(ctx=fake_ctx) + self.assertEqual('/path/to/id_private.key', path) + + mock.patch.stopall() + + def test_save_key_file(self): + fake_file = mock.mock_open() + with mock.patch( + '__builtin__.open', fake_file, create=True): + with mock.patch( + 'vcloud_network_plugin.keypair.chmod'): + keypair._save_key_file('/path/to/file/', 'private') + handle = fake_file() + handle.write.assert_called_once_with('private') + + def test_delete_key_file(self): + unlink = mock.MagicMock() + with mock.patch( + 'vcloud_network_plugin.keypair.os.unlink', unlink): + keypair._delete_key_file({ + keypair.PRIVATE_KEY: { + keypair.PATH: '/path/' + } + }) + unlink.assert_called_with('/path/') + + def test_server_connect_to_keypair(self): + # check copy properties from target to source + fake_ctx = self.generate_relation_context_with_current_ctx() + fake_ctx.source.instance.runtime_properties = {} + fake_ctx.target.instance.runtime_properties = { + keypair.PRIVATE_KEY: { + keypair.PATH: '/path/', + keypair.KEY: 'key', + }, + keypair.PUBLIC_KEY: { + keypair.USER: 'user' + } + } + keypair.server_connect_to_keypair(ctx=fake_ctx) + self.assertEqual( + fake_ctx.source.instance.runtime_properties, + { + keypair.AGENT_CONFIG: { + keypair.KEY: '/path/' + }, + keypair.SSH_KEY: { + keypair.KEY: 'key', + keypair.PATH: '/path/', + keypair.USER: 'user' + } + } + ) + + def test_server_disconnect_from_keypair(self): + # test drop all keys informations from node + fake_ctx = self.generate_relation_context_with_current_ctx() + fake_ctx.source.instance.runtime_properties = { + keypair.AGENT_CONFIG: { + keypair.KEY: '/path/' + }, + keypair.SSH_KEY: { + keypair.KEY: 'key', + keypair.PATH: '/path/', + keypair.USER: 'user' + } + } + keypair.server_disconnect_from_keypair(ctx=fake_ctx) + self.assertEqual( + fake_ctx.source.instance.runtime_properties, {} + ) + + if __name__ == '__main__': unittest.main() diff --git a/tests/unittests/test_mock_network_plugin_network.py b/tests/unittests/test_mock_network_plugin_network.py index ef380a1..7c46603 100644 --- a/tests/unittests/test_mock_network_plugin_network.py +++ b/tests/unittests/test_mock_network_plugin_network.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved +# Copyright (c) 2014-2020 Cloudify Platform Ltd. All rights reserved # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -8,16 +8,16 @@ # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, -# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# * See the License for the specific language governing permissions and -# * limitations under the License. +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. import mock import unittest from cloudify import exceptions as cfy_exc from tests.unittests import test_mock_base -from network_plugin import network +from vcloud_network_plugin import network import vcloud_plugin_common @@ -29,7 +29,7 @@ def test_delete(self): 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): - fake_ctx = self.generate_node_context( + fake_ctx = self.generate_node_context_with_current_ctx( properties={ 'network': { 'dhcp': { @@ -47,13 +47,13 @@ def test_delete(self): } ) - network.delete(ctx=fake_ctx) + network.delete(ctx=fake_ctx, vca_client=None) self.assertFalse( network.VCLOUD_NETWORK_NAME in fake_ctx.instance.runtime_properties ) - fake_ctx = self.generate_node_context( + fake_ctx = self.generate_node_context_with_current_ctx( properties={ 'network': { 'dhcp': { @@ -77,14 +77,14 @@ def test_delete(self): vcloud_plugin_common.TASK_STATUS_ERROR ) with self.assertRaises(cfy_exc.NonRecoverableError): - network.delete(ctx=fake_ctx) + network.delete(ctx=fake_ctx, vca_client=None) # None in deleted vdc network self.set_services_conf_result( fake_client._vdc_gateway, vcloud_plugin_common.TASK_STATUS_SUCCESS ) with self.assertRaises(cfy_exc.NonRecoverableError): - network.delete(ctx=fake_ctx) + network.delete(ctx=fake_ctx, vca_client=None) # Error in deleted vdc network task_delete_vdc = self.generate_task( vcloud_plugin_common.TASK_STATUS_ERROR @@ -93,18 +93,34 @@ def test_delete(self): return_value=(True, task_delete_vdc) ) with self.assertRaises(cfy_exc.NonRecoverableError): - network.delete(ctx=fake_ctx) + network.delete(ctx=fake_ctx, vca_client=None) fake_client.delete_vdc_network.assert_called_with( 'vdc_name', 'secret_network' ) + # retry in save configuration + self.prepere_gatway_busy_retry(fake_client, fake_ctx) + network.delete(ctx=fake_ctx, vca_client=None) + self.check_retry_realy_called(fake_ctx) # Success in deleted vdc network + self.set_services_conf_result( + fake_client._vdc_gateway, + vcloud_plugin_common.TASK_STATUS_SUCCESS + ) task_delete_vdc = self.generate_task( vcloud_plugin_common.TASK_STATUS_SUCCESS ) fake_client.delete_vdc_network = mock.MagicMock( return_value=(True, task_delete_vdc) ) - network.delete(ctx=fake_ctx) + network.delete(ctx=fake_ctx, vca_client=None) + # in use + task_delete_vdc = self.generate_task( + vcloud_plugin_common.TASK_STATUS_SUCCESS + ) + fake_client.delete_vdc_network = mock.MagicMock( + return_value=(False, network.CANT_DELETE) + ) + network.delete(ctx=fake_ctx, vca_client=None) def test_create(self): fake_client = self.generate_client() @@ -112,13 +128,13 @@ def test_create(self): 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): - fake_ctx = self.generate_node_context( + fake_ctx = self.generate_node_context_with_current_ctx( properties={ 'network': { 'dhcp': { 'dhcp_range': "10.1.1.128-10.1.1.255" }, - 'static_range': "10.1.1.2-10.1.1.127", + 'static_range': "10.1.1.2-10.1.1.127", 'gateway_ip': "10.1.1.1", 'edge_gateway': 'gateway', 'name': 'secret_network', @@ -136,7 +152,7 @@ def test_create(self): ) # error in create_vdc_network with self.assertRaises(cfy_exc.NonRecoverableError): - network.create(ctx=fake_ctx) + network.create(ctx=fake_ctx, vca_client=None) fake_client.create_vdc_network.assert_called_with( 'vdc_name', 'secret_network', 'gateway', '10.1.1.2', '10.1.1.127', '10.1.1.1', '255.255.255.0', '8.8.8.8', @@ -150,8 +166,8 @@ def test_create(self): return_value=(True, task) ) with self.assertRaises(cfy_exc.NonRecoverableError): - network.create(ctx=fake_ctx) - # success in create_vdc_network + network.create(ctx=fake_ctx, vca_client=None) + # retry in save configuration fake_client.create_vdc_network = mock.MagicMock( return_value=( True, self.generate_task( @@ -159,23 +175,29 @@ def test_create(self): ) ) ) + self.prepere_gatway_busy_retry(fake_client, fake_ctx) + network.create(ctx=fake_ctx, vca_client=None) + self.check_retry_realy_called(fake_ctx) + runtime_properties = fake_ctx.instance.runtime_properties + self.assertTrue(runtime_properties[network.SKIP_CREATE_NETWORK]) + # success in create_vdc_network self.set_services_conf_result( fake_client._vdc_gateway, vcloud_plugin_common.TASK_STATUS_SUCCESS ) - network.create(ctx=fake_ctx) + network.create(ctx=fake_ctx, vca_client=None) # error in get gateway fake_client.get_gateway = mock.MagicMock(return_value=None) with self.assertRaises(cfy_exc.NonRecoverableError): - network.create(ctx=fake_ctx) + network.create(ctx=fake_ctx, vca_client=None) # use external - fake_ctx = self.generate_node_context( + fake_ctx = self.generate_node_context_with_current_ctx( properties={ 'network': { 'dhcp': { 'dhcp_range': "10.1.1.128-10.1.1.255" }, - 'static_range': "10.1.1.2-10.1.1.127", + 'static_range': "10.1.1.2-10.1.1.127", 'gateway_ip': "10.1.1.1", 'edge_gateway': 'gateway', 'name': 'secret_network', @@ -192,15 +214,15 @@ def test_create(self): 'vcloud_network_name': 'secret_network' } ) - network.create(ctx=fake_ctx) + network.create(ctx=fake_ctx, vca_client=None) # not extist network - fake_ctx = self.generate_node_context( + fake_ctx = self.generate_node_context_with_current_ctx( properties={ 'network': { 'dhcp': { 'dhcp_range': "10.1.1.128-10.1.1.255" }, - 'static_range': "10.1.1.2-10.1.1.127", + 'static_range': "10.1.1.2-10.1.1.127", 'gateway_ip': "10.1.1.1", 'edge_gateway': 'gateway', 'name': 'secret_network', @@ -219,7 +241,32 @@ def test_create(self): ) fake_client.get_network = mock.MagicMock(return_value=None) with self.assertRaises(cfy_exc.NonRecoverableError): - network.create(ctx=fake_ctx) + network.create(ctx=fake_ctx, vca_client=None) + + def node_for_check_create_network(self): + return self.generate_node_context_with_current_ctx( + properties={ + 'network': { + 'dhcp': { + 'dhcp_range': "10.1.1.128-10.1.1.255" + }, + 'static_range': "10.1.1.2-10.1.1.127", + 'gateway_ip': "10.1.1.1", + 'edge_gateway': 'gateway', + 'name': 'secret_network', + "netmask": '255.255.255.0', + "dns": ["8.8.8.8", "4.4.4.4"] + }, + 'vcloud_config': { + 'vdc': 'vdc_name' + }, + 'use_external_resource': False, + 'resource_id': 'secret_network' + }, + runtime_properties={ + 'vcloud_network_name': 'secret_network' + } + ) def test_create_exist_same_network(self): fake_client = self.generate_client( @@ -230,32 +277,26 @@ def test_create_exist_same_network(self): mock.MagicMock(return_value=fake_client) ): # exist same network - fake_ctx = self.generate_node_context( - properties={ - 'network': { - 'dhcp': { - 'dhcp_range': "10.1.1.128-10.1.1.255" - }, - 'static_range': "10.1.1.2-10.1.1.127", - 'gateway_ip': "10.1.1.1", - 'edge_gateway': 'gateway', - 'name': 'secret_network', - "netmask": '255.255.255.0', - "dns": ["8.8.8.8", "4.4.4.4"] - }, - 'vcloud_config': { - 'vdc': 'vdc_name' - }, - 'use_external_resource': False, - 'resource_id': 'secret_network' - }, - runtime_properties={ - 'vcloud_network_name': 'secret_network' - } - ) + fake_ctx = self.node_for_check_create_network() fake_client.get_network = mock.MagicMock(return_value=None) with self.assertRaises(cfy_exc.NonRecoverableError): - network.create(ctx=fake_ctx) + network.create(ctx=fake_ctx, vca_client=None) + + def test_dhcp_operation(self): + fake_ctx = self.node_for_check_create_network() + fake_client = self.generate_client( + vdc_networks=['secret_network'] + ) + fake_client._vdc_gateway.is_busy = mock.MagicMock( + return_value=True + ) + with mock.patch('vcloud_plugin_common.ctx', fake_ctx): + self.assertFalse( + network._dhcp_operation( + fake_ctx, fake_client, fake_ctx.node.properties, + 'secret_network', network.DELETE_POOL + ) + ) def test_creation_validation(self): fake_client = self.generate_client( @@ -266,7 +307,7 @@ def test_creation_validation(self): mock.MagicMock(return_value=fake_client) ): # network not exist in node properties - fake_ctx = self.generate_node_context( + fake_ctx = self.generate_node_context_with_current_ctx( properties={ 'use_external_resource': False, 'resource_id': 'secret_network' @@ -276,9 +317,9 @@ def test_creation_validation(self): } ) with self.assertRaises(cfy_exc.NonRecoverableError): - network.creation_validation(ctx=fake_ctx) + network.creation_validation(ctx=fake_ctx, vca_client=None) # network already exist - fake_ctx = self.generate_node_context( + fake_ctx = self.generate_node_context_with_current_ctx( properties={ 'use_external_resource': False, 'resource_id': 'secret_network', @@ -286,7 +327,7 @@ def test_creation_validation(self): 'dhcp': { 'dhcp_range': "10.1.1.128-10.1.1.255" }, - 'static_range': "10.1.1.2-10.1.1.127", + 'static_range': "10.1.1.2-10.1.1.127", 'gateway_ip': "10.1.1.1", 'edge_gateway': 'gateway', 'name': 'secret_network', @@ -303,12 +344,12 @@ def test_creation_validation(self): ) fake_client.get_network = mock.MagicMock(return_value=True) with self.assertRaises(cfy_exc.NonRecoverableError): - network.creation_validation(ctx=fake_ctx) + network.creation_validation(ctx=fake_ctx, vca_client=None) fake_client.get_network.assert_called_with( 'vdc_name', 'secret_network' ) # use external - fake_ctx = self.generate_node_context( + fake_ctx = self.generate_node_context_with_current_ctx( properties={ 'use_external_resource': True, 'resource_id': 'secret_network', @@ -316,7 +357,7 @@ def test_creation_validation(self): 'dhcp': { 'dhcp_range': "10.1.1.128-10.1.1.255" }, - 'static_range': "10.1.1.2-10.1.1.127", + 'static_range': "10.1.1.2-10.1.1.127", 'gateway_ip': "10.1.1.1", 'edge_gateway': 'gateway', 'name': 'secret_network', @@ -331,7 +372,7 @@ def test_creation_validation(self): 'vcloud_network_name': 'secret_network' } ) - network.creation_validation(ctx=fake_ctx) + network.creation_validation(ctx=fake_ctx, vca_client=None) def test_creation_validation_gateway(self): fake_client = self.generate_client( @@ -341,14 +382,14 @@ def test_creation_validation_gateway(self): 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): - fake_ctx = self.generate_node_context( + fake_ctx = self.generate_node_context_with_current_ctx( properties={ 'use_external_resource': False, 'network': { 'dhcp': { 'dhcp_range': "10.1.1.128-10.1.1.255" }, - 'static_range': "10.1.1.2-10.1.1.127", + 'static_range': "10.1.1.2-10.1.1.127", 'gateway_ip': "10.1.1.1", 'edge_gateway': 'gateway', 'name': 'private_network', @@ -364,7 +405,7 @@ def test_creation_validation_gateway(self): } ) fake_client.get_network = mock.MagicMock(return_value=None) - network.creation_validation(ctx=fake_ctx) + network.creation_validation(ctx=fake_ctx, vca_client=None) fake_client.get_gateway.assert_called_with( 'vdc_name', 'gateway' ) @@ -372,7 +413,7 @@ def test_creation_validation_gateway(self): # no gateway fake_client.get_gateway = mock.MagicMock(return_value=None) with self.assertRaises(cfy_exc.NonRecoverableError): - network.creation_validation(ctx=fake_ctx) + network.creation_validation(ctx=fake_ctx, vca_client=None) def test_creation_validation_network_mask(self): # test network mask @@ -383,14 +424,14 @@ def test_creation_validation_network_mask(self): 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): - fake_ctx = self.generate_node_context( + fake_ctx = self.generate_node_context_with_current_ctx( properties={ 'use_external_resource': False, 'network': { 'dhcp': { 'dhcp_range': "10.1.1.128-10.1.1.255" }, - 'static_range': "10.1.1.2-10.1.1.127", + 'static_range': "10.1.1.2-10.1.1.127", 'gateway_ip': "10.1.1.1", 'edge_gateway': 'gateway', 'name': 'private_network', @@ -407,7 +448,7 @@ def test_creation_validation_network_mask(self): ) fake_client.get_network = mock.MagicMock(return_value=None) with self.assertRaises(cfy_exc.NonRecoverableError): - network.creation_validation(ctx=fake_ctx) + network.creation_validation(ctx=fake_ctx, vca_client=None) def test_creation_validation_separate_ips(self): # test separate ips @@ -418,14 +459,14 @@ def test_creation_validation_separate_ips(self): 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): - fake_ctx = self.generate_node_context( + fake_ctx = self.generate_node_context_with_current_ctx( properties={ 'use_external_resource': False, 'network': { 'dhcp': { 'dhcp_range': "10.1.1.10-10.1.1.255" }, - 'static_range': "10.1.1.2-10.1.1.210", + 'static_range': "10.1.1.2-10.1.1.210", 'gateway_ip': "10.1.1.1", 'edge_gateway': 'gateway', 'name': 'private_network', @@ -442,7 +483,8 @@ def test_creation_validation_separate_ips(self): ) fake_client.get_network = mock.MagicMock(return_value=None) with self.assertRaises(cfy_exc.NonRecoverableError): - network.creation_validation(ctx=fake_ctx) + network.creation_validation(ctx=fake_ctx, vca_client=None) + if __name__ == '__main__': unittest.main() diff --git a/tests/unittests/test_mock_network_plugin_network_subroutes.py b/tests/unittests/test_mock_network_plugin_network_subroutes.py index 2f88d69..71f9f0a 100644 --- a/tests/unittests/test_mock_network_plugin_network_subroutes.py +++ b/tests/unittests/test_mock_network_plugin_network_subroutes.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved +# Copyright (c) 2014-2020 Cloudify Platform Ltd. All rights reserved # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -8,16 +8,16 @@ # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, -# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# * See the License for the specific language governing permissions and -# * limitations under the License. +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. import mock import unittest from cloudify import exceptions as cfy_exc import test_mock_base -from network_plugin import network +from vcloud_network_plugin import network class NetworkPluginNetworkSubroutesMockTestCase(test_mock_base.TestBase): @@ -57,11 +57,11 @@ def test__dhcp_operation(self): 'vdc': 'vdc_name' } }) - with mock.patch('network_plugin.network.ctx', fake_ctx): - with mock.patch('vcloud_plugin_common.ctx', fake_ctx): - network._dhcp_operation( - fake_client, '_management_network', network.ADD_POOL - ) + with mock.patch('vcloud_plugin_common.ctx', fake_ctx): + network._dhcp_operation( + fake_ctx, fake_client, fake_ctx.node.properties, + '_management_network', network.ADD_POOL + ) # wrong dhcp_range fake_ctx = self.generate_node_context(properties={ 'network': { @@ -74,12 +74,12 @@ def test__dhcp_operation(self): 'vdc': 'vdc_name' } }) - with mock.patch('network_plugin.network.ctx', fake_ctx): - with mock.patch('vcloud_plugin_common.ctx', fake_ctx): - with self.assertRaises(cfy_exc.NonRecoverableError): - network._dhcp_operation( - fake_client, '_management_network', network.ADD_POOL - ) + with mock.patch('vcloud_plugin_common.ctx', fake_ctx): + with self.assertRaises(cfy_exc.NonRecoverableError): + network._dhcp_operation( + fake_ctx, fake_client, fake_ctx.node.properties, + '_management_network', network.ADD_POOL + ) fake_ctx = self.generate_node_context(properties={ 'network': { @@ -93,39 +93,41 @@ def test__dhcp_operation(self): } }) - with mock.patch('network_plugin.network.ctx', fake_ctx): - with mock.patch('vcloud_plugin_common.ctx', fake_ctx): - # returned error/None from server - with self.assertRaises(cfy_exc.NonRecoverableError): - network._dhcp_operation( - fake_client, '_management_network', network.ADD_POOL - ) - fake_client.get_gateway.assert_called_with( - 'vdc_name', 'gateway' + with mock.patch('vcloud_plugin_common.ctx', fake_ctx): + # returned error/None from server + with self.assertRaises(cfy_exc.NonRecoverableError): + network._dhcp_operation( + fake_ctx, fake_client, fake_ctx.node.properties, + '_management_network', network.ADD_POOL ) + fake_client.get_gateway.assert_called_with( + 'vdc_name', 'gateway' + ) - # returned error/None from server delete - with self.assertRaises(cfy_exc.NonRecoverableError): - network._dhcp_operation( - fake_client, '_management_network', network.DELETE_POOL - ) + # returned error/None from server delete + with self.assertRaises(cfy_exc.NonRecoverableError): + network._dhcp_operation( + fake_ctx, fake_client, fake_ctx.node.properties, + '_management_network', network.DELETE_POOL + ) - # returned busy, try next time - self.set_gateway_busy(fake_client._vdc_gateway) - self.prepare_retry(fake_ctx) + # returned busy, try next time + self.set_gateway_busy(fake_client._vdc_gateway) + self.prepare_retry(fake_ctx) + self.assertFalse(network._dhcp_operation( + fake_ctx, fake_client, fake_ctx.node.properties, + '_management_network', network.DELETE_POOL + )) + + # no such gateway + fake_client.get_gateway = mock.MagicMock(return_value=None) + with self.assertRaises(cfy_exc.NonRecoverableError): network._dhcp_operation( - fake_client, '_management_network', - network.DELETE_POOL - ), None - self.check_retry_realy_called(fake_ctx) + fake_ctx, fake_client, fake_ctx.node.properties, + '_management_network', network.ADD_POOL + ) - # no such gateway - fake_client.get_gateway = mock.MagicMock(return_value=None) - with self.assertRaises(cfy_exc.NonRecoverableError): - network._dhcp_operation( - fake_client, '_management_network', network.ADD_POOL - ) if __name__ == '__main__': unittest.main() diff --git a/tests/unittests/test_mock_network_plugin_port.py b/tests/unittests/test_mock_network_plugin_port.py index 379a582..8040aca 100644 --- a/tests/unittests/test_mock_network_plugin_port.py +++ b/tests/unittests/test_mock_network_plugin_port.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved +# Copyright (c) 2014-2020 Cloudify Platform Ltd. All rights reserved # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -8,20 +8,32 @@ # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, -# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# * See the License for the specific language governing permissions and -# * limitations under the License. +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. import mock import unittest from cloudify import exceptions as cfy_exc from tests.unittests import test_mock_base -from network_plugin import port +from vcloud_network_plugin import port class NetworkPluginPortMockTestCase(test_mock_base.TestBase): + def test_delete(self): + fake_client = self.generate_client() + with mock.patch( + 'vcloud_plugin_common.VcloudAirClient.get', + mock.MagicMock(return_value=fake_client) + ): + # no port + fake_ctx = self.generate_node_context_with_current_ctx( + properties={} + ) + port.delete(ctx=fake_ctx, vca_client=None) + def test_creation_validation(self): fake_client = self.generate_client() with mock.patch( @@ -29,22 +41,22 @@ def test_creation_validation(self): mock.MagicMock(return_value=fake_client) ): # no port - fake_ctx = self.generate_node_context( + fake_ctx = self.generate_node_context_with_current_ctx( properties={} ) with self.assertRaises(cfy_exc.NonRecoverableError): - port.creation_validation(ctx=fake_ctx) + port.creation_validation(ctx=fake_ctx, vca_client=None) # port without allocation - fake_ctx = self.generate_node_context( + fake_ctx = self.generate_node_context_with_current_ctx( properties={ 'port': { 'some_field': 'some_value' } } ) - port.creation_validation(ctx=fake_ctx) + port.creation_validation(ctx=fake_ctx, vca_client=None) # wrong allocation mode - fake_ctx = self.generate_node_context( + fake_ctx = self.generate_node_context_with_current_ctx( properties={ 'port': { 'ip_allocation_mode': 'realy wrong' @@ -52,19 +64,19 @@ def test_creation_validation(self): } ) with self.assertRaises(cfy_exc.NonRecoverableError): - port.creation_validation(ctx=fake_ctx) + port.creation_validation(ctx=fake_ctx, vca_client=None) # correct allocation for mode in ['manual', 'dhcp', 'pool']: - fake_ctx = self.generate_node_context( + fake_ctx = self.generate_node_context_with_current_ctx( properties={ 'port': { 'ip_allocation_mode': mode } } ) - port.creation_validation(ctx=fake_ctx) + port.creation_validation(ctx=fake_ctx, vca_client=None) # wrong manual ip - fake_ctx = self.generate_node_context( + fake_ctx = self.generate_node_context_with_current_ctx( properties={ 'port': { 'ip_allocation_mode': 'manual', @@ -73,9 +85,9 @@ def test_creation_validation(self): } ) with self.assertRaises(cfy_exc.NonRecoverableError): - port.creation_validation(ctx=fake_ctx) + port.creation_validation(ctx=fake_ctx, vca_client=None) # correct manual ip - fake_ctx = self.generate_node_context( + fake_ctx = self.generate_node_context_with_current_ctx( properties={ 'port': { 'ip_allocation_mode': 'manual', @@ -83,7 +95,7 @@ def test_creation_validation(self): } } ) - port.creation_validation(ctx=fake_ctx) + port.creation_validation(ctx=fake_ctx, vca_client=None) if __name__ == '__main__': diff --git a/tests/unittests/test_mock_network_plugin_public_nat.py b/tests/unittests/test_mock_network_plugin_public_nat.py index 1df8088..ce21a48 100644 --- a/tests/unittests/test_mock_network_plugin_public_nat.py +++ b/tests/unittests/test_mock_network_plugin_public_nat.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved +# Copyright (c) 2014-2020 Cloudify Platform Ltd. All rights reserved # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -8,18 +8,18 @@ # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, -# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# * See the License for the specific language governing permissions and -# * limitations under the License. +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. import mock import unittest from cloudify import exceptions as cfy_exc from tests.unittests import test_mock_base -from network_plugin import public_nat -from network_plugin import utils -import network_plugin +from vcloud_network_plugin import public_nat +from vcloud_network_plugin import utils +import vcloud_network_plugin import vcloud_plugin_common from IPy import IP @@ -39,79 +39,76 @@ def test_is_rule_exists(self): # not exist self.assertFalse( public_nat._is_rule_exists( - [rule_inlist], 'SNAT', 'external', '22', 'internal', + [rule_inlist], 'DNAT', 'external', '22', 'internal', '11', 'UDP') ) def test_get_original_port_for_delete(self): # no replacement - fake_ctx = self.generate_relation_context() - with mock.patch( - 'network_plugin.public_nat.ctx', fake_ctx - ): - self.assertEqual( - public_nat._get_original_port_for_delete("10.1.1.1", "11"), - "11" - ) + fake_ctx = self.generate_relation_context_with_current_ctx() + fake_ctx._target.instance.runtime_properties = { + public_nat.PORT_REPLACEMENT: {}} + + self.assertEqual( + public_nat._get_original_port_for_delete( + fake_ctx, "10.1.1.1", "11"), + "11" + ) # replacement for other - fake_ctx = self.generate_relation_context() + fake_ctx = self.generate_relation_context_with_current_ctx() fake_ctx._target.instance.runtime_properties = { public_nat.PORT_REPLACEMENT: { - ("10.1.1.2", "11"): '12' + "10.1.1.2:11": '12' } } - with mock.patch( - 'network_plugin.public_nat.ctx', fake_ctx - ): - self.assertEqual( - public_nat._get_original_port_for_delete("10.1.1.1", "11"), - "11" - ) + self.assertEqual( + public_nat._get_original_port_for_delete( + fake_ctx, "10.1.1.1", "11"), + "11" + ) # replacement for other - fake_ctx = self.generate_relation_context() + fake_ctx = self.generate_relation_context_with_current_ctx() fake_ctx._target.instance.runtime_properties = { public_nat.PORT_REPLACEMENT: { - ("10.1.1.2", "11"): '12' + "10.1.1.2:11": '12' } } - with mock.patch( - 'network_plugin.public_nat.ctx', fake_ctx - ): - self.assertEqual( - public_nat._get_original_port_for_delete("10.1.1.2", "11"), - "12" - ) + self.assertEqual( + public_nat._get_original_port_for_delete( + fake_ctx, "10.1.1.2", "11"), + "12" + ) def test_get_original_port_for_create(self): gateway = mock.Mock() + fake_ctx = self.generate_relation_context_with_current_ctx() rule_inlist = self.generate_nat_rule( - 'SNAT', 'external', 'any', 'internal', '11', 'TCP' - ) + 'DNAT', 'external', 'any', 'internal', '11', 'TCP') gateway.get_nat_rules = mock.MagicMock(return_value=[rule_inlist]) # exeption about same port with self.assertRaises(cfy_exc.NonRecoverableError): public_nat._get_original_port_for_create( - gateway, 'SNAT', 'external', 'any', 'internal', '11', 'TCP' + fake_ctx, gateway, 'DNAT', 'external', 'any', 'internal', + '11', 'TCP' ) # everythiong fine with different port self.assertEqual( public_nat._get_original_port_for_create( - gateway, 'SNAT', 'external', 'any', 'internal', '12', 'TCP' + fake_ctx, gateway, 'DNAT', 'external', '12', 'internal', + '12', 'TCP' ), - 'any' - ) + 12) # relink some port to other # port have not used yet self.assertEqual( public_nat._get_original_port_for_create( - gateway, 'SNAT', 'external', 10, 'internal', '12', 'TCP' - ), - 10 - ) + fake_ctx, gateway, 'SNAT', 'external', 13, 'internal', + '12', 'TCP'), + 13) def test_get_original_port_for_create_with_ctx(self): # with replace, but without replace table - up port +1 - fake_ctx = self.generate_relation_context() + fake_ctx = self.generate_relation_context_with_current_ctx() fake_ctx._target.instance.runtime_properties = { public_nat.PORT_REPLACEMENT: {} } @@ -120,42 +117,38 @@ def test_get_original_port_for_create_with_ctx(self): 'SNAT', 'external', 10, 'internal', 11, 'TCP' ) gateway.get_nat_rules = mock.MagicMock(return_value=[rule_inlist]) - with mock.patch( - 'network_plugin.public_nat.ctx', fake_ctx - ): - self.assertEqual( - public_nat._get_original_port_for_create( - gateway, 'SNAT', 'external', '10', 'internal', '11', 'TCP' - ), - 11 - ) - self.assertEqual( - fake_ctx._target.instance.runtime_properties, - { - public_nat.PORT_REPLACEMENT: { - ('external', '10'): 11 - } + self.assertEqual( + public_nat._get_original_port_for_create( + fake_ctx, gateway, 'SNAT', 'external', '10', 'internal', + '11', 'TCP' + ), + 11 + ) + self.assertEqual( + fake_ctx._target.instance.runtime_properties, + { + public_nat.PORT_REPLACEMENT: { + 'external:10': 11 } - ) + } + ) # same but without replacement at all fake_ctx._target.instance.runtime_properties = {} - with mock.patch( - 'network_plugin.public_nat.ctx', fake_ctx - ): - self.assertEqual( - public_nat._get_original_port_for_create( - gateway, 'SNAT', 'external', '10', 'internal', '11', 'TCP' - ), - 11 - ) - self.assertEqual( - fake_ctx._target.instance.runtime_properties, - { - public_nat.PORT_REPLACEMENT: { - ('external', '10'): 11 - } + self.assertEqual( + public_nat._get_original_port_for_create( + fake_ctx, gateway, 'SNAT', 'external', '10', 'internal', + '11', 'TCP' + ), + 11 + ) + self.assertEqual( + fake_ctx._target.instance.runtime_properties, + { + public_nat.PORT_REPLACEMENT: { + 'external:10': 11 } - ) + } + ) # we dont have enought ports rule_inlist = self.generate_nat_rule( 'SNAT', 'external', utils.MAX_PORT_NUMBER, @@ -163,14 +156,11 @@ def test_get_original_port_for_create_with_ctx(self): ) gateway.get_nat_rules = mock.MagicMock(return_value=[rule_inlist]) fake_ctx._target.instance.runtime_properties = {} - with mock.patch( - 'network_plugin.public_nat.ctx', fake_ctx - ): - with self.assertRaises(cfy_exc.NonRecoverableError): - public_nat._get_original_port_for_create( - gateway, 'SNAT', 'external', - utils.MAX_PORT_NUMBER, 'internal', '11', 'TCP' - ) + with self.assertRaises(cfy_exc.NonRecoverableError): + public_nat._get_original_port_for_create( + fake_ctx, gateway, 'SNAT', 'external', + utils.MAX_PORT_NUMBER, 'internal', '11', 'TCP' + ) def test_get_gateway_ip_range(self): gate = mock.Mock() @@ -197,16 +187,16 @@ def test_get_gateway_ip_range(self): ) def test_obtain_public_ip(self): - fake_ctx = self.generate_relation_context() + fake_ctx = self.generate_relation_context_with_current_ctx() fake_ctx._target.instance.runtime_properties = { - network_plugin.PUBLIC_IP: '192.168.1.1' + vcloud_network_plugin.PUBLIC_IP: '192.168.1.1' } gateway = mock.Mock() fake_client = mock.Mock() # exist some ip for delete self.assertEqual( public_nat._obtain_public_ip( - fake_client, fake_ctx, gateway, network_plugin.DELETE + fake_client, fake_ctx, gateway, vcloud_network_plugin.DELETE ), '192.168.1.1' ) @@ -214,7 +204,7 @@ def test_obtain_public_ip(self): fake_ctx._target.instance.runtime_properties = {} with self.assertRaises(cfy_exc.NonRecoverableError): public_nat._obtain_public_ip( - fake_client, fake_ctx, gateway, network_plugin.DELETE + fake_client, fake_ctx, gateway, vcloud_network_plugin.DELETE ) # unknow operation with self.assertRaises(cfy_exc.NonRecoverableError): @@ -224,12 +214,12 @@ def test_obtain_public_ip(self): # exist some public ip fake_ctx._target.node.properties = { 'nat': { - network_plugin.PUBLIC_IP: '192.168.1.1' + vcloud_network_plugin.PUBLIC_IP: '192.168.1.1' } } self.assertEqual( public_nat._obtain_public_ip( - fake_client, fake_ctx, gateway, network_plugin.CREATE + fake_client, fake_ctx, gateway, vcloud_network_plugin.CREATE ), '192.168.1.1' ) @@ -253,18 +243,15 @@ def test_obtain_public_ip(self): return_value=[rule_inlist] ) with mock.patch( - 'network_plugin.public_nat.ctx', fake_ctx + 'vcloud_plugin_common.ctx', fake_ctx ): - with mock.patch( - 'vcloud_plugin_common.ctx', fake_ctx - ): - self.assertEqual( - public_nat._obtain_public_ip( - fake_client, fake_ctx, gateway, - network_plugin.CREATE - ), - '10.18.1.2' - ) + self.assertEqual( + public_nat._obtain_public_ip( + fake_client, fake_ctx, gateway, + vcloud_network_plugin.CREATE + ), + '10.18.1.2' + ) def test_get_network_ip_range(self): # dont have ip range for this network @@ -298,13 +285,14 @@ def test_get_network_ip_range(self): def test_create_ip_range(self): # context - fake_ctx = self.generate_relation_context() + fake_ctx = self.generate_relation_context_with_current_ctx() fake_ctx._source.instance.runtime_properties = { - network_plugin.network.VCLOUD_NETWORK_NAME: "some" + vcloud_network_plugin.network.VCLOUD_NETWORK_NAME: "some" } fake_ctx._source.node.properties = { 'vcloud_config': { - 'org': 'some_org' + 'org': 'some_org', + 'vdc': 'some_vdc' } } fake_ctx._target.instance.runtime_properties = {} @@ -318,38 +306,35 @@ def test_create_ip_range(self): ) fake_client.get_networks = mock.MagicMock(return_value=[network]) with mock.patch( - 'network_plugin.public_nat.ctx', fake_ctx + 'vcloud_plugin_common.ctx', fake_ctx ): - with mock.patch( - 'vcloud_plugin_common.ctx', fake_ctx - ): - # empty gateway dhcp pool - # vca pool: 127.1.1.100..127.1.1.200 - self.assertEqual( - public_nat._create_ip_range(fake_client, gate), - '127.1.1.100 - 127.1.1.200' - ) - fake_client.get_networks.assert_called_with("some_org") - # network from gate - gate.get_dhcp_pools = mock.MagicMock(return_value=[ - self.genarate_pool( - "some", '127.1.1.1', '127.1.1.255' - ) - ]) - self.assertEqual( - public_nat._create_ip_range(fake_client, gate), - '127.1.1.1 - 127.1.1.255' - ) - # network not exist - network = self.generate_fake_client_network( - name="other", start_ip="127.1.1.100", - end_ip="127.1.1.200" - ) - fake_client.get_networks = mock.MagicMock( - return_value=[network] + # empty gateway dhcp pool + # vca pool: 127.1.1.100..127.1.1.200 + self.assertEqual( + public_nat._create_ip_range(fake_ctx, fake_client, gate), + '127.1.1.100 - 127.1.1.200' + ) + fake_client.get_networks.assert_called_with("some_vdc") + # network from gate + gate.get_dhcp_pools = mock.MagicMock(return_value=[ + self.genarate_pool( + "some", '127.1.1.1', '127.1.1.255' ) - with self.assertRaises(cfy_exc.NonRecoverableError): - public_nat._create_ip_range(fake_client, gate) + ]) + self.assertEqual( + public_nat._create_ip_range(fake_ctx, fake_client, gate), + '127.1.1.1 - 127.1.1.255' + ) + # network not exist + network = self.generate_fake_client_network( + name="other", start_ip="127.1.1.100", + end_ip="127.1.1.200" + ) + fake_client.get_networks = mock.MagicMock( + return_value=[network] + ) + with self.assertRaises(cfy_exc.NonRecoverableError): + public_nat._create_ip_range(fake_ctx, fake_client, gate) def test_save_configuration(self): @@ -357,16 +342,23 @@ def _context_for_delete(service_type): """ create correct context for delete """ - fake_ctx = self.generate_relation_context() + fake_ctx = self.generate_relation_context_with_current_ctx() self.set_services_conf_result( gateway, vcloud_plugin_common.TASK_STATUS_SUCCESS ) fake_ctx._target.instance.runtime_properties = { - network_plugin.PUBLIC_IP: "1.2.3.4" + vcloud_network_plugin.PUBLIC_IP: "1.2.3.4", + public_nat.PORT_REPLACEMENT: { + '127.0.0.1:10': '100' + }, + vcloud_network_plugin.SSH_PORT: '23', + vcloud_network_plugin.SSH_PUBLIC_IP: '10.1.1.1' } properties = { 'vcloud_config': { - 'org': 'some_org', + 'edge_gateway': 'gateway', + 'vdc': 'vdc', + 'org': 'some_org' } } if service_type: @@ -379,7 +371,7 @@ def _ip_exist_in_runtime(fake_ctx): ip still exist in ctx """ runtime_properties = fake_ctx._target.instance.runtime_properties - return network_plugin.PUBLIC_IP in runtime_properties + return vcloud_network_plugin.PUBLIC_IP in runtime_properties fake_client = self.generate_client() gateway = fake_client._vdc_gateway @@ -388,59 +380,50 @@ def _ip_exist_in_runtime(fake_ctx): gateway, None ) self.set_gateway_busy(gateway) - fake_ctx = self.generate_relation_context() - with mock.patch( - 'network_plugin.public_nat.ctx', fake_ctx - ): - self.prepare_retry(fake_ctx) - public_nat._save_configuration( - gateway, fake_client, "any", "any" - ) - self.check_retry_realy_called(fake_ctx) + fake_ctx = self.generate_relation_context_with_current_ctx() + self.assertFalse(public_nat._save_configuration( + fake_ctx, gateway, fake_client, vcloud_network_plugin.CREATE, + "1.2.3.4" + )) + # operation create - fake_ctx = self.generate_relation_context() + fake_ctx = self.generate_relation_context_with_current_ctx() self.set_services_conf_result( gateway, vcloud_plugin_common.TASK_STATUS_SUCCESS ) - with mock.patch( - 'network_plugin.public_nat.ctx', fake_ctx - ): - # success save configuration + # success save configuration + with mock.patch('vcloud_plugin_common.ctx', fake_ctx): public_nat._save_configuration( - gateway, fake_client, network_plugin.CREATE, "1.2.3.4" - ) - self.assertEqual( - fake_ctx._target.instance.runtime_properties, - { - network_plugin.PUBLIC_IP: "1.2.3.4" - } - ) + fake_ctx, gateway, fake_client, vcloud_network_plugin.CREATE, + "1.2.3.4") + self.assertEqual( + fake_ctx._target.instance.runtime_properties, + { + vcloud_network_plugin.PUBLIC_IP: "1.2.3.4" + } + ) # delete - subscription service fake_ctx = _context_for_delete( vcloud_plugin_common.SUBSCRIPTION_SERVICE_TYPE ) with mock.patch( - 'network_plugin.public_nat.ctx', fake_ctx + 'vcloud_plugin_common.ctx', fake_ctx ): - with mock.patch( - 'vcloud_plugin_common.ctx', fake_ctx - ): - public_nat._save_configuration( - gateway, fake_client, network_plugin.DELETE, "1.2.3.4" - ) + public_nat._save_configuration( + fake_ctx, gateway, fake_client, vcloud_network_plugin.DELETE, + "1.2.3.4" + ) self.assertFalse(_ip_exist_in_runtime(fake_ctx)) # delete - without service fake_ctx = _context_for_delete(None) with mock.patch( - 'network_plugin.public_nat.ctx', fake_ctx + 'vcloud_plugin_common.ctx', fake_ctx ): - with mock.patch( - 'vcloud_plugin_common.ctx', fake_ctx - ): - public_nat._save_configuration( - gateway, fake_client, network_plugin.DELETE, "1.2.3.4" - ) + public_nat._save_configuration( + fake_ctx, gateway, fake_client, vcloud_network_plugin.DELETE, + "1.2.3.4" + ) self.assertFalse(_ip_exist_in_runtime(fake_ctx)) # delete - ondemand service - nat @@ -449,18 +432,16 @@ def _ip_exist_in_runtime(fake_ctx): ) fake_ctx._target.node.properties = { 'nat': { - network_plugin.PUBLIC_IP: "1.2.3.4" + vcloud_network_plugin.PUBLIC_IP: "1.2.3.4" } } with mock.patch( - 'network_plugin.public_nat.ctx', fake_ctx + 'vcloud_plugin_common.ctx', fake_ctx ): - with mock.patch( - 'vcloud_plugin_common.ctx', fake_ctx - ): - public_nat._save_configuration( - gateway, fake_client, network_plugin.DELETE, "1.2.3.4" - ) + public_nat._save_configuration( + fake_ctx, gateway, fake_client, vcloud_network_plugin.DELETE, + "1.2.3.4" + ) self.assertFalse(_ip_exist_in_runtime(fake_ctx)) # delete - ondemand - not nat @@ -476,42 +457,57 @@ def _ip_exist_in_runtime(fake_ctx): 'nat': {} } with mock.patch( - 'network_plugin.public_nat.ctx', fake_ctx + 'vcloud_plugin_common.ctx', fake_ctx ): - with mock.patch( - 'vcloud_plugin_common.ctx', fake_ctx - ): - public_nat._save_configuration( - gateway, fake_client, network_plugin.DELETE, "1.2.3.4" - ) + # import pdb;pdb.set_trace() + public_nat._save_configuration( + fake_ctx, gateway, fake_client, vcloud_network_plugin.DELETE, + "1.2.3.4" + ) gateway.deallocate_public_ip.assert_called_with("1.2.3.4") self.assertFalse(_ip_exist_in_runtime(fake_ctx)) + runtime_properties = fake_ctx._target.instance.runtime_properties + self.assertFalse( + public_nat.PORT_REPLACEMENT in runtime_properties + ) + self.assertFalse( + vcloud_network_plugin.SSH_PORT in runtime_properties + ) + self.assertFalse( + vcloud_network_plugin.SSH_PUBLIC_IP in runtime_properties + ) def test_nat_network_operation(self): fake_client = self.generate_client() + fake_ctx = self.generate_relation_context_with_current_ctx() gateway = fake_client._vdc_gateway # used wrong operation with self.assertRaises(cfy_exc.NonRecoverableError): public_nat.nat_network_operation( - fake_client, gateway, "unknow", "DNAT", "1.2.3.4", + fake_ctx, fake_client, gateway, "unknow", "DNAT", "1.2.3.4", "2.3.4.5", "11", "11", "TCP" ) # run correct operation/rule - fake_ctx = self.generate_relation_context() - for operation in [network_plugin.DELETE, network_plugin.CREATE]: + for operation in [ + vcloud_network_plugin.DELETE, vcloud_network_plugin.CREATE + ]: for rule_type in ["SNAT", "DNAT"]: + # cleanup properties + fake_ctx = self.generate_relation_context_with_current_ctx() + fake_ctx._target.instance.runtime_properties = { + public_nat.PORT_REPLACEMENT: {}} + fake_ctx._source.instance.runtime_properties = {} + # checks with mock.patch( - 'network_plugin.public_nat.ctx', fake_ctx + 'vcloud_plugin_common.ctx', fake_ctx ): - with mock.patch( - 'vcloud_plugin_common.ctx', fake_ctx - ): - public_nat.nat_network_operation( - fake_client, gateway, operation, rule_type, - "1.2.3.4", "2.3.4.5", "11", "11", "TCP" - ) + public_nat.nat_network_operation( + fake_ctx, fake_client, gateway, operation, + rule_type, + "1.2.3.4", "2.3.4.5", "11", "11", "TCP" + ) if rule_type == "DNAT": - if operation == network_plugin.DELETE: + if operation == vcloud_network_plugin.DELETE: gateway.del_nat_rule.assert_called_with( 'DNAT', '1.2.3.4', '11', '2.3.4.5', '11', 'TCP' @@ -522,7 +518,7 @@ def test_nat_network_operation(self): 'TCP' ) else: - if operation == network_plugin.DELETE: + if operation == vcloud_network_plugin.DELETE: gateway.del_nat_rule.assert_called_with( 'SNAT', '2.3.4.5', 'any', '1.2.3.4', 'any', 'any' @@ -532,19 +528,49 @@ def test_nat_network_operation(self): 'SNAT', '2.3.4.5', 'any', '1.2.3.4', 'any', 'any' ) + # cleanup properties + fake_ctx = self.generate_relation_context_with_current_ctx() + fake_ctx._target.instance.runtime_properties = { + public_nat.PORT_REPLACEMENT: {}} + fake_ctx._source.instance.runtime_properties = {} + # save ssh port + with mock.patch( + 'vcloud_plugin_common.ctx', fake_ctx + ): + public_nat.nat_network_operation( + fake_ctx, fake_client, gateway, + vcloud_network_plugin.CREATE, + "DNAT", "1.2.3.4", "2.3.4.5", "43", "22", "TCP" + ) + self.assertEqual( + {'port_replacement': {'1.2.3.4:43': 43}}, + fake_ctx._target.instance.runtime_properties + ) + self.assertEqual( + {'ssh_port': '43', 'ssh_public_ip': '1.2.3.4'}, + fake_ctx._source.instance.runtime_properties + ) + # error with type + with self.assertRaises(cfy_exc.NonRecoverableError): + public_nat.nat_network_operation( + fake_ctx, fake_client, gateway, + vcloud_network_plugin.CREATE, + "QNAT", "1.2.3.4", "2.3.4.5", "43", "22", "TCP" + ) - def generate_client_and_context_server(self): + def generate_client_and_context_server(self, no_vmip=False): """ for test prepare_server_operation based operations """ + vm_ip = '1.1.1.1' if not no_vmip else None fake_client = self.generate_client(vms_networks=[{ 'is_connected': True, 'network_name': 'network_name', 'is_primary': True, - 'ip': '1.1.1.1' + 'ip': vm_ip }]) self.set_network_routed_in_client(fake_client) - fake_ctx = self.generate_relation_context() + fake_ctx = self.generate_relation_context_with_current_ctx() fake_ctx._target.node.properties = { 'nat': { 'edge_gateway': 'gateway' @@ -553,11 +579,12 @@ def generate_client_and_context_server(self): fake_ctx._source.node.properties = { 'vcloud_config': { 'vdc': 'vdc_name', - 'service_type': vcloud_plugin_common.SUBSCRIPTION_SERVICE_TYPE + 'service_type': + vcloud_plugin_common.SUBSCRIPTION_SERVICE_TYPE } } fake_ctx._target.instance.runtime_properties = { - network_plugin.PUBLIC_IP: '192.168.1.1' + vcloud_network_plugin.PUBLIC_IP: '192.168.1.1' } self.set_services_conf_result( fake_client._vdc_gateway, @@ -570,16 +597,13 @@ def test_prepare_server_operation(self): fake_client, fake_ctx = self.generate_client_and_context_server() # no rules for update with mock.patch( - 'network_plugin.public_nat.ctx', fake_ctx + 'vcloud_plugin_common.ctx', fake_ctx ): - with mock.patch( - 'vcloud_plugin_common.ctx', fake_ctx - ): - with self.assertRaises(cfy_exc.NonRecoverableError): - public_nat.prepare_server_operation( - fake_client, network_plugin.DELETE - ) - # with some rules + with self.assertRaises(cfy_exc.NonRecoverableError): + public_nat.prepare_server_operation( + fake_ctx, fake_client, vcloud_network_plugin.DELETE + ) + # public ip equal to None in node properties fake_client, fake_ctx = self.generate_client_and_context_server() fake_ctx._target.node.properties = { 'nat': { @@ -592,22 +616,66 @@ def test_prepare_server_operation(self): 'translated_port': "11" }] } + fake_ctx._target.instance.runtime_properties = { + vcloud_network_plugin.PUBLIC_IP: None + } with mock.patch( - 'network_plugin.public_nat.ctx', fake_ctx + 'vcloud_plugin_common.ctx', fake_ctx ): - with mock.patch( - 'vcloud_plugin_common.ctx', fake_ctx - ): + self.assertFalse( public_nat.prepare_server_operation( - fake_client, network_plugin.DELETE + fake_ctx, fake_client, vcloud_network_plugin.DELETE ) + ) + # we dont have connected private ip + fake_client, fake_ctx = self.generate_client_and_context_server( + no_vmip=True + ) + fake_ctx._target.node.properties = { + 'nat': { + 'edge_gateway': 'gateway' + }, + 'rules': [{ + 'type': 'DNAT', + 'protocol': 'TCP', + 'original_port': "11", + 'translated_port': "11" + }] + } + with mock.patch( + 'vcloud_plugin_common.ctx', fake_ctx + ): + self.assertFalse( + public_nat.prepare_server_operation( + fake_ctx, fake_client, vcloud_network_plugin.DELETE + ) + ) + # with some rules + fake_client, fake_ctx = self.generate_client_and_context_server() + fake_ctx._target.node.properties = { + 'nat': { + 'edge_gateway': 'gateway' + }, + 'rules': [{ + 'type': 'DNAT', + 'protocol': 'TCP', + 'original_port': "11", + 'translated_port': "11" + }] + } + with mock.patch( + 'vcloud_plugin_common.ctx', fake_ctx + ): + public_nat.prepare_server_operation( + fake_ctx, fake_client, vcloud_network_plugin.DELETE + ) fake_client._vdc_gateway.del_nat_rule.assert_called_with( 'DNAT', '192.168.1.1', '11', '1.1.1.1', '11', 'TCP' ) # with default value fake_client, fake_ctx = self.generate_client_and_context_server() fake_ctx._target.instance.runtime_properties = { - network_plugin.PUBLIC_IP: '192.168.1.1' + vcloud_network_plugin.PUBLIC_IP: '192.168.1.1' } fake_ctx._target.node.properties = { 'nat': { @@ -618,17 +686,31 @@ def test_prepare_server_operation(self): }] } with mock.patch( - 'network_plugin.public_nat.ctx', fake_ctx + 'vcloud_plugin_common.ctx', fake_ctx ): - with mock.patch( - 'vcloud_plugin_common.ctx', fake_ctx - ): - public_nat.prepare_server_operation( - fake_client, network_plugin.DELETE - ) + public_nat.prepare_server_operation( + fake_ctx, fake_client, vcloud_network_plugin.DELETE + ) fake_client._vdc_gateway.del_nat_rule.assert_called_with( 'DNAT', '192.168.1.1', 'any', '1.1.1.1', 'any', 'any' ) + # with SNAT rules + fake_client, fake_ctx = self.generate_client_and_context_server() + fake_ctx._target.node.properties = { + 'nat': { + 'edge_gateway': 'gateway' + }, + 'rules': [{'type': 'SNAT'}, {'type': 'SNAT'}] + } + with mock.patch( + 'vcloud_plugin_common.ctx', fake_ctx + ): + public_nat.prepare_server_operation( + fake_ctx, fake_client, vcloud_network_plugin.DELETE + ) + fake_client._vdc_gateway.del_nat_rule.assert_called_with( + 'SNAT', '1.1.1.1', 'any', '192.168.1.1', 'any', 'any' + ) def generate_client_and_context_network(self): """ @@ -652,15 +734,16 @@ def generate_client_and_context_network(self): vcloud_plugin_common.TASK_STATUS_SUCCESS ) # ctx - fake_ctx = self.generate_relation_context() + fake_ctx = self.generate_relation_context_with_current_ctx() fake_ctx._source.instance.runtime_properties = { - network_plugin.network.VCLOUD_NETWORK_NAME: "some" + vcloud_network_plugin.network.VCLOUD_NETWORK_NAME: "some" } fake_ctx._source.node.properties = { 'vcloud_config': { 'org': 'some_org', 'vdc': 'vdc_name', - 'service_type': vcloud_plugin_common.SUBSCRIPTION_SERVICE_TYPE + 'service_type': + vcloud_plugin_common.SUBSCRIPTION_SERVICE_TYPE } } fake_ctx._target.node.properties = { @@ -669,7 +752,7 @@ def generate_client_and_context_network(self): } } fake_ctx._target.instance.runtime_properties = { - network_plugin.PUBLIC_IP: '192.168.1.1' + vcloud_network_plugin.PUBLIC_IP: '192.168.1.1' } return fake_client, fake_ctx @@ -677,34 +760,50 @@ def test_prepare_network_operation(self): # no rules fake_client, fake_ctx = self.generate_client_and_context_network() with mock.patch( - 'network_plugin.public_nat.ctx', fake_ctx + 'vcloud_plugin_common.ctx', fake_ctx ): - with mock.patch( - 'vcloud_plugin_common.ctx', fake_ctx - ): - with self.assertRaises(cfy_exc.NonRecoverableError): - public_nat.prepare_network_operation( - fake_client, network_plugin.DELETE - ) - # rules with default values + with self.assertRaises(cfy_exc.NonRecoverableError): + public_nat.prepare_network_operation( + fake_ctx, fake_client, vcloud_network_plugin.DELETE + ) + # public ip equal to None in node properties fake_client, fake_ctx = self.generate_client_and_context_network() + fake_ctx._target.instance.runtime_properties = { + vcloud_network_plugin.PUBLIC_IP: None + } fake_ctx._target.node.properties = { 'nat': { 'edge_gateway': 'gateway' }, 'rules': [{ - 'type': 'DNAT' + 'type': 'DNAT', + }] } with mock.patch( - 'network_plugin.public_nat.ctx', fake_ctx + 'vcloud_plugin_common.ctx', fake_ctx ): - with mock.patch( - 'vcloud_plugin_common.ctx', fake_ctx - ): + self.assertFalse( public_nat.prepare_network_operation( - fake_client, network_plugin.DELETE + fake_ctx, fake_client, vcloud_network_plugin.DELETE ) + ) + # rules with default values + fake_client, fake_ctx = self.generate_client_and_context_network() + fake_ctx._target.node.properties = { + 'nat': { + 'edge_gateway': 'gateway' + }, + 'rules': [{ + 'type': 'DNAT' + }] + } + with mock.patch( + 'vcloud_plugin_common.ctx', fake_ctx + ): + public_nat.prepare_network_operation( + fake_ctx, fake_client, vcloud_network_plugin.DELETE + ) fake_client._vdc_gateway.del_nat_rule.assert_called_with( 'DNAT', '192.168.1.1', 'any', '127.1.1.100 - 127.1.1.200', 'any', 'any' @@ -713,7 +812,7 @@ def test_prepare_network_operation(self): def test_creation_validation(self): fake_client = self.generate_client() # no nat - fake_ctx = self.generate_node_context( + fake_ctx = self.generate_node_context_with_current_ctx( properties={ 'vcloud_config': { 'vdc': 'vdc_name' @@ -725,9 +824,9 @@ def test_creation_validation(self): mock.MagicMock(return_value=fake_client) ): with self.assertRaises(cfy_exc.NonRecoverableError): - public_nat.creation_validation(ctx=fake_ctx) + public_nat.creation_validation(ctx=fake_ctx, vca_client=None) # no gateway - fake_ctx = self.generate_node_context( + fake_ctx = self.generate_node_context_with_current_ctx( properties={ 'vcloud_config': { 'vdc': 'vdc_name' @@ -742,151 +841,172 @@ def test_creation_validation(self): mock.MagicMock(return_value=fake_client) ): with self.assertRaises(cfy_exc.NonRecoverableError): - public_nat.creation_validation(ctx=fake_ctx) + public_nat.creation_validation(ctx=fake_ctx, vca_client=None) # wrong ip - fake_ctx = self.generate_node_context(properties={ - 'vcloud_config': { - 'vdc': 'vdc_name', - 'service_type': vcloud_plugin_common.SUBSCRIPTION_SERVICE_TYPE - }, - 'nat': { - 'edge_gateway': 'gateway', - network_plugin.PUBLIC_IP: 'any' + fake_ctx = self.generate_node_context_with_current_ctx( + properties={ + 'vcloud_config': { + 'vdc': 'vdc_name', + 'service_type': + vcloud_plugin_common.SUBSCRIPTION_SERVICE_TYPE + }, + 'nat': { + 'edge_gateway': 'gateway', + vcloud_network_plugin.PUBLIC_IP: 'any' + } } - }) + ) with mock.patch( 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): with self.assertRaises(cfy_exc.NonRecoverableError): - public_nat.creation_validation(ctx=fake_ctx) + public_nat.creation_validation(ctx=fake_ctx, vca_client=None) # no free ip - fake_ctx = self.generate_node_context(properties={ - 'vcloud_config': { - 'vdc': 'vdc_name', - 'service_type': vcloud_plugin_common.SUBSCRIPTION_SERVICE_TYPE - }, - 'nat': { - 'edge_gateway': 'gateway' + fake_ctx = self.generate_node_context_with_current_ctx( + properties={ + 'vcloud_config': { + 'vdc': 'vdc_name', + 'service_type': + vcloud_plugin_common.SUBSCRIPTION_SERVICE_TYPE + }, + 'nat': { + 'edge_gateway': 'gateway' + } } - }) + ) with mock.patch( 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): with self.assertRaises(cfy_exc.NonRecoverableError): - public_nat.creation_validation(ctx=fake_ctx) + public_nat.creation_validation(ctx=fake_ctx, vca_client=None) # no rules - fake_ctx = self.generate_node_context(properties={ - 'vcloud_config': { - 'vdc': 'vdc_name', - 'service_type': vcloud_plugin_common.SUBSCRIPTION_SERVICE_TYPE - }, - 'nat': { - 'edge_gateway': 'gateway', - network_plugin.PUBLIC_IP: '10.12.2.1' + fake_ctx = self.generate_node_context_with_current_ctx( + properties={ + 'vcloud_config': { + 'vdc': 'vdc_name', + 'service_type': + vcloud_plugin_common.SUBSCRIPTION_SERVICE_TYPE + }, + 'nat': { + 'edge_gateway': 'gateway', + vcloud_network_plugin.PUBLIC_IP: '10.12.2.1' + } } - }) + ) with mock.patch( 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): with self.assertRaises(cfy_exc.NonRecoverableError): - public_nat.creation_validation(ctx=fake_ctx) + public_nat.creation_validation(ctx=fake_ctx, vca_client=None) # wrong protocol - fake_ctx = self.generate_node_context(properties={ - 'vcloud_config': { - 'vdc': 'vdc_name', - 'service_type': vcloud_plugin_common.SUBSCRIPTION_SERVICE_TYPE - }, - 'nat': { - 'edge_gateway': 'gateway', - network_plugin.PUBLIC_IP: '10.12.2.1' - }, - 'rules': [{ - 'type': 'DNAT', - 'protocol': "some" - }] - }) + fake_ctx = self.generate_node_context_with_current_ctx( + properties={ + 'vcloud_config': { + 'vdc': 'vdc_name', + 'service_type': + vcloud_plugin_common.SUBSCRIPTION_SERVICE_TYPE + }, + 'nat': { + 'edge_gateway': 'gateway', + vcloud_network_plugin.PUBLIC_IP: '10.12.2.1' + }, + 'rules': [{ + 'type': 'DNAT', + 'protocol': "some" + }] + } + ) with mock.patch( 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): with self.assertRaises(cfy_exc.NonRecoverableError): - public_nat.creation_validation(ctx=fake_ctx) + public_nat.creation_validation(ctx=fake_ctx, vca_client=None) # wrong original_port - fake_ctx = self.generate_node_context(properties={ - 'vcloud_config': { - 'vdc': 'vdc_name', - 'service_type': vcloud_plugin_common.SUBSCRIPTION_SERVICE_TYPE - }, - 'nat': { - 'edge_gateway': 'gateway', - network_plugin.PUBLIC_IP: '10.12.2.1' - }, - 'rules': [{ - 'type': 'DNAT', - 'protocol': "TCP", - 'original_port': 'some' - }] - }) + fake_ctx = self.generate_node_context_with_current_ctx( + properties={ + 'vcloud_config': { + 'vdc': 'vdc_name', + 'service_type': + vcloud_plugin_common.SUBSCRIPTION_SERVICE_TYPE + }, + 'nat': { + 'edge_gateway': 'gateway', + vcloud_network_plugin.PUBLIC_IP: '10.12.2.1' + }, + 'rules': [{ + 'type': 'DNAT', + 'protocol': "TCP", + 'original_port': 'some' + }] + } + ) with mock.patch( 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): with self.assertRaises(cfy_exc.NonRecoverableError): - public_nat.creation_validation(ctx=fake_ctx) + public_nat.creation_validation(ctx=fake_ctx, vca_client=None) # wrong original_port - fake_ctx = self.generate_node_context(properties={ - 'vcloud_config': { - 'vdc': 'vdc_name', - 'service_type': vcloud_plugin_common.SUBSCRIPTION_SERVICE_TYPE - }, - 'nat': { - 'edge_gateway': 'gateway', - network_plugin.PUBLIC_IP: '10.12.2.1' - }, - 'rules': [{ - 'type': 'DNAT', - 'protocol': "TCP", - 'original_port': 11, - 'translated_port': 'some' - }] - }) + fake_ctx = self.generate_node_context_with_current_ctx( + properties={ + 'vcloud_config': { + 'vdc': 'vdc_name', + 'service_type': + vcloud_plugin_common.SUBSCRIPTION_SERVICE_TYPE + }, + 'nat': { + 'edge_gateway': 'gateway', + vcloud_network_plugin.PUBLIC_IP: '10.12.2.1' + }, + 'rules': [{ + 'type': 'DNAT', + 'protocol': "TCP", + 'original_port': 11, + 'translated_port': 'some' + }] + } + ) with mock.patch( 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): with self.assertRaises(cfy_exc.NonRecoverableError): - public_nat.creation_validation(ctx=fake_ctx) + public_nat.creation_validation(ctx=fake_ctx, vca_client=None) # fine - fake_ctx = self.generate_node_context(properties={ - 'vcloud_config': { - 'vdc': 'vdc_name', - 'service_type': vcloud_plugin_common.SUBSCRIPTION_SERVICE_TYPE - }, - 'nat': { - 'edge_gateway': 'gateway', - network_plugin.PUBLIC_IP: '10.12.2.1' - }, - 'rules': [{ - 'type': 'DNAT', - 'protocol': "TCP", - 'original_port': 11, - 'translated_port': 12 - }] - }) + fake_ctx = self.generate_node_context_with_current_ctx( + properties={ + 'vcloud_config': { + 'vdc': 'vdc_name', + 'service_type': + vcloud_plugin_common.SUBSCRIPTION_SERVICE_TYPE + }, + 'nat': { + 'edge_gateway': 'gateway', + vcloud_network_plugin.PUBLIC_IP: '10.12.2.1' + }, + 'rules': [{ + 'type': 'DNAT', + 'protocol': "TCP", + 'original_port': 11, + 'translated_port': 12 + }] + } + ) with mock.patch( 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): - public_nat.creation_validation(ctx=fake_ctx) + public_nat.creation_validation(ctx=fake_ctx, vca_client=None) - def test_server_disconnect_from_nat(self): + def _server_disconnect_to_nat_noexternal(self): fake_client, fake_ctx = self.generate_client_and_context_server() fake_ctx._target.instance.runtime_properties = { - network_plugin.PUBLIC_IP: '192.168.1.1' + vcloud_network_plugin.PUBLIC_IP: '192.168.1.1' } fake_ctx._target.node.properties = { 'nat': { @@ -896,19 +1016,57 @@ def test_server_disconnect_from_nat(self): 'type': 'DNAT' }] } + fake_ctx._source.node.properties = { + 'vcloud_config': + { + 'edge_gateway': 'gateway', + 'vdc': 'vdc' + } + } + fake_ctx._source.instance.runtime_properties = { + 'gateway_lock': False, + 'vcloud_vapp_name': 'vapp' + } + return fake_client, fake_ctx + + def test_server_disconnect_from_nat(self): + # successful + fake_client, fake_ctx = self._server_disconnect_to_nat_noexternal() with mock.patch( 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): - public_nat.server_disconnect_from_nat(ctx=fake_ctx) + public_nat.server_disconnect_from_nat(ctx=fake_ctx, + vca_client=None) fake_client._vdc_gateway.del_nat_rule.assert_called_with( 'DNAT', '192.168.1.1', 'any', '1.1.1.1', 'any', 'any' ) + # check retry + fake_client, fake_ctx = self._server_disconnect_to_nat_noexternal() + with mock.patch( + 'vcloud_plugin_common.VcloudAirClient.get', + mock.MagicMock(return_value=fake_client) + ): + self.prepere_gatway_busy_retry(fake_client, fake_ctx) + public_nat.server_disconnect_from_nat(ctx=fake_ctx, + vca_client=None) + self.check_retry_realy_called(fake_ctx) - def test_server_connect_to_nat(self): + def _server_connect_to_nat_noexternal(self): fake_client, fake_ctx = self.generate_client_and_context_server() fake_ctx._target.instance.runtime_properties = { - network_plugin.PUBLIC_IP: '192.168.1.1' + vcloud_network_plugin.PUBLIC_IP: '192.168.1.1' + } + fake_ctx._source.instance.runtime_properties = { + 'gateway_lock': False, + 'vcloud_vapp_name': 'vapp' + } + fake_ctx._source.node.properties = { + 'vcloud_config': + { + 'edge_gateway': 'gateway', + 'vdc': 'vdc' + } } fake_ctx._target.node.properties = { 'nat': { @@ -918,30 +1076,32 @@ def test_server_connect_to_nat(self): 'type': 'DNAT' }] } + fake_client._vdc_gateway.get_public_ips = mock.MagicMock( return_value=['10.18.1.1'] ) + return fake_client, fake_ctx + + def test_server_connect_to_nat(self): + fake_client, fake_ctx = self._server_connect_to_nat_noexternal() with mock.patch( 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): - public_nat.server_connect_to_nat(ctx=fake_ctx) + public_nat.server_connect_to_nat(ctx=fake_ctx, vca_client=None) fake_client._vdc_gateway.add_nat_rule.assert_called_with( 'DNAT', '10.18.1.1', 'any', '1.1.1.1', 'any', 'any' ) - - def test_net_disconnect_from_nat(self): - # use external - fake_client, fake_ctx = self.generate_client_and_context_network() - fake_ctx._target.node.properties = { - 'use_external_resource': True - } + fake_client, fake_ctx = self._server_connect_to_nat_noexternal() with mock.patch( 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): - public_nat.net_disconnect_from_nat(ctx=fake_ctx) - # no external + self.prepere_gatway_busy_retry(fake_client, fake_ctx) + public_nat.server_connect_to_nat(ctx=fake_ctx, vca_client=None) + self.check_retry_realy_called(fake_ctx) + + def _net_disconnect_from_nat_noexternal(self): fake_client, fake_ctx = self.generate_client_and_context_network() fake_ctx._target.node.properties = { 'nat': { @@ -951,15 +1111,59 @@ def test_net_disconnect_from_nat(self): 'type': 'DNAT' }] } + fake_ctx._source.node.properties = { + 'vcloud_config': + { + 'edge_gateway': 'gateway', + 'vdc': 'vdc' + } + } + return fake_client, fake_ctx + + def test_net_disconnect_from_nat(self): + # use external + fake_client, fake_ctx = self.generate_client_and_context_network() + fake_ctx._target.node.properties = { + 'use_external_resource': True + } + fake_ctx._source.node.properties = { + 'vcloud_config': + { + 'edge_gateway': 'gateway', + 'vdc': 'vdc' + } + } + fake_ctx._source.instance.runtime_properties = { + 'gateway_lock': False, + 'vcloud_vapp_name': 'vapp' + } + with mock.patch( 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): - public_nat.net_disconnect_from_nat(ctx=fake_ctx) + public_nat.net_disconnect_from_nat(ctx=fake_ctx, + vca_client=fake_client) + # no external + fake_client, fake_ctx = self._net_disconnect_from_nat_noexternal() + with mock.patch( + 'vcloud_plugin_common.VcloudAirClient.get', + mock.MagicMock(return_value=fake_client) + ): + public_nat.net_disconnect_from_nat(ctx=fake_ctx, vca_client=None) fake_client._vdc_gateway.del_nat_rule.assert_called_with( 'DNAT', '192.168.1.1', 'any', '127.1.1.100 - 127.1.1.200', 'any', 'any' ) + # retry check + fake_client, fake_ctx = self._net_disconnect_from_nat_noexternal() + with mock.patch( + 'vcloud_plugin_common.VcloudAirClient.get', + mock.MagicMock(return_value=fake_client) + ): + self.prepere_gatway_busy_retry(fake_client, fake_ctx) + public_nat.net_disconnect_from_nat(ctx=fake_ctx, vca_client=None) + self.check_retry_realy_called(fake_ctx) def test_net_connect_to_nat(self): # use external @@ -967,11 +1171,19 @@ def test_net_connect_to_nat(self): fake_ctx._target.node.properties = { 'use_external_resource': True } + fake_ctx._source.node.properties = { + 'vcloud_config': + { + 'edge_gateway': 'gateway', + 'vdc': 'vdc' + } + } + with mock.patch( 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): - public_nat.net_connect_to_nat(ctx=fake_ctx) + public_nat.net_connect_to_nat(ctx=fake_ctx, vca_client=None) # no external fake_client, fake_ctx = self.generate_client_and_context_network() fake_ctx._target.node.properties = { @@ -982,6 +1194,13 @@ def test_net_connect_to_nat(self): 'type': 'DNAT' }] } + fake_ctx._source.node.properties = { + 'vcloud_config': + { + 'edge_gateway': 'gateway', + 'vdc': 'vdc' + } + } fake_client._vdc_gateway.get_public_ips = mock.MagicMock(return_value=[ '10.18.1.1' ]) @@ -989,11 +1208,63 @@ def test_net_connect_to_nat(self): 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): - public_nat.net_connect_to_nat(ctx=fake_ctx) + public_nat.net_connect_to_nat(ctx=fake_ctx, vca_client=None) fake_client._vdc_gateway.add_nat_rule.assert_called_with( 'DNAT', '10.18.1.1', 'any', '127.1.1.100 - 127.1.1.200', 'any', 'any' ) + # retry check + with mock.patch( + 'vcloud_plugin_common.VcloudAirClient.get', + mock.MagicMock(return_value=fake_client) + ): + self.prepere_gatway_busy_retry(fake_client, fake_ctx) + public_nat.net_connect_to_nat(ctx=fake_ctx, vca_client=None) + self.check_retry_realy_called(fake_ctx) + + def test_net_connect_to_nat_preconfigure(self): + fake_client, fake_ctx = self.generate_client_and_context_network() + fake_ctx._target.node.properties = { + 'nat': { + 'edge_gateway': 'gateway' + }, + 'rules': [{ + 'type': 'DNAT' + }] + } + with mock.patch( + 'vcloud_plugin_common.VcloudAirClient.get', + mock.MagicMock(return_value=fake_client) + ): + with self.assertRaises(cfy_exc.NonRecoverableError): + public_nat.net_connect_to_nat_preconfigure(ctx=fake_ctx, + vca_client=None) + + fake_client, fake_ctx = self.generate_client_and_context_network() + fake_ctx._target.node.properties = { + 'nat': { + 'edge_gateway': 'gateway' + }, + 'rules': [{ + 'type': 'SNAT' + }] + } + with mock.patch( + 'vcloud_plugin_common.VcloudAirClient.get', + mock.MagicMock(return_value=fake_client) + ): + public_nat.net_connect_to_nat_preconfigure(ctx=fake_ctx, + vca_client=None) + # empty rules + fake_ctx._target.node.properties.update({'rules': []}) + with mock.patch( + 'vcloud_plugin_common.VcloudAirClient.get', + mock.MagicMock(return_value=fake_client) + ): + with self.assertRaises(cfy_exc.NonRecoverableError): + public_nat.net_connect_to_nat_preconfigure(ctx=fake_ctx, + vca_client=None) + if __name__ == '__main__': unittest.main() diff --git a/tests/unittests/test_mock_network_plugin_security_group.py b/tests/unittests/test_mock_network_plugin_security_group.py index eb56c05..2588906 100644 --- a/tests/unittests/test_mock_network_plugin_security_group.py +++ b/tests/unittests/test_mock_network_plugin_security_group.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved +# Copyright (c) 2014-2020 Cloudify Platform Ltd. All rights reserved # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -8,16 +8,16 @@ # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, -# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# * See the License for the specific language governing permissions and -# * limitations under the License. +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. import mock import unittest from cloudify import exceptions as cfy_exc from tests.unittests import test_mock_base -from network_plugin import security_group +from vcloud_network_plugin import security_group import vcloud_plugin_common @@ -34,7 +34,7 @@ def test_get_gateway_name_from_params(self): ) def test_get_gateway_name_from_ctx(self): - fake_ctx = self.generate_node_context( + fake_ctx = self.generate_node_context_with_current_ctx( properties={ 'vcloud_config': { 'edge_gateway': 'some_edge_gateway' @@ -50,7 +50,7 @@ def test_get_gateway_name_from_ctx(self): ) def generate_context_for_security_group(self): - fake_ctx = self.generate_relation_context() + fake_ctx = self.generate_relation_context_with_current_ctx() fake_ctx._source.node.properties = { 'vcloud_config': { 'edge_gateway': 'some_edge_gateway', @@ -77,11 +77,10 @@ def check_rule_operation(self, rule_type, rules, vms_networks=None): gateway.delete_fw_rule = mock.MagicMock(return_value=None) # any networks will be routed self.set_network_routed_in_client(fake_client) - with mock.patch('network_plugin.security_group.ctx', fake_ctx): - with mock.patch('vcloud_plugin_common.ctx', fake_ctx): - security_group._rule_operation( - rule_type, fake_client - ) + with mock.patch('vcloud_plugin_common.ctx', fake_ctx): + security_group._rule_operation( + fake_ctx, rule_type, fake_client + ) return gateway def check_rule_operation_fail(self, rule_type, rules): @@ -93,17 +92,13 @@ def check_rule_operation_fail(self, rule_type, rules): # check busy gateway = fake_client._vdc_gateway self.set_gateway_busy(gateway) - self.prepare_retry(fake_ctx) self.set_services_conf_result( fake_client._vdc_gateway, None ) - with mock.patch('network_plugin.security_group.ctx', fake_ctx): - with mock.patch('vcloud_plugin_common.ctx', fake_ctx): - security_group._rule_operation( - rule_type, fake_client - ) - - self.check_retry_realy_called(fake_ctx) + with mock.patch('vcloud_plugin_common.ctx', fake_ctx): + self.assertFalse(security_group._rule_operation( + fake_ctx, rule_type, fake_client + )) def test_rule_operation_empty_rule(self): for rule_type in [ @@ -340,6 +335,17 @@ def test_create(self): fake_ctx._target.node.properties = { 'rules': [] } + fake_ctx._source.node.properties = { + 'vcloud_config': + { + 'edge_gateway': 'gateway', + 'vdc': 'vdc' + } + } + fake_ctx._source.instance.runtime_properties = { + 'gateway_lock': False, + 'vcloud_vapp_name': 'vapp' + } self.set_services_conf_result( fake_client._vdc_gateway, vcloud_plugin_common.TASK_STATUS_SUCCESS ) @@ -347,7 +353,12 @@ def test_create(self): 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): - security_group.create(ctx=fake_ctx) + # success case + security_group.create(ctx=fake_ctx, vca_client=None) + # with retry + self.prepere_gatway_busy_retry(fake_client, fake_ctx) + security_group.create(ctx=fake_ctx, vca_client=None) + self.check_retry_realy_called(fake_ctx) def test_delete(self): fake_ctx = self.generate_context_for_security_group() @@ -356,6 +367,17 @@ def test_delete(self): fake_ctx._target.node.properties = { 'rules': [] } + fake_ctx._source.node.properties = { + 'vcloud_config': + { + 'edge_gateway': 'gateway', + 'vdc': 'vdc' + } + } + fake_ctx._source.instance.runtime_properties = { + 'gateway_lock': False, + 'vcloud_vapp_name': 'vapp' + } self.set_services_conf_result( fake_client._vdc_gateway, vcloud_plugin_common.TASK_STATUS_SUCCESS ) @@ -363,7 +385,12 @@ def test_delete(self): 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): - security_group.delete(ctx=fake_ctx) + # successful + security_group.delete(ctx=fake_ctx, vca_client=None) + # with retry + self.prepere_gatway_busy_retry(fake_client, fake_ctx) + security_group.delete(ctx=fake_ctx, vca_client=None) + self.check_retry_realy_called(fake_ctx) def check_creation_validation(self, rule): fake_client = self.generate_client() @@ -371,7 +398,7 @@ def check_creation_validation(self, rule): 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): - fake_ctx = self.generate_node_context( + fake_ctx = self.generate_node_context_with_current_ctx( properties={ 'vcloud_config': { 'edge_gateway': 'some_edge_gateway', @@ -380,7 +407,8 @@ def check_creation_validation(self, rule): 'rules': [rule] } ) - security_group.creation_validation(ctx=fake_ctx) + security_group.creation_validation(ctx=fake_ctx, + vca_client=None) def test_creation_validation(self): fake_client = self.generate_client() @@ -388,7 +416,7 @@ def test_creation_validation(self): 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): - fake_ctx = self.generate_node_context( + fake_ctx = self.generate_node_context_with_current_ctx( properties={ 'vcloud_config': { 'edge_gateway': 'some_edge_gateway', @@ -401,13 +429,15 @@ def test_creation_validation(self): ) # Gateway firewall is disabled with self.assertRaises(cfy_exc.NonRecoverableError): - security_group.creation_validation(ctx=fake_ctx) + security_group.creation_validation(ctx=fake_ctx, + vca_client=None) fake_client._vdc_gateway.is_fw_enabled = mock.MagicMock( return_value=True ) # no rules with self.assertRaises(cfy_exc.NonRecoverableError): - security_group.creation_validation(ctx=fake_ctx) + security_group.creation_validation(ctx=fake_ctx, + vca_client=None) # wrong description with self.assertRaises(cfy_exc.NonRecoverableError): self.check_creation_validation({ @@ -420,7 +450,8 @@ def test_creation_validation(self): "source": 11 }) with self.assertRaises(cfy_exc.NonRecoverableError): - security_group.creation_validation(ctx=fake_ctx) + security_group.creation_validation(ctx=fake_ctx, + vca_client=None) # wrong ip with self.assertRaises(cfy_exc.NonRecoverableError): self.check_creation_validation({ diff --git a/tests/unittests/test_mock_server_plugin_server.py b/tests/unittests/test_mock_server_plugin_server.py index be60ab3..e46adeb 100644 --- a/tests/unittests/test_mock_server_plugin_server.py +++ b/tests/unittests/test_mock_server_plugin_server.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved +# Copyright (c) 2014-2020 Cloudify Platform Ltd. All rights reserved # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -8,23 +8,24 @@ # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, -# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# * See the License for the specific language governing permissions and -# * limitations under the License. +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. import mock import unittest from cloudify import exceptions as cfy_exc -from server_plugin import server +from vcloud_server_plugin import server import vcloud_plugin_common +import vcloud_network_plugin from tests.unittests import test_mock_base class ServerPluginServerMockTestCase(test_mock_base.TestBase): def test_delete_external_resource(self): - fake_ctx = self.generate_node_context( + fake_ctx = self.generate_node_context_with_current_ctx( properties={ 'use_external_resource': True } @@ -34,14 +35,14 @@ def test_delete_external_resource(self): 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): - server.delete(ctx=fake_ctx) + server.delete(ctx=fake_ctx, vca_client=None) self.assertFalse( server.VCLOUD_VAPP_NAME in fake_ctx.instance.runtime_properties ) def test_delete(self): - fake_ctx = self.generate_node_context() + fake_ctx = self.generate_node_context_with_current_ctx() fake_client = self.generate_client() with mock.patch( 'vcloud_plugin_common.VcloudAirClient.get', @@ -52,7 +53,7 @@ def test_delete(self): return_value=None ) with self.assertRaises(cfy_exc.NonRecoverableError): - server.delete(ctx=fake_ctx) + server.delete(ctx=fake_ctx, vca_client=None) fake_client._vapp.delete.assert_called_with() self.check_get_vapp(fake_client, 'vapp_name') @@ -64,7 +65,7 @@ def test_delete(self): return_value=fake_task ) with self.assertRaises(cfy_exc.NonRecoverableError): - server.delete(ctx=fake_ctx) + server.delete(ctx=fake_ctx, vca_client=None) # success fake_task = self.generate_task( @@ -73,13 +74,13 @@ def test_delete(self): fake_client._vapp.delete = mock.MagicMock( return_value=fake_task ) - server.delete(ctx=fake_ctx) + server.delete(ctx=fake_ctx, vca_client=None) self.assertFalse( server.VCLOUD_VAPP_NAME in fake_ctx.instance.runtime_properties ) def test_stop_external_resource(self): - fake_ctx = self.generate_node_context( + fake_ctx = self.generate_node_context_with_current_ctx( properties={ 'use_external_resource': True } @@ -89,14 +90,14 @@ def test_stop_external_resource(self): 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): - server.stop(ctx=fake_ctx) + server.stop(ctx=fake_ctx, vca_client=None) self.assertTrue( server.VCLOUD_VAPP_NAME in fake_ctx.instance.runtime_properties ) def test_stop(self): - fake_ctx = self.generate_node_context() + fake_ctx = self.generate_node_context_with_current_ctx() fake_client = self.generate_client() with mock.patch( 'vcloud_plugin_common.VcloudAirClient.get', @@ -106,7 +107,7 @@ def test_stop(self): return_value=None ) with self.assertRaises(cfy_exc.NonRecoverableError): - server.stop(ctx=fake_ctx) + server.stop(ctx=fake_ctx, vca_client=None) fake_client._vapp.undeploy.assert_called_with() self.check_get_vapp(fake_client, 'vapp_name') @@ -118,7 +119,7 @@ def test_stop(self): return_value=fake_task ) with self.assertRaises(cfy_exc.NonRecoverableError): - server.stop(ctx=fake_ctx) + server.stop(ctx=fake_ctx, vca_client=None) # success fake_task = self.generate_task( @@ -127,7 +128,7 @@ def test_stop(self): fake_client._vapp.undeploy = mock.MagicMock( return_value=fake_task ) - server.stop(ctx=fake_ctx) + server.stop(ctx=fake_ctx, vca_client=None) self.assertTrue( server.VCLOUD_VAPP_NAME in fake_ctx.instance.runtime_properties ) @@ -139,9 +140,10 @@ def check_get_vapp(self, fake_client, vapp_name): ) def test_start(self): - fake_ctx = self.generate_node_context() + fake_ctx = self.generate_node_context_with_current_ctx() fake_client = self.generate_client([{ 'is_connected': True, + 'is_primary': True, 'network_name': 'network_name', 'ip': '1.1.1.1' }]) @@ -157,7 +159,7 @@ def test_start(self): return_value=None ) with self.assertRaises(cfy_exc.NonRecoverableError): - server.start(ctx=fake_ctx) + server.start(ctx=fake_ctx, vca_client=None) fake_client._vapp.poweron.assert_called_with() self.check_get_vapp(fake_client, 'vapp_name') @@ -172,8 +174,18 @@ def test_start(self): return_value=fake_task ) with self.assertRaises(cfy_exc.NonRecoverableError): - server.start(ctx=fake_ctx) + server.start(ctx=fake_ctx, vca_client=None) + fake_client = self.generate_client([{ + 'is_connected': False, + 'is_primary': False, + 'network_name': 'network_name', + 'ip': '1.1.1.1' + }]) + with mock.patch( + 'vcloud_plugin_common.VcloudAirClient.get', + mock.MagicMock(return_value=fake_client) + ): # poweroff with success in task but not connected fake_client._vapp.me.get_status = mock.MagicMock( return_value=vcloud_plugin_common.STATUS_POWERED_OFF @@ -184,18 +196,19 @@ def test_start(self): fake_client._vapp.poweron = mock.MagicMock( return_value=fake_task ) - with self.assertRaises(cfy_exc.OperationRetry): - server.start(ctx=fake_ctx) + self.assertEquals( + server.start(ctx=fake_ctx, vca_client=None), None) # poweron with success in task but not connected fake_client._vapp.me.get_status = mock.MagicMock( return_value=vcloud_plugin_common.STATUS_POWERED_OFF ) - with self.assertRaises(cfy_exc.OperationRetry): - server.start(ctx=fake_ctx) + self.assertEquals( + server.start(ctx=fake_ctx, vca_client=None), None) fake_client = self.generate_client([{ 'is_connected': True, + 'is_primary': True, 'network_name': '_management_network', 'ip': '1.1.1.1' }]) @@ -207,14 +220,34 @@ def test_start(self): fake_client._vapp.me.get_status = mock.MagicMock( return_value=vcloud_plugin_common.STATUS_POWERED_ON ) - with self.assertRaises(cfy_exc.OperationRetry): - server.start(ctx=fake_ctx) + self.assertEquals( + server.start(ctx=fake_ctx, vca_client=None), None) + + # use external without any power state changes, run retry + fake_ctx = self.generate_node_context_with_current_ctx() + fake_ctx.node.properties['use_external_resource'] = True + fake_client = self.generate_client([{ + 'is_connected': True, + 'is_primary': True, + 'network_name': '_management_network', + 'ip': None + }]) + with mock.patch( + 'vcloud_plugin_common.VcloudAirClient.get', + mock.MagicMock(return_value=fake_client) + ): + self.prepare_retry(fake_ctx) + server.start(ctx=fake_ctx, vca_client=None) + self.check_retry_realy_called( + fake_ctx, + "Waiting for VM's configuration to complete", 5 + ) def test_start_external_resource(self): """ start with external resource, as success status used retry """ - fake_ctx = self.generate_node_context( + fake_ctx = self.generate_node_context_with_current_ctx( properties={ 'use_external_resource': True, 'vcloud_config': { @@ -225,6 +258,7 @@ def test_start_external_resource(self): ) fake_client = self.generate_client([{ 'is_connected': True, + 'is_primary': True, 'network_name': 'network_name', 'ip': '1.1.1.1' }]) @@ -232,23 +266,25 @@ def test_start_external_resource(self): 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): - with self.assertRaises(cfy_exc.OperationRetry): - server.start(ctx=fake_ctx) + self.assertEquals( + server.start(ctx=fake_ctx, vca_client=None), None) def test_create_default_values(self): """ test server create with default value and error in request """ - fake_ctx = self.generate_node_context(properties={ - 'management_network': '_management_network', - 'vcloud_config': { - 'vdc': 'vdc_name' - }, - 'server': { - 'template': 'template', - 'catalog': 'catalog' + fake_ctx = self.generate_node_context_with_current_ctx( + properties={ + 'management_network': '_management_network', + 'vcloud_config': { + 'vdc': 'vdc_name' + }, + 'server': { + 'template': 'template', + 'catalog': 'catalog' + } } - }) + ) fake_client = self.generate_client() with mock.patch( 'vcloud_plugin_common.VcloudAirClient.get', @@ -257,17 +293,37 @@ def test_create_default_values(self): self.run_with_statuses( fake_client, fake_ctx ) + fake_ctx.instance._relationships = None with self.assertRaises(cfy_exc.NonRecoverableError): - server.create(ctx=fake_ctx) - fake_client.create_vapp.assert_called_with( - 'vdc_name', 'test', 'template', 'catalog', - vm_name='test') + server.create(ctx=fake_ctx, vca_client=None) + self.check_create_call(fake_client, fake_ctx) - def test_create_cpu_mem_values(self): + def test_create_configure_cpu_mem_values(self): """ check custom cpu/memmory with error in task """ - fake_ctx = self.generate_node_context( + # use existed vm + fake_ctx = self.generate_node_context_with_current_ctx( + properties={ + 'management_network': '_management_network', + 'vcloud_config': { + 'vdc': 'vdc_name' + }, + 'use_external_resource': True, + 'resource_id': 'some_server' + }, + relation_node_properties={ + "not_test": "not_test" + } + ) + fake_client = self.generate_client() + with mock.patch( + 'vcloud_plugin_common.VcloudAirClient.get', + mock.MagicMock(return_value=fake_client) + ): + server.configure(ctx=fake_ctx, vca_client=None) + # can't get vapp + fake_ctx = self.generate_node_context_with_current_ctx( properties={ 'management_network': '_management_network', 'vcloud_config': { @@ -279,6 +335,12 @@ def test_create_cpu_mem_values(self): 'hardware': { 'cpu': 1, 'memory': 512 + }, + 'guest_customization': { + 'pre_script': 'pre_script', + 'post_script': 'post_script', + 'admin_password': 'pass', + 'computer_name': 'computer' } } }, @@ -287,11 +349,22 @@ def test_create_cpu_mem_values(self): } ) fake_client = self.generate_client() + fake_client.get_vapp = mock.MagicMock(return_value=None) + with mock.patch( + 'vcloud_plugin_common.VcloudAirClient.get', + mock.MagicMock(return_value=fake_client) + ): + with self.assertRaises(cfy_exc.NonRecoverableError): + server.configure(ctx=fake_ctx, vca_client=None) + # create new vm + fake_client = self.generate_client() self.run_with_statuses( fake_client, fake_ctx, vcloud_plugin_common.TASK_STATUS_SUCCESS, vcloud_plugin_common.TASK_STATUS_SUCCESS, vcloud_plugin_common.TASK_STATUS_SUCCESS, + vcloud_plugin_common.TASK_STATUS_SUCCESS, + vcloud_plugin_common.TASK_STATUS_SUCCESS, vcloud_plugin_common.TASK_STATUS_SUCCESS ) with mock.patch( @@ -300,10 +373,7 @@ def test_create_cpu_mem_values(self): ): # can't customize memory with self.assertRaises(cfy_exc.NonRecoverableError): - server.create(ctx=fake_ctx) - fake_client.create_vapp.assert_called_with( - 'vdc_name', 'test', 'ubuntu', 'public', - vm_name='test') + server.configure(ctx=fake_ctx, vca_client=None) fake_client._vapp.modify_vm_memory.assert_called_with( 'test', 512 ) @@ -312,9 +382,10 @@ def test_create_cpu_mem_values(self): vcloud_plugin_common.TASK_STATUS_SUCCESS ) ) + # can't customize cpu with self.assertRaises(cfy_exc.NonRecoverableError): - server.create(ctx=fake_ctx) + server.configure(ctx=fake_ctx, vca_client=None) fake_client._vapp.modify_vm_cpu.assert_called_with( 'test', 1 ) @@ -323,21 +394,85 @@ def test_create_cpu_mem_values(self): vcloud_plugin_common.TASK_STATUS_SUCCESS ) ) + fake_client._vapp.modify_vm_name = mock.MagicMock( + return_value=self.generate_task( + vcloud_plugin_common.TASK_STATUS_SUCCESS + ) + ) + + # need force customization, successfull customization + fake_client._vapp.customize_on_next_poweron = mock.MagicMock( + return_value=False + ) + server.configure(ctx=fake_ctx, vca_client=None) + + # somethin wrong with force_customization + fake_client._vapp.force_customization = mock.MagicMock( + return_value=None + ) + with self.assertRaises(cfy_exc.NonRecoverableError): + server.configure(ctx=fake_ctx, vca_client=None) + # everything fine - server.create(ctx=fake_ctx) + fake_client._vapp.customize_on_next_poweron = mock.MagicMock( + return_value=True + ) + server.configure(ctx=fake_ctx, vca_client=None) + server.create(ctx=fake_ctx, vca_client=None) + fake_client._vapp.modify_vm_name.assert_called_with( + 1, 'test' + ) - def check_create_call(self, fake_client, fake_ctx): + # we dont have connected ip + fake_client._vapp.get_vms_network_info = mock.MagicMock( + return_value=[[{'is_connected': False}]] + ) + fake_client._vapp.me.get_status = mock.MagicMock( + return_value=vcloud_plugin_common.STATUS_POWERED_ON + ) + sleep_mock = mock.MagicMock() + with mock.patch( + 'time.sleep', + sleep_mock + ): + server.configure(ctx=fake_ctx, vca_client=None) + sleep_mock.assert_called_with( + vcloud_network_plugin.GATEWAY_TIMEOUT + ) + # after first run we have ip + vapp_with_network = mock.MagicMock() + vapp_with_network.get_vms_network_info = mock.MagicMock( + return_value=[[{ + 'is_connected': True, + 'is_primary': True, + 'network_name': 'network_name', + 'ip': '1.1.1.1' + }]] + ) + fake_client.get_vapp = mock.MagicMock( + side_effect=[fake_client._vapp, vapp_with_network] + ) + with mock.patch( + 'time.sleep', + sleep_mock + ): + server.configure(ctx=fake_ctx, vca_client=None) + + def check_create_call(self, fake_client, fake_ctx, positive=True): fake_client.create_vapp.assert_called_with( - 'vdc_name', 'test', 'template', 'catalog', - vm_name='test') - self.assertTrue( + 'vdc_name', 'test', 'template', 'catalog' + ) + + self.assertEqual( + positive, server.VCLOUD_VAPP_NAME in fake_ctx.instance.runtime_properties ) def run_with_statuses( self, fake_client, fake_ctx, - create_app=None, connect_to_network=None, connect_vms=None, - customize_guest_os=None + create_app=None, modify_vm_name=None, + connect_to_network=None, connect_vms=None, + customize_guest_os=None, force_customization=None ): fake_task = None if create_app: @@ -346,6 +481,13 @@ def run_with_statuses( return_value=fake_task ) + fake_modify_vm_name = None + if modify_vm_name: + fake_modify_vm_name = self.generate_task(modify_vm_name) + fake_client._vapp.modify_vm_name = mock.MagicMock( + return_value=fake_modify_vm_name + ) + fake_task_network = None if connect_to_network: fake_task_network = self.generate_task(connect_to_network) @@ -367,8 +509,15 @@ def run_with_statuses( return_value=fake_task_customize ) + fake_force_customization = None + if force_customization: + fake_force_customization = self.generate_task(force_customization) + fake_client._vapp.force_customization = mock.MagicMock( + return_value=fake_force_customization + ) + def generate_context_for_create(self): - return self.generate_node_context( + return self.generate_node_context_with_current_ctx( properties={ 'management_network': '_management_network', 'vcloud_config': { @@ -385,7 +534,7 @@ def generate_context_for_create(self): ) def generate_context_for_customization(self): - return self.generate_node_context( + return self.generate_node_context_with_current_ctx( properties={ 'management_network': '_management_network', 'vcloud_config': { @@ -412,10 +561,13 @@ def test_create_external_resource(self): """ must run without any errors """ - fake_ctx = self.generate_node_context( + fake_ctx = self.generate_node_context_with_current_ctx( properties={ 'use_external_resource': True, - 'resource_id': 'ServerName' + 'resource_id': 'ServerName', + 'vcloud_config': { + 'vdc': 'vdc_name' + }, } ) @@ -423,7 +575,7 @@ def test_create_external_resource(self): 'vcloud_plugin_common.VcloudAirClient', self.generate_vca() ): - server.create(ctx=fake_ctx) + server.create(ctx=fake_ctx, vca_client=None) self.assertTrue( server.VCLOUD_VAPP_NAME in fake_ctx.instance.runtime_properties ) @@ -441,6 +593,7 @@ def test_create_connection_error(self): self.run_with_statuses( fake_client, fake_ctx, vcloud_plugin_common.TASK_STATUS_SUCCESS, + vcloud_plugin_common.TASK_STATUS_SUCCESS, vcloud_plugin_common.TASK_STATUS_ERROR ) with mock.patch( @@ -448,7 +601,26 @@ def test_create_connection_error(self): mock.MagicMock(return_value=fake_client) ): with self.assertRaises(cfy_exc.NonRecoverableError): - server.create(ctx=fake_ctx) + server.create(ctx=fake_ctx, vca_client=None) + self.check_create_call(fake_client, fake_ctx) + + def test_create_cant_change_name(self): + """ + test server create with default value and empty task + from change name + """ + fake_ctx = self.generate_context_for_create() + fake_client = self.generate_client() + self.run_with_statuses( + fake_client, fake_ctx, + vcloud_plugin_common.TASK_STATUS_SUCCESS + ) + with mock.patch( + 'vcloud_plugin_common.VcloudAirClient.get', + mock.MagicMock(return_value=fake_client) + ): + with self.assertRaises(cfy_exc.NonRecoverableError): + server.create(ctx=fake_ctx, vca_client=None) self.check_create_call(fake_client, fake_ctx) def test_create_connection_empty_task(self): @@ -460,6 +632,7 @@ def test_create_connection_empty_task(self): fake_client = self.generate_client() self.run_with_statuses( fake_client, fake_ctx, + vcloud_plugin_common.TASK_STATUS_SUCCESS, vcloud_plugin_common.TASK_STATUS_SUCCESS ) with mock.patch( @@ -467,13 +640,14 @@ def test_create_connection_empty_task(self): mock.MagicMock(return_value=fake_client) ): with self.assertRaises(cfy_exc.NonRecoverableError): - server.create(ctx=fake_ctx) + server.create(ctx=fake_ctx, vca_client=None) self.check_create_call(fake_client, fake_ctx) def test_create_cant_get_vapp(self): """ test server create with default value and empty vapp """ + # with create fake_ctx = self.generate_context_for_create() fake_client = self.generate_client() fake_client.get_vapp = mock.MagicMock( @@ -482,6 +656,7 @@ def test_create_cant_get_vapp(self): self.run_with_statuses( fake_client, fake_ctx, vcloud_plugin_common.TASK_STATUS_SUCCESS, + vcloud_plugin_common.TASK_STATUS_SUCCESS, vcloud_plugin_common.TASK_STATUS_SUCCESS ) with mock.patch( @@ -489,8 +664,17 @@ def test_create_cant_get_vapp(self): mock.MagicMock(return_value=fake_client) ): with self.assertRaises(cfy_exc.NonRecoverableError): - server.create(ctx=fake_ctx) + server.create(ctx=fake_ctx, vca_client=None) self.check_create_call(fake_client, fake_ctx) + # use external resource + fake_ctx.node.properties['use_external_resource'] = True + fake_ctx.node.properties['resource_id'] = 'someresource' + with mock.patch( + 'vcloud_plugin_common.VcloudAirClient.get', + mock.MagicMock(return_value=fake_client) + ): + with self.assertRaises(cfy_exc.NonRecoverableError): + server.create(ctx=fake_ctx, vca_client=None) def test_create_link_empty(self): """ @@ -501,6 +685,7 @@ def test_create_link_empty(self): self.run_with_statuses( fake_client, fake_ctx, vcloud_plugin_common.TASK_STATUS_SUCCESS, + vcloud_plugin_common.TASK_STATUS_SUCCESS, vcloud_plugin_common.TASK_STATUS_SUCCESS ) with mock.patch( @@ -509,7 +694,7 @@ def test_create_link_empty(self): ): # link empty with self.assertRaises(cfy_exc.NonRecoverableError): - server.create(ctx=fake_ctx) + server.create(ctx=fake_ctx, vca_client=None) self.check_create_call(fake_client, fake_ctx) def test_create_link_error(self): @@ -522,6 +707,7 @@ def test_create_link_error(self): fake_client, fake_ctx, vcloud_plugin_common.TASK_STATUS_SUCCESS, vcloud_plugin_common.TASK_STATUS_SUCCESS, + vcloud_plugin_common.TASK_STATUS_SUCCESS, vcloud_plugin_common.TASK_STATUS_ERROR ) with mock.patch( @@ -529,7 +715,7 @@ def test_create_link_error(self): mock.MagicMock(return_value=fake_client) ): with self.assertRaises(cfy_exc.NonRecoverableError): - server.create(ctx=fake_ctx) + server.create(ctx=fake_ctx, vca_client=None) self.check_create_call(fake_client, fake_ctx) def test_create_link_success(self): @@ -542,14 +728,19 @@ def test_create_link_success(self): fake_client, fake_ctx, vcloud_plugin_common.TASK_STATUS_SUCCESS, vcloud_plugin_common.TASK_STATUS_SUCCESS, + vcloud_plugin_common.TASK_STATUS_SUCCESS, vcloud_plugin_common.TASK_STATUS_SUCCESS ) with mock.patch( 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): - server.create(ctx=fake_ctx) - self.check_create_call(fake_client, fake_ctx) + fake_client._vapp.modify_vm_name = mock.MagicMock( + return_value=self.generate_task( + vcloud_plugin_common.TASK_STATUS_SUCCESS + ) + ) + server.create(ctx=fake_ctx, vca_client=None) def test_create_customization(self): """ @@ -561,6 +752,7 @@ def test_create_customization(self): fake_client, fake_ctx, vcloud_plugin_common.TASK_STATUS_SUCCESS, vcloud_plugin_common.TASK_STATUS_SUCCESS, + vcloud_plugin_common.TASK_STATUS_SUCCESS, vcloud_plugin_common.TASK_STATUS_SUCCESS ) with mock.patch( @@ -568,8 +760,7 @@ def test_create_customization(self): mock.MagicMock(return_value=fake_client) ): with self.assertRaises(cfy_exc.NonRecoverableError): - server.create(ctx=fake_ctx) - self.check_create_call(fake_client, fake_ctx) + server.configure(ctx=fake_ctx, vca_client=None) def test_create_customization_error(self): """ @@ -582,6 +773,7 @@ def test_create_customization_error(self): vcloud_plugin_common.TASK_STATUS_SUCCESS, vcloud_plugin_common.TASK_STATUS_SUCCESS, vcloud_plugin_common.TASK_STATUS_SUCCESS, + vcloud_plugin_common.TASK_STATUS_SUCCESS, vcloud_plugin_common.TASK_STATUS_ERROR ) with mock.patch( @@ -589,8 +781,7 @@ def test_create_customization_error(self): mock.MagicMock(return_value=fake_client) ): with self.assertRaises(cfy_exc.NonRecoverableError): - server.create(ctx=fake_ctx) - self.check_create_call(fake_client, fake_ctx) + server.configure(ctx=fake_ctx, vca_client=None) def test_create_customization_un_customized(self): """ @@ -603,18 +794,21 @@ def test_create_customization_un_customized(self): vcloud_plugin_common.TASK_STATUS_SUCCESS, vcloud_plugin_common.TASK_STATUS_SUCCESS, vcloud_plugin_common.TASK_STATUS_SUCCESS, + vcloud_plugin_common.TASK_STATUS_SUCCESS, vcloud_plugin_common.TASK_STATUS_SUCCESS ) fake_client._vapp.customize_on_next_poweron = mock.MagicMock( return_value=None ) + fake_client._vapp.force_customization = mock.MagicMock( + return_value=None + ) with mock.patch( 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): with self.assertRaises(cfy_exc.NonRecoverableError): - server.create(ctx=fake_ctx) - self.check_create_call(fake_client, fake_ctx) + server.configure(ctx=fake_ctx, vca_client=None) def test_create_customization_customized(self): """ @@ -630,15 +824,15 @@ def test_create_customization_customized(self): vcloud_plugin_common.TASK_STATUS_SUCCESS, vcloud_plugin_common.TASK_STATUS_SUCCESS, vcloud_plugin_common.TASK_STATUS_SUCCESS, + vcloud_plugin_common.TASK_STATUS_SUCCESS, vcloud_plugin_common.TASK_STATUS_SUCCESS ) with mock.patch( 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): - server.create(ctx=fake_ctx) - self.check_create_call(fake_client, fake_ctx) - self.check_get_vapp(fake_client, 'test') + server.configure(ctx=fake_ctx, vca_client=None) + if __name__ == '__main__': unittest.main() diff --git a/tests/unittests/test_mock_server_plugin_server_subroutes.py b/tests/unittests/test_mock_server_plugin_server_subroutes.py index 6318b37..5f0b7cf 100644 --- a/tests/unittests/test_mock_server_plugin_server_subroutes.py +++ b/tests/unittests/test_mock_server_plugin_server_subroutes.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved +# Copyright (c) 2014-2020 Cloudify Platform Ltd. All rights reserved # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -8,17 +8,18 @@ # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, -# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# * See the License for the specific language governing permissions and -# * limitations under the License. +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. import mock import unittest from cloudify import exceptions as cfy_exc from cloudify import mocks as cfy_mocks -from server_plugin import server +from vcloud_server_plugin import server from tests.unittests import test_mock_base +from cloudify.state import current_ctx class ServerPluginServerSubRoutesMockTestCase(test_mock_base.TestBase): @@ -60,71 +61,12 @@ def test_check_hardware_memory_is_string(self): def test_check_hardware(self): server._check_hardware(1, 512) - def test_get_management_network_name_in_properties(self): - ''' exist some managment network name in properties ''' - fake_ctx = cfy_mocks.MockCloudifyContext( - node_id='test', - node_name='test', - properties={ - 'management_network': '_management_network' - }) - with mock.patch('server_plugin.server.ctx', fake_ctx): - self.assertEqual( - '_management_network', - server._get_management_network_from_node() - ) - - def test_get_management_network_name_without_properties(self): - ''' without name in properties ''' - fake_ctx = cfy_mocks.MockCloudifyContext( - node_id='test', - node_name='test', - properties={}) - with mock.patch('server_plugin.server.ctx', fake_ctx): - with self.assertRaises(cfy_exc.NonRecoverableError): - server._get_management_network_from_node() - - def test_get_management_network_name_in_provider_context(self): - ''' with name in provider_context ''' - fake_ctx = cfy_mocks.MockCloudifyContext( - node_id='test', - node_name='test', - properties={}, - provider_context={ - 'resources': { - 'int_network': { - 'name': '_management_network' - } - } - }) - with mock.patch('server_plugin.server.ctx', fake_ctx): - self.assertEqual( - '_management_network', - server._get_management_network_from_node() - ) - - def test_get_management_network_without_name_in_context(self): - ''' without name in provider_context ''' - fake_ctx = cfy_mocks.MockCloudifyContext( - node_id='test', - node_name='test', - properties={}, - provider_context={ - 'resources': { - 'int_network': { - 'other_name': '_management_network' - } - } - }) - with mock.patch('server_plugin.server.ctx', fake_ctx): - with self.assertRaises(cfy_exc.NonRecoverableError): - self.assertEqual( - '_management_network', - server._get_management_network_from_node() - ) - def test_build_script(self): - self.assertEqual(None, server._build_script({})) + with mock.patch('vcloud_server_plugin.server._get_connected_keypairs', + mock.MagicMock( + return_value=[])): + self.assertEqual(None, server._build_script({}, [])) + custom = { 'pre_script': 'pre_script', 'post_script': 'post_script', @@ -132,23 +74,28 @@ def test_build_script(self): 'key': True }] } - self.assertNotEqual(None, server._build_script(custom)) + with mock.patch('vcloud_server_plugin.server._get_connected_keypairs', + mock.MagicMock( + return_value=[{'key': 'key'}])): + self.assertNotEqual(None, server._build_script(custom, [])) def test_build_public_keys_script(self): - self.assertEqual('', server._build_public_keys_script([])) + def script_fun(a, b, c, d, e): + return a.append("{}{}{}{}".format(b, c, d, e)) + self.assertEqual('', server._build_public_keys_script([], script_fun)) self.assertEqual('', server._build_public_keys_script([ {'key': False} - ])) + ], script_fun)) self.assertNotEqual('', server._build_public_keys_script([ {'key': True} - ])) + ], script_fun)) self.assertNotEqual('', server._build_public_keys_script([ { 'key': True, 'user': 'test', 'home': 'home' } - ])) + ], script_fun)) def test_creation_validation_empty_settings(self): fake_ctx = cfy_mocks.MockCloudifyContext( @@ -159,13 +106,14 @@ def test_creation_validation_empty_settings(self): }, provider_context={} ) + current_ctx.set(fake_ctx) with mock.patch( 'vcloud_plugin_common.VcloudAirClient', self.generate_vca() ): with self.assertRaises(cfy_exc.NonRecoverableError): - server.creation_validation(ctx=fake_ctx) + server.creation_validation(ctx=fake_ctx, vca_client=None) def test_creation_validation_external_resource(self): """ @@ -181,13 +129,14 @@ def test_creation_validation_external_resource(self): }, provider_context={} ) + current_ctx.set(fake_ctx) with mock.patch( 'vcloud_plugin_common.VcloudAirClient', self.generate_vca() ): with self.assertRaises(cfy_exc.NonRecoverableError): - server.creation_validation(ctx=fake_ctx) + server.creation_validation(ctx=fake_ctx, vca_client=None) # with resource_id fake_ctx = cfy_mocks.MockCloudifyContext( node_id='test', @@ -198,12 +147,13 @@ def test_creation_validation_external_resource(self): }, provider_context={} ) + current_ctx.set(fake_ctx) with mock.patch( 'vcloud_plugin_common.VcloudAirClient', self.generate_vca() ): - server.creation_validation(ctx=fake_ctx) + server.creation_validation(ctx=fake_ctx, vca_client=None) def test_creation_validation_settings_wrong_catalog(self): fake_ctx = cfy_mocks.MockCloudifyContext( @@ -217,13 +167,14 @@ def test_creation_validation_settings_wrong_catalog(self): }, provider_context={} ) + current_ctx.set(fake_ctx) with mock.patch( 'vcloud_plugin_common.VcloudAirClient.get', self.generate_vca() ): with self.assertRaises(cfy_exc.NonRecoverableError): - server.creation_validation(ctx=fake_ctx) + server.creation_validation(ctx=fake_ctx, vca_client=None) def test_creation_validation_settings_wrong_template(self): fake_ctx = cfy_mocks.MockCloudifyContext( @@ -237,13 +188,14 @@ def test_creation_validation_settings_wrong_template(self): }, provider_context={} ) + current_ctx.set(fake_ctx) with mock.patch( 'vcloud_plugin_common.VcloudAirClient.get', self.generate_vca() ): with self.assertRaises(cfy_exc.NonRecoverableError): - server.creation_validation(ctx=fake_ctx) + server.creation_validation(ctx=fake_ctx, vca_client=None) def test_creation_validation_settings(self): fake_ctx = cfy_mocks.MockCloudifyContext( @@ -257,12 +209,13 @@ def test_creation_validation_settings(self): }, provider_context={} ) + current_ctx.set(fake_ctx) with mock.patch( 'vcloud_plugin_common.VcloudAirClient.get', self.generate_vca() ): - server.creation_validation(ctx=fake_ctx) + server.creation_validation(ctx=fake_ctx, vca_client=None) def test_isDhcpAvailable(self): client = self.generate_client() @@ -280,24 +233,26 @@ def test_isDhcpAvailable(self): }, provider_context={} ) + current_ctx.set(fake_ctx) - with mock.patch('server_plugin.server.ctx', fake_ctx): - with mock.patch('vcloud_plugin_common.ctx', fake_ctx): - self.assertEqual( - True, server._isDhcpAvailable(client, 'bridged') - ) - self.assertEqual( - False, server._isDhcpAvailable(client, 'local') - ) - self.assertEqual( - True, server._isDhcpAvailable(client, 'vdc_name') - ) + with mock.patch('vcloud_plugin_common.ctx', fake_ctx): + self.assertEqual( + True, server._isDhcpAvailable(client, 'bridged') + ) + self.assertEqual( + False, server._isDhcpAvailable(client, 'local') + ) + self.assertEqual( + True, server._isDhcpAvailable(client, 'vdc_name') + ) def test_get_connected(self): - fake_ctx = self.generate_node_context(relation_node_properties={ - "not_test": "not_test" - }) + fake_ctx = self.generate_node_context_with_current_ctx( + relation_node_properties={ + "not_test": "not_test" + } + ) self.assertEqual( server._get_connected(fake_ctx.instance, "test"), [] @@ -316,81 +271,134 @@ def test_get_connected(self): def test_create_connections_list(self): # one connection from port, one from network and # one managment_network - fake_ctx = self.generate_node_context(relation_node_properties={ - "not_test": "not_test", - 'port': { - 'network': 'private_network', - 'ip_address': "1.1.1.1", - 'mac_address': "hex", - 'ip_allocation_mode': 'pool', - 'primary_interface': True - }, - 'network': { - 'name': 'some_network' + fake_ctx = self.generate_node_context_with_current_ctx( + relation_node_properties={ + "not_test": "not_test", + 'port': { + 'network': 'private_network', + 'ip_address': "1.1.1.1", + 'mac_address': "hex", + 'ip_allocation_mode': 'pool', + 'primary_interface': True + }, + 'network': { + 'name': 'some_network' + } } - }) + ) fake_client = self.generate_client() - with mock.patch('server_plugin.server.ctx', fake_ctx): - with mock.patch('vcloud_plugin_common.ctx', fake_ctx): - connection = server._create_connections_list(fake_client) - self.assertEqual( - [ - { - 'ip_address': '1.1.1.1', - 'ip_allocation_mode': 'POOL', - 'mac_address': 'hex', - 'network': 'private_network', - 'primary_interface': True - }, { - 'ip_address': None, - 'ip_allocation_mode': 'POOL', - 'mac_address': None, - 'network': 'some_network', - 'primary_interface': False - }, { - 'ip_address': None, - 'ip_allocation_mode': 'POOL', - 'mac_address': None, - 'network': '_management_network', - 'primary_interface': False - } - ], connection - ) + with mock.patch('vcloud_plugin_common.ctx', fake_ctx): + connection = server._create_connections_list( + ctx=fake_ctx, vca_client=fake_client) + self.assertEqual( + [ + { + 'network': 'private_network', + 'mac_address': 'hex', + 'ip_allocation_mode': 'POOL', + 'primary_interface': True, + 'ip_address': '1.1.1.1', + 'nic_order': 0 + }, { + 'network': 'some_network', + 'mac_address': None, + 'ip_allocation_mode': 'POOL', + 'primary_interface': False, + 'ip_address': None, + 'nic_order': 0 + }, { + 'network': '_management_network', + 'mac_address': None, + 'ip_allocation_mode': 'POOL', + 'primary_interface': False, + 'ip_address': None, + 'nic_order': 0 + } + ], connection + ) + # get network name from first avaible but not primary + fake_ctx = self.generate_node_context_with_current_ctx( + relation_node_properties={ + "not_test": "not_test", + 'port': { + 'network': 'private_network', + 'ip_address': "1.1.1.1", + 'mac_address': "hex", + 'ip_allocation_mode': 'pool', + 'primary_interface': False, + 'nic_order': 0 + } + } + ) + fake_client = self.generate_client() + fake_ctx.node.properties['management_network'] = None + with mock.patch('vcloud_plugin_common.ctx', fake_ctx): + connection = server._create_connections_list( + ctx=fake_ctx, vca_client=fake_client) + self.assertEqual( + [ + { + 'ip_address': '1.1.1.1', + 'ip_allocation_mode': 'POOL', + 'mac_address': 'hex', + 'network': 'private_network', + 'primary_interface': True, + 'nic_order': 0 + } + ], connection + ) + # no connections + fake_ctx = self.generate_node_context_with_current_ctx( + relation_node_properties={ + "not_test": "not_test" + } + ) + fake_client = self.generate_client() + fake_ctx.node.properties['management_network'] = None + with mock.patch('vcloud_plugin_common.ctx', fake_ctx): + with self.assertRaises(cfy_exc.NonRecoverableError): + server._create_connections_list(ctx=fake_ctx, + vca_client=fake_client) # one network same as managment + port - fake_ctx = self.generate_node_context(relation_node_properties={ - "not_test": "not_test", - 'port': { - 'network': '_management_network', - 'ip_address': "1.1.1.1", - 'mac_address': "hex", - 'ip_allocation_mode': 'pool', - 'primary_interface': True - }, - 'network': { - 'name': 'some_network' + fake_ctx = self.generate_node_context_with_current_ctx( + relation_node_properties={ + "not_test": "not_test", + 'port': { + 'network': '_management_network', + 'ip_address': "1.1.1.1", + 'mac_address': "hex", + 'ip_allocation_mode': 'pool', + 'primary_interface': True, + 'nic_order': 0 + }, + 'network': { + 'name': 'some_network' + } } - }) - with mock.patch('server_plugin.server.ctx', fake_ctx): - with mock.patch('vcloud_plugin_common.ctx', fake_ctx): - connection = server._create_connections_list(fake_client) - self.assertEqual( - [ - { - 'ip_address': '1.1.1.1', - 'ip_allocation_mode': 'POOL', - 'mac_address': 'hex', - 'network': '_management_network', - 'primary_interface': True - }, - { - 'ip_address': None, - 'ip_allocation_mode': 'POOL', - 'mac_address': None, - 'network': 'some_network', - 'primary_interface': False - } - ], connection - ) + ) + with mock.patch('vcloud_plugin_common.ctx', fake_ctx): + connection = server._create_connections_list( + ctx=fake_ctx, vca_client=fake_client) + self.assertEqual( + [ + { + 'ip_address': '1.1.1.1', + 'ip_allocation_mode': 'POOL', + 'mac_address': 'hex', + 'network': '_management_network', + 'primary_interface': True, + 'nic_order': 0 + }, + { + 'ip_address': None, + 'ip_allocation_mode': 'POOL', + 'mac_address': None, + 'network': 'some_network', + 'primary_interface': False, + 'nic_order': 0 + } + ], connection + ) # check dhcp, with no dhcp server fake_ctx = self.generate_node_context(relation_node_properties={ "not_test": "not_test", @@ -399,31 +407,35 @@ def test_create_connections_list(self): 'ip_address': "1.1.1.1", 'mac_address': "hex", 'ip_allocation_mode': 'dhcp', - 'primary_interface': True + 'primary_interface': True, + 'nic_order': 0 }, 'network': { 'name': 'some_network' } }) - with mock.patch('server_plugin.server.ctx', fake_ctx): - with mock.patch('vcloud_plugin_common.ctx', fake_ctx): - with self.assertRaises(cfy_exc.NonRecoverableError): - server._create_connections_list(fake_client) + # we support case when with dhcpd on vm inside network + # instead use gateway service, + # look to cc676430a1e06e9ac2fd8d0a56b9a414d3232939 + with mock.patch('vcloud_plugin_common.ctx', fake_ctx): + server._create_connections_list(ctx=fake_ctx, + vca_client=fake_client) # only managment node fake_ctx.instance._relationships = [] - with mock.patch('server_plugin.server.ctx', fake_ctx): - with mock.patch('vcloud_plugin_common.ctx', fake_ctx): - connection = server._create_connections_list(fake_client) - self.assertEqual( - [{ - 'ip_address': None, - 'ip_allocation_mode': 'POOL', - 'mac_address': None, - 'network': '_management_network', - 'primary_interface': True - }], - connection - ) + with mock.patch('vcloud_plugin_common.ctx', fake_ctx): + connection = server._create_connections_list( + ctx=fake_ctx, vca_client=fake_client) + self.assertEqual( + [{ + 'ip_address': None, + 'ip_allocation_mode': 'POOL', + 'mac_address': None, + 'network': '_management_network', + 'primary_interface': True, + 'nic_order': 0 + }], + connection + ) # no networks fake_ctx.instance._relationships = [] @@ -431,10 +443,10 @@ def _generate_fake_client_network(vdc_name, network_name): return None fake_client.get_network = _generate_fake_client_network - with mock.patch('server_plugin.server.ctx', fake_ctx): - with mock.patch('vcloud_plugin_common.ctx', fake_ctx): - with self.assertRaises(cfy_exc.NonRecoverableError): - server._create_connections_list(fake_client) + with mock.patch('vcloud_plugin_common.ctx', fake_ctx): + with self.assertRaises(cfy_exc.NonRecoverableError): + server._create_connections_list(ctx=fake_ctx, + vca_client=fake_client) def test_get_vm_network_connections(self): # one connection from port, one from network and @@ -495,37 +507,110 @@ def test_get_vm_network_connection(self): self.assertEqual(None, connection) def test_get_state(self): - fake_ctx = self.generate_node_context() - with mock.patch('server_plugin.server.ctx', fake_ctx): - with mock.patch('vcloud_plugin_common.ctx', fake_ctx): - # connected network_name - fake_client = self.generate_client([{ - 'is_connected': True, - 'network_name': 'network_name', - 'ip': '1.1.1.1' - }]) - self.assertFalse(server._get_state(fake_client)) - # not connected network_name - fake_client = self.generate_client([{ - 'is_connected': False, - 'network_name': 'network_name', - 'ip': '1.1.1.1' - }]) - self.assertTrue(server._get_state(fake_client)) - # not ip in connected network_name - fake_client = self.generate_client([{ - 'is_connected': True, - 'network_name': 'network_name', - 'ip': None - }]) - self.assertFalse(server._get_state(fake_client)) - # with managment_network - fake_client = self.generate_client([{ - 'is_connected': True, - 'network_name': '_management_network', - 'ip': '1.1.1.1' - }]) - self.assertTrue(server._get_state(fake_client)) + fake_ctx = self.generate_node_context_with_current_ctx() + with mock.patch('vcloud_plugin_common.ctx', fake_ctx): + # connected network_name + fake_client = self.generate_client([{ + 'is_connected': True, + 'is_primary': False, + 'network_name': 'network_name', + 'ip': '1.1.1.1' + }]) + self.assertFalse(server._get_state(ctx=fake_ctx, + vca_client=fake_client)) + # not connected network_name + fake_client = self.generate_client([{ + 'is_connected': False, + 'network_name': 'network_name', + 'ip': '1.1.1.1' + }]) + self.assertTrue(server._get_state(ctx=fake_ctx, + vca_client=fake_client)) + # not ip in connected network_name + fake_client = self.generate_client([{ + 'is_connected': True, + 'is_primary': False, + 'network_name': 'network_name', + 'ip': None + }]) + self.assertFalse(server._get_state(ctx=fake_ctx, + vca_client=fake_client)) + # with managment_network + fake_client = self.generate_client([{ + 'is_connected': True, + 'is_primary': True, + 'network_name': '_management_network', + 'ip': '1.1.1.1' + }]) + self.assertTrue(server._get_state(ctx=fake_ctx, + vca_client=fake_client)) + + def test_add_key_script(self): + commands = [] + server._add_key_script(commands, "~A~", "~B~", "~C~", "~D~") + self.assertTrue(commands) + # check create directory .ssh + self.assertTrue("~A~" in commands[0]) + self.assertTrue("~B~" in commands[0]) + self.assertTrue("~C~" in commands[0]) + # inject value to key file + self.assertTrue("~C~" in commands[0]) + self.assertTrue("~D~" in commands[1]) + + def test_get_connected_keypairs(self): + # empty list of relationships + fake_ctx = self.generate_node_context_with_current_ctx() + fake_ctx.instance._relationships = None + self.assertEqual([], server._get_connected_keypairs(ctx=fake_ctx)) + # exist some content + relationship = self.generate_relation_context() + runtime_properties = {'public_key': "a"} + relationship.target.instance.runtime_properties = runtime_properties + fake_ctx.instance._relationships = [relationship] + self.assertEqual( + server._get_connected_keypairs(ctx=fake_ctx), ["a"] + ) + + def test_is_primary_connection_has_ip(self): + # no network info at all + vapp = mock.MagicMock() + vapp.get_vms_network_info = mock.MagicMock(return_value=False) + self.assertTrue(server._is_primary_connection_has_ip(vapp)) + # empty list of connections + vapp.get_vms_network_info = mock.MagicMock(return_value=[None]) + self.assertTrue(server._is_primary_connection_has_ip(vapp)) + # exist connection, but without ip + vapp.get_vms_network_info = mock.MagicMock(return_value=[[ + {'is_connected': False} + ]]) + self.assertFalse(server._is_primary_connection_has_ip(vapp)) + # everything connected + vapp.get_vms_network_info = mock.MagicMock(return_value=[[{ + 'is_connected': True, + 'is_primary': True, + 'ip': '127.0.0.1' + }]]) + self.assertTrue(server._is_primary_connection_has_ip(vapp)) + # connected but to different port + vapp.get_vms_network_info = mock.MagicMock(return_value=[[{ + 'is_connected': True, + 'is_primary': False, + 'ip': '127.0.0.1' + }, { + 'is_connected': True, + 'is_primary': True, + 'ip': None + }]]) + self.assertFalse(server._is_primary_connection_has_ip(vapp)) + + def test_remove_key_script(self): + commands = [] + server._remove_key_script( + commands, "super!", ".ssh!", "somekey", "!**! !@^" + ) + self.assertEqual( + commands, [' sed -i /!@^/d somekey'] + ) if __name__ == '__main__': diff --git a/tests/unittests/test_mock_server_plugin_vdc.py b/tests/unittests/test_mock_server_plugin_vdc.py new file mode 100644 index 0000000..ea70a63 --- /dev/null +++ b/tests/unittests/test_mock_server_plugin_vdc.py @@ -0,0 +1,206 @@ +# Copyright (c) 2015-2020 Cloudify Platform Ltd. All rights reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import mock + +from cloudify import exceptions as cfy_exc +from vcloud_server_plugin import vdc +import vcloud_plugin_common +from tests.unittests import test_mock_base + + +class ServerPluginVdcMockTestCase(test_mock_base.TestBase): + + def test_creation_validation(self): + """check validation for vdc operations""" + fake_client = self.generate_client() + with mock.patch( + 'vcloud_plugin_common.VcloudAirClient.get', + mock.MagicMock(return_value=fake_client) + ): + fake_ctx = self.generate_node_context_with_current_ctx( + properties={} + ) + # no vdc name + with self.assertRaises(cfy_exc.NonRecoverableError): + vdc.creation_validation(ctx=fake_ctx, vca_client=None) + # name exist but someone already created this vdc + fake_ctx = self.generate_node_context_with_current_ctx( + properties={ + 'name': 'not_existed' + } + ) + fake_client.get_vdc = mock.MagicMock( + return_value=mock.MagicMock() + ) + with self.assertRaises(cfy_exc.NonRecoverableError): + vdc.creation_validation(ctx=fake_ctx, vca_client=None) + fake_client.get_vdc.assert_called_with('not_existed') + # everthing fine + fake_client.get_vdc = mock.MagicMock(return_value=None) + vdc.creation_validation(ctx=fake_ctx, vca_client=None) + # external but without name + fake_ctx = self.generate_node_context_with_current_ctx( + properties={ + 'use_external_resource': True + } + ) + with self.assertRaises(cfy_exc.NonRecoverableError): + vdc.creation_validation(ctx=fake_ctx, vca_client=None) + # use unexisted vdc + fake_ctx = self.generate_node_context_with_current_ctx( + properties={ + 'use_external_resource': True, + 'resource_id': 'not_existed' + } + ) + fake_client.get_vdc = mock.MagicMock(return_value=None) + with self.assertRaises(cfy_exc.NonRecoverableError): + vdc.creation_validation(ctx=fake_ctx, vca_client=None) + fake_client.get_vdc.assert_called_with('not_existed') + # exist everything + fake_client.get_vdc = mock.MagicMock( + return_value=mock.MagicMock() + ) + vdc.creation_validation(ctx=fake_ctx, vca_client=None) + fake_client.get_vdc.assert_called_with('not_existed') + + def test_create(self): + """check vdc creation operation""" + fake_client = self.generate_client() + with mock.patch( + 'vcloud_plugin_common.VcloudAirClient.get', + mock.MagicMock(return_value=fake_client) + ): + # tried to create new vdc on subscription + fake_ctx = self.generate_node_context_with_current_ctx( + properties={ + 'vcloud_config': { + 'service_type': + vcloud_plugin_common.SUBSCRIPTION_SERVICE_TYPE + } + } + ) + with self.assertRaises(cfy_exc.NonRecoverableError): + vdc.create(ctx=fake_ctx, vca_client=None) + # use ondemand + # use external resource without vdc + fake_ctx = self.generate_node_context_with_current_ctx( + properties={ + 'vcloud_config': { + 'service_type': + vcloud_plugin_common.ONDEMAND_SERVICE_TYPE + }, + 'use_external_resource': True, + 'resource_id': 'not_existed' + } + ) + fake_client.get_vdc = mock.MagicMock(return_value=None) + with self.assertRaises(cfy_exc.NonRecoverableError): + vdc.create(ctx=fake_ctx, vca_client=None) + fake_client.get_vdc.assert_called_with('not_existed') + # successful for create on external resource + fake_client.get_vdc = mock.MagicMock( + return_value=mock.MagicMock() + ) + vdc.create(ctx=fake_ctx, vca_client=None) + # no name for vdc + fake_ctx = self.generate_node_context_with_current_ctx( + properties={ + 'vcloud_config': { + 'service_type': + vcloud_plugin_common.ONDEMAND_SERVICE_TYPE + }, + 'use_external_resource': False, + } + ) + with self.assertRaises(cfy_exc.NonRecoverableError): + vdc.create(ctx=fake_ctx, vca_client=None) + # create new vdc for deployment + fake_ctx = self.generate_node_context_with_current_ctx( + properties={ + 'vcloud_config': { + 'service_type': + vcloud_plugin_common.ONDEMAND_SERVICE_TYPE + }, + 'use_external_resource': False, + 'name': "something" + } + ) + # no task returned + fake_client.create_vdc = mock.MagicMock( + return_value=None + ) + with self.assertRaises(cfy_exc.NonRecoverableError): + vdc.create(ctx=fake_ctx, vca_client=None) + # everything fine + fake_client.create_vdc = mock.MagicMock( + return_value=self.generate_task( + vcloud_plugin_common.TASK_STATUS_SUCCESS + ) + ) + vdc.create(ctx=fake_ctx, vca_client=None) + + def test_delete(self): + """check vdc deletion operation""" + fake_client = self.generate_client() + with mock.patch( + 'vcloud_plugin_common.VcloudAirClient.get', + mock.MagicMock(return_value=fake_client) + ): + # external resorce + fake_ctx = self.generate_node_context_with_current_ctx( + properties={ + 'vcloud_config': { + 'service_type': + vcloud_plugin_common.ONDEMAND_SERVICE_TYPE + }, + 'use_external_resource': True, + 'resource_id': 'not_existed' + } + ) + vdc.delete(ctx=fake_ctx, vca_client=None) + # return fail from delete vdc + fake_client.delete_vdc = mock.MagicMock( + return_value=(False, None) + ) + fake_ctx = self.generate_node_context_with_current_ctx( + properties={ + 'vcloud_config': { + 'service_type': + vcloud_plugin_common.ONDEMAND_SERVICE_TYPE + }, + 'use_external_resource': False, + 'name': "something" + }, + runtime_properties={ + vdc.VDC_NAME: "something" + } + ) + with self.assertRaises(cfy_exc.NonRecoverableError): + vdc.delete(ctx=fake_ctx, vca_client=None) + fake_client.delete_vdc.assert_called_with("something") + self.assertTrue( + vdc.VDC_NAME in fake_ctx.instance.runtime_properties + ) + # succesful delete + fake_client.delete_vdc = mock.MagicMock( + return_value=(True, self.generate_task( + vcloud_plugin_common.TASK_STATUS_SUCCESS + )) + ) + vdc.delete(ctx=fake_ctx, vca_client=None) + self.assertFalse( + vdc.VDC_NAME in fake_ctx.instance.runtime_properties + ) diff --git a/tests/unittests/test_mock_server_plugin_volume.py b/tests/unittests/test_mock_storage_plugin_volume.py similarity index 71% rename from tests/unittests/test_mock_server_plugin_volume.py rename to tests/unittests/test_mock_storage_plugin_volume.py index 8058af7..6313f9a 100644 --- a/tests/unittests/test_mock_server_plugin_volume.py +++ b/tests/unittests/test_mock_storage_plugin_volume.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved +# Copyright (c) 2014-2020 Cloudify Platform Ltd. All rights reserved # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -8,22 +8,23 @@ # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, -# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# * See the License for the specific language governing permissions and -# * limitations under the License. +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. import mock import unittest from cloudify import exceptions as cfy_exc from cloudify import mocks as cfy_mocks -from server_plugin import volume +from vcloud_storage_plugin import volume import vcloud_plugin_common from tests.unittests import test_mock_base -import network_plugin +import vcloud_network_plugin +from cloudify.state import current_ctx -class ServerPluginServerMockTestCase(test_mock_base.TestBase): +class StoragePluginVolumeMockTestCase(test_mock_base.TestBase): # vapp name used for tests VAPPNAME = "some_other" @@ -40,13 +41,14 @@ def test_creation_validation_external_resource(self): } } ) - # use external without resorse_id + current_ctx.set(fake_ctx) + # use external without resource_id with mock.patch( 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): with self.assertRaises(cfy_exc.NonRecoverableError): - volume.creation_validation(ctx=fake_ctx) + volume.creation_validation(ctx=fake_ctx, vca_client=None) fake_client.get_disks.assert_called_with('vdc_name') # with resource id, but without disks(no disks for this client) fake_ctx = cfy_mocks.MockCloudifyContext( @@ -60,12 +62,13 @@ def test_creation_validation_external_resource(self): } } ) + current_ctx.set(fake_ctx) with mock.patch( 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): with self.assertRaises(cfy_exc.NonRecoverableError): - volume.creation_validation(ctx=fake_ctx) + volume.creation_validation(ctx=fake_ctx, vca_client=None) # good case for external resource fake_client.get_disks = mock.MagicMock(return_value=[ [ @@ -77,7 +80,7 @@ def test_creation_validation_external_resource(self): 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): - volume.creation_validation(ctx=fake_ctx) + volume.creation_validation(ctx=fake_ctx, vca_client=None) def test_creation_validation_internal(self): fake_client = self.generate_client() @@ -92,14 +95,16 @@ def test_creation_validation_internal(self): } } ) + current_ctx.set(fake_ctx) + with mock.patch( 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): with self.assertRaises(cfy_exc.NonRecoverableError): - volume.creation_validation(ctx=fake_ctx) + volume.creation_validation(ctx=fake_ctx, vca_client=None) fake_client.get_disks.assert_called_with('vdc_name') - # internal resourse wit volume and name, + # internal resource wit volume and name, # but already exist such volume fake_ctx = cfy_mocks.MockCloudifyContext( node_id='test', @@ -114,6 +119,7 @@ def test_creation_validation_internal(self): } } ) + current_ctx.set(fake_ctx) fake_client.get_disks = mock.MagicMock(return_value=[ [ self.generate_fake_client_disk('some'), @@ -125,7 +131,7 @@ def test_creation_validation_internal(self): mock.MagicMock(return_value=fake_client) ): with self.assertRaises(cfy_exc.NonRecoverableError): - volume.creation_validation(ctx=fake_ctx) + volume.creation_validation(ctx=fake_ctx, vca_client=None) # correct name but without size fake_ctx = cfy_mocks.MockCloudifyContext( node_id='test', @@ -140,12 +146,13 @@ def test_creation_validation_internal(self): } } ) + current_ctx.set(fake_ctx) with mock.patch( 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): with self.assertRaises(cfy_exc.NonRecoverableError): - volume.creation_validation(ctx=fake_ctx) + volume.creation_validation(ctx=fake_ctx, vca_client=None) # good case fake_ctx = cfy_mocks.MockCloudifyContext( node_id='test', @@ -161,11 +168,12 @@ def test_creation_validation_internal(self): } } ) + current_ctx.set(fake_ctx) with mock.patch( 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): - volume.creation_validation(ctx=fake_ctx) + volume.creation_validation(ctx=fake_ctx, vca_client=None) def test_delete_volume(self): fake_client = self.generate_client() @@ -180,11 +188,12 @@ def test_delete_volume(self): } } ) + current_ctx.set(fake_ctx) with mock.patch( 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): - volume.delete_volume(ctx=fake_ctx) + volume.delete_volume(ctx=fake_ctx, vca_client=None) # cant't add disk fake_ctx = cfy_mocks.MockCloudifyContext( node_id='test', @@ -200,12 +209,13 @@ def test_delete_volume(self): } } ) + current_ctx.set(fake_ctx) with mock.patch( 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): with self.assertRaises(cfy_exc.NonRecoverableError): - volume.delete_volume(ctx=fake_ctx) + volume.delete_volume(ctx=fake_ctx, vca_client=None) fake_client.delete_disk.assert_called_with( 'vdc_name', 'some-other' ) @@ -221,7 +231,7 @@ def test_delete_volume(self): 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): - volume.delete_volume(ctx=fake_ctx) + volume.delete_volume(ctx=fake_ctx, vca_client=None) def test_create_volume(self): fake_client = self.generate_client() @@ -236,11 +246,12 @@ def test_create_volume(self): } } ) + current_ctx.set(fake_ctx) with mock.patch( 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): - volume.create_volume(ctx=fake_ctx) + volume.create_volume(ctx=fake_ctx, vca_client=None) # fail on create volume fake_ctx = cfy_mocks.MockCloudifyContext( node_id='test', @@ -256,12 +267,13 @@ def test_create_volume(self): } } ) + current_ctx.set(fake_ctx) with mock.patch( 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): with self.assertRaises(cfy_exc.NonRecoverableError): - volume.create_volume(ctx=fake_ctx) + volume.create_volume(ctx=fake_ctx, vca_client=None) fake_client.add_disk.assert_called_with( 'vdc_name', 'some-other', 11534336 ) @@ -279,7 +291,7 @@ def test_create_volume(self): 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): - volume.create_volume(ctx=fake_ctx) + volume.create_volume(ctx=fake_ctx, vca_client=None) def test_volume_operation(self): fake_ctx, fake_client = self._gen_volume_context_and_client() @@ -288,10 +300,7 @@ def _run_volume_operation(fake_ctx, fake_client, operation): with mock.patch( 'vcloud_plugin_common.ctx', fake_ctx ): - with mock.patch( - 'server_plugin.volume.ctx', fake_ctx - ): - volume._volume_operation(fake_client, operation) + volume._volume_operation(fake_ctx, fake_client, operation) # use external resource, no disks _run_volume_operation(fake_ctx, fake_client, 'ATTACH') @@ -342,10 +351,23 @@ def _run_volume_operation(fake_ctx, fake_client, operation): fake_client._vapp.detach_disk_from_vm.assert_called_with( 'some_other', disk_ref ) + # disk exist, use external resource + fake_ctx._target.node.properties = { + 'volume': { + 'name': 'some' + }, + 'use_external_resource': False + } + fake_ctx._source.node.properties.update( + {'use_external_resource': True}) + _run_volume_operation(fake_ctx, fake_client, 'DETACH') + fake_client._vapp.detach_disk_from_vm.assert_called_with( + 'some_other', disk_ref + ) def _gen_volume_context_and_client(self): fake_client = self.generate_client() - fake_ctx = self.generate_relation_context() + fake_ctx = self.generate_relation_context_with_current_ctx() fake_ctx._target.node.properties = { 'use_external_resource': True } @@ -359,7 +381,8 @@ def _gen_volume_context_and_client(self): 'resource_id': 'some' } fake_ctx._target.instance.runtime_properties = { - network_plugin.VCLOUD_VAPP_NAME: self.VAPPNAME + vcloud_network_plugin.VCLOUD_VAPP_NAME: self.VAPPNAME, + 'ip': "1.2.3.4" } return fake_ctx, fake_client @@ -368,11 +391,13 @@ def test_attach_volume(self): use external resource, try to attach but no disks """ fake_ctx, fake_client = self._gen_volume_context_and_client() - with mock.patch( - 'vcloud_plugin_common.VcloudAirClient.get', - mock.MagicMock(return_value=fake_client) - ): - volume.attach_volume(ctx=fake_ctx) + with mock.patch('vcloud_plugin_common.VcloudAirClient.get', + mock.MagicMock(return_value=fake_client)): + with mock.patch( + 'vcloud_storage_plugin.volume._wait_for_boot', + mock.MagicMock() + ): + volume.attach_volume(ctx=fake_ctx, vca_client=None) def test_detach_volume(self): """ @@ -383,7 +408,60 @@ def test_detach_volume(self): 'vcloud_plugin_common.VcloudAirClient.get', mock.MagicMock(return_value=fake_client) ): - volume.detach_volume(ctx=fake_ctx) + volume.detach_volume(ctx=fake_ctx, vca_client=None) + + def run_wait_boot(self, fabric_settings, fabric_run, ctx, sleep_call=True): + sleep_function = mock.MagicMock() + with mock.patch( + 'fabric.api.settings', fabric_settings + ): + with mock.patch( + 'fabric.api.run', fabric_run + ): + with mock.patch( + 'time.sleep', sleep_function + ): + volume._wait_for_boot(ctx) + # check for sleep calls + if sleep_call: + sleep_function.assert_called_with(5) + + def test_wait_for_boot(self): + """ + check that machine is alive + """ + fake_ctx = self.generate_relation_context_with_current_ctx() + fake_ctx._target.instance.runtime_properties['ip'] = 'unknowhost' + fabric_context = mock.MagicMock(return_value=None) + fabric_context.__exit__ = mock.MagicMock(return_value=None) + fabric_context.__enter__ = mock.MagicMock(return_value=None) + fabric_settings = mock.MagicMock(return_value=fabric_context) + fabric_run = mock.MagicMock(return_value=None) + with mock.patch( + 'vcloud_plugin_common.ctx', fake_ctx + ): + # can't connect and run + with self.assertRaises(cfy_exc.NonRecoverableError): + self.run_wait_boot(fabric_settings, fabric_run, + ctx=fake_ctx) + # command successfully finished + + def _sysexit(_): + raise SystemExit + + fabric_run = mock.MagicMock(side_effect=_sysexit) + self.run_wait_boot(fabric_settings, fabric_run, + sleep_call=False, ctx=fake_ctx) + # raised some exception during run + + def _raiseex(_): + raise Exception + + fabric_run = mock.MagicMock(side_effect=_raiseex) + with self.assertRaises(cfy_exc.NonRecoverableError): + self.run_wait_boot(fabric_settings, fabric_run, + ctx=fake_ctx) + if __name__ == '__main__': unittest.main() diff --git a/tests/unittests/test_mock_vcloud_plugin_common.py b/tests/unittests/test_mock_vcloud_plugin_common.py index 6a7a09b..d7a21ef 100644 --- a/tests/unittests/test_mock_vcloud_plugin_common.py +++ b/tests/unittests/test_mock_vcloud_plugin_common.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved +# Copyright (c) 2014-2020 Cloudify Platform Ltd. All rights reserved # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -8,9 +8,9 @@ # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, -# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# * See the License for the specific language governing permissions and -# * limitations under the License. +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. import mock import unittest @@ -178,17 +178,20 @@ def test_is_ondemand(self): def test_wait_for_task(self): fake_client = self.generate_client() + fake_ctx = self.generate_node_context() # error in task fake_task = self.generate_task( vcloud_plugin_common.TASK_STATUS_ERROR ) - with self.assertRaises(cfy_exc.NonRecoverableError): - vcloud_plugin_common.wait_for_task(fake_client, fake_task) + with mock.patch('vcloud_plugin_common.ctx', fake_ctx): + with self.assertRaises(cfy_exc.NonRecoverableError): + vcloud_plugin_common.wait_for_task(fake_client, fake_task) # success in task fake_task = self.generate_task( vcloud_plugin_common.TASK_STATUS_SUCCESS ) - vcloud_plugin_common.wait_for_task(fake_client, fake_task) + with mock.patch('vcloud_plugin_common.ctx', fake_ctx): + vcloud_plugin_common.wait_for_task(fake_client, fake_task) # success after wait fake_task = self.generate_task( None @@ -210,9 +213,10 @@ def test_wait_for_task(self): 'time.sleep', sleep ): - vcloud_plugin_common.wait_for_task( - fake_client, fake_task - ) + with mock.patch('vcloud_plugin_common.ctx', + fake_ctx): + vcloud_plugin_common.wait_for_task( + fake_client, fake_task) def test_with_vca_client(self): # context.NODE_INSTANCE @@ -236,7 +240,7 @@ def _some_function(vca_client, **kwargs): return vca_client self.assertEqual( - _some_function(ctx=fake_ctx), + _some_function(ctx=fake_ctx, vca_client=None), fake_client ) # context.DEPLOYMENT @@ -281,10 +285,8 @@ def test_config(self): '__builtin__.open', fake_file ): config = vcloud_plugin_common.Config() - self.assertEqual( - config.get(), - {} - ) + self.assertFalse(config.get()) + if __name__ == '__main__': unittest.main() diff --git a/tests/unittests/test_mock_vcloud_plugin_common_vca_client.py b/tests/unittests/test_mock_vcloud_plugin_common_vca_client.py index 85c8cd2..8132c26 100644 --- a/tests/unittests/test_mock_vcloud_plugin_common_vca_client.py +++ b/tests/unittests/test_mock_vcloud_plugin_common_vca_client.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved +# Copyright (c) 2014-2020 Cloudify Platform Ltd. All rights reserved # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -8,9 +8,9 @@ # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, -# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# * See the License for the specific language governing permissions and -# * limitations under the License. +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. import mock import unittest @@ -53,10 +53,16 @@ def _run( with mock.patch( 'vcloud_plugin_common.ctx', fake_ctx ): - return client._subscription_login( - url, username, password, token, service, - org_name - ) + with mock.patch( + 'pyvcloud.vcloudair.VCS', mock.MagicMock() + ): + with mock.patch( + 'vcloud_plugin_common.local_session_token', + None + ): + return client._subscription_login( + url, username, password, token, service, + org_name) # can't login with token with self.assertRaises(cfy_exc.NonRecoverableError): _run( @@ -124,10 +130,16 @@ def _run( with mock.patch( 'vcloud_plugin_common.ctx', fake_ctx ): - return client._ondemand_login( - url, username, password, token, instance_id - ) - + with mock.patch( + 'pyvcloud.vcloudair.VCS', mock.MagicMock() + ): + with mock.patch( + 'vcloud_plugin_common.local_session_token', + None + ): + return client._ondemand_login( + url, username, password, token, + instance_id) # bad case without instance with self.assertRaises(cfy_exc.NonRecoverableError): _run( @@ -248,7 +260,7 @@ def _run( fake_vca_client.assert_called_with( service_type='vcd', username='root', host='some_url', - version='upstream' + version='upstream', verify=True ) fake_client.login.assert_called_with( token='secret_token', org_url='org_url' @@ -263,7 +275,7 @@ def _run( fake_vca_client.assert_called_with( service_type='vcd', username='root', host='some_url', - version='upstream' + version='upstream', verify=True ) fake_client.login.assert_called_with( 'secret_password', org='org_name' @@ -377,7 +389,6 @@ def loginc_check(fake_client): 'url': 'url', 'username': 'username' }) - with mock.patch( 'time.sleep', mock.MagicMock(return_value=None) @@ -389,7 +400,10 @@ def loginc_check(fake_client): with mock.patch( 'vcloud_plugin_common.ctx', fake_ctx ): - loginc_check(fake_client) + with mock.patch( + 'pyvcloud.vcloudair.VCS', + mock.MagicMock()): + loginc_check(fake_client) def test_get(self): client = vcloud_plugin_common.VcloudAirClient() @@ -425,5 +439,6 @@ def test_get(self): fake_client ) + if __name__ == '__main__': unittest.main() diff --git a/tox.ini b/tox.ini index 71065a9..69fd006 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py27-ondemand, py27-subscription, py27-unittests, pep8 +envlist = py27-unittests, pep8, validate minversion = 1.6 skipsdist = True @@ -9,18 +9,28 @@ envdir = .tox/devenv deps = -rtest-requirements.txt -rdev-requirements.txt -[testenv:py27-ondemand] -commands = nosetests -x -s --tc=ondemand: tests/integration {posargs} - -[testenv:py27-subscription] -commands = nosetests -x -s --tc=subscription: tests/integration {posargs} - [testenv:py27-unittests] -commands = nosetests -x -s tests/unittests --cover-html --with-coverage --cover-package=vcloud_plugin_common --cover-package=network_plugin --cover-package=server_plugin +commands = nosetests tests/unittests --cover-html --with-coverage --cover-package=vcloud_plugin_common --cover-package=vcloud_network_plugin --cover-package=vcloud_server_plugin --cover-package=vcloud_storage_plugin [testenv:pep8] commands= - flake8 network_plugin server_plugin vcloud_plugin_common tests manager_blueprint/scripts + flake8 tests + flake8 vcloud_network_plugin + flake8 vcloud_server_plugin + flake8 vcloud_plugin_common + flake8 vcloud_storage_plugin + pylint -E tests.unittests \ + -E vcloud_network_plugin \ + -E vcloud_server_plugin \ + -E vcloud_plugin_common \ + -E vcloud_storage_plugin ignore = exclude=.venv,.tox,dist,*egg,etc,build filename=*.py + +[testenv:validate] +deps = + cloudify==4.5 + {[testenv]deps} +commands = + cfy blueprint validate examples/blueprint.yaml diff --git a/network_plugin/__init__.py b/vcloud_network_plugin/__init__.py similarity index 58% rename from network_plugin/__init__.py rename to vcloud_network_plugin/__init__.py index e0a1e26..18192cf 100644 --- a/network_plugin/__init__.py +++ b/vcloud_network_plugin/__init__.py @@ -1,24 +1,51 @@ +# Copyright (c) 2015-2020 Cloudify Platform Ltd. All rights reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + from IPy import IP from cloudify import exceptions as cfy_exc import collections from pyvcloud.schema.vcd.v1_5.schemas.vcloud import taskType from vcloud_plugin_common import (wait_for_task, get_vcloud_config, - is_subscription) + is_ondemand, error_response) +from cloudify_rest_client import exceptions as rest_exceptions +import time +from functools import wraps +from cloudify.manager import get_rest_client VCLOUD_VAPP_NAME = 'vcloud_vapp_name' PUBLIC_IP = 'public_ip' +SSH_PUBLIC_IP = 'ssh_public_ip' +SSH_PORT = 'ssh_port' NAT_ROUTED = 'natRouted' +GATEWAY_LOCK = 'gateway_lock' CREATE = 1 DELETE = 2 AssignedIPs = collections.namedtuple('AssignedIPs', 'external internal') -BUSY_MESSAGE = "The entity gateway is busy completing an operation." +BUSY_MESSAGE = "is busy completing an operation" + +GATEWAY_TIMEOUT = 30 +# try n times before fail +RETRY_COUNT = 10 +# sleep n seconds before retry +RETRY_SLEEP = 10 def check_ip(address): """ - check ip + check ip format """ try: IP(address) @@ -131,21 +158,21 @@ def get_vapp_name(runtime_properties): return vapp_name -def save_gateway_configuration(gateway, vca_client): +def save_gateway_configuration(gateway, vca_client, ctx): """ save gateway configuration, - return - True - everything successfully finished - False - gateway busy - raise NonRecoverableError - can't get task description + return everything successfully finished + raise NonRecoverableError - can't get task description """ task = gateway.save_services_configuration() if task: wait_for_task(vca_client, task) + ctx.logger.info("Gateway parameters has been saved.") return True else: error = taskType.parseString(gateway.response.content, True) if BUSY_MESSAGE in error.message: + ctx.logger.info("Gateway is busy.") return False else: raise cfy_exc.NonRecoverableError(error.message) @@ -215,7 +242,7 @@ def get_network(vca_client, network_name): """ if not network_name: raise cfy_exc.NonRecoverableError( - "Network name is empty".format(network_name)) + "Network name is empty: {0}".format(network_name)) network = vca_client.get_network(get_vcloud_config()['vdc'], network_name) if not network: raise cfy_exc.NonRecoverableError( @@ -228,18 +255,33 @@ def get_ondemand_public_ip(vca_client, gateway, ctx): try to allocate new public ip for ondemand service """ old_public_ips = set(gateway.get_public_ips()) - task = gateway.allocate_public_ip() - if task: - wait_for_task(vca_client, task) - else: - raise cfy_exc.NonRecoverableError( - "Can't get public ip for ondemand service") + allocated_ips = set([address.external + for address in collectAssignedIps(gateway)]) + available_ips = old_public_ips - allocated_ips + if available_ips: + new_ip = list(available_ips)[0] + ctx.logger.info("Public IP {0} was reused.".format(new_ip)) + return new_ip + for i in xrange(RETRY_COUNT): + ctx.logger.info("Try to allocate public IP") + wait_for_gateway(vca_client, gateway.get_name(), ctx) + task = gateway.allocate_public_ip() + if task: + try: + wait_for_task(vca_client, task) + break + except cfy_exc.NonRecoverableError: + continue + else: + raise cfy_exc.NonRecoverableError( + "Can't get public ip for ondemand service {0}". + format(error_response(gateway))) # update gateway for new IP address gateway = vca_client.get_gateways(get_vcloud_config()['vdc'])[0] new_public_ips = set(gateway.get_public_ips()) new_ip = new_public_ips - old_public_ips if new_ip: - ctx.logger.info("Assign public IP {0}".format(new_ip)) + ctx.logger.info("Public IP {0} was asigned.".format(new_ip)) else: raise cfy_exc.NonRecoverableError( "Can't get new public IP address") @@ -250,20 +292,23 @@ def del_ondemand_public_ip(vca_client, gateway, ip, ctx): """ try to deallocate public ip """ + ctx.logger.info("Try to deallocate public IP {0}".format(ip)) + wait_for_gateway(vca_client, gateway.get_name(), ctx) task = gateway.deallocate_public_ip(ip) if task: wait_for_task(vca_client, task) - ctx.logger.info("Public IP {0} deallocated".format(ip)) + ctx.logger.info("Public IP {0} was deallocated".format(ip)) else: raise cfy_exc.NonRecoverableError( - "Can't deallocate public ip {0} for ondemand service".format(ip)) + "Can't deallocate public ip {0}. {1} for ondemand service". + format(ip, error_response(gateway))) def get_public_ip(vca_client, gateway, service_type, ctx): """ return new public ip """ - if is_subscription(service_type): + if not is_ondemand(service_type): public_ip = getFreeIP(gateway) ctx.logger.info("Assign external IP {0}".format(public_ip)) else: @@ -281,3 +326,99 @@ def get_gateway(vca_client, gateway_name): raise cfy_exc.NonRecoverableError( "Gateway {0} not found".format(gateway_name)) return gateway + + +def set_retry(ctx): + """ + set retry on cloudify level + """ + return ctx.operation.retry( + message='Waiting for gateway.', + retry_after=GATEWAY_TIMEOUT) + + +def save_ssh_parameters(ctx, port, ip): + """save port and ip for ssh to context""" + retries_update = 3 + update_pending = True + while retries_update > 0 and update_pending: + retries_update = retries_update - 1 + try: + ctx.source.instance.runtime_properties[SSH_PORT] = port + ctx.source.instance.runtime_properties[SSH_PUBLIC_IP] = ip + ctx.source.instance.update() + update_pending = False + except rest_exceptions.CloudifyClientError as e: + if 'conflict' in str(e): + # cannot 'return' in contextmanager + ctx.logger.info( + "Conflict in updating backend, retrying") + else: + raise e + + +def wait_for_gateway(vca_client, gateway_name, ctx): + """try ten times to wait 10 seconds for gateway""" + for i in xrange(RETRY_COUNT): + gateway = get_gateway(vca_client, gateway_name) + if not gateway.is_busy(): + return + ctx.logger.info("Check {0}. Gateway is busy.".format(i)) + time.sleep(RETRY_SLEEP) + raise cfy_exc.NonRecoverableError( + "Can't wait gateway {0}".format(gateway_name)) + + +def _is_gateway_locked(ctx): + rest = None + try: + rest = get_rest_client() + except KeyError: + pass + if rest: + node_instances = rest.node_instances.list( + deployment_id=ctx.deployment.id) + elif ctx.deployment.id == 'local': + storage = ctx._endpoint.storage + node_instances = storage.get_node_instances() + else: + return False + for instance in node_instances: + rt_properties = instance['runtime_properties'] + if rt_properties.get(GATEWAY_LOCK): + return True + return False + + +def lock_gateway(f): + """loc gateway before operation""" + def update_parameters(ctx, value): + ctx.source.instance.runtime_properties[GATEWAY_LOCK] = value + ctx.source.instance.update() + + @wraps(f) + def wrapper(*args, **kw): + ctx = kw['ctx'] + if _is_gateway_locked(ctx): + ctx.logger.info("Gateway locked.") + return set_retry(ctx) + try: + ctx.logger.info("Lock gateway.") + update_parameters(ctx, True) + vca_client = kw['vca_client'] + gateway_name = get_vcloud_config().get('edge_gateway') + if gateway_name: + wait_for_gateway(vca_client, gateway_name, ctx) + else: + # we need gateway_name from vcloud for use this functionality + ctx.logger.info( + "'edge_gateway' in vcloud_config is empty." + " Can't check state of gateway correctly." + ) + result = f(*args, **kw) + finally: + ctx.logger.info("Unlock gateway.") + ctx.source.instance._node_instance = None + update_parameters(ctx, False) + return result + return wrapper diff --git a/network_plugin/floatingip.py b/vcloud_network_plugin/floatingip.py similarity index 50% rename from network_plugin/floatingip.py rename to vcloud_network_plugin/floatingip.py index 09dc613..17271b4 100644 --- a/network_plugin/floatingip.py +++ b/vcloud_network_plugin/floatingip.py @@ -1,36 +1,57 @@ -from cloudify import ctx +# Copyright (c) 2015-2020 Cloudify Platform Ltd. All rights reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + from cloudify import exceptions as cfy_exc from cloudify.decorators import operation from vcloud_plugin_common import (with_vca_client, get_vcloud_config, - is_subscription, is_ondemand, get_mandatory) -from network_plugin import (check_ip, CheckAssignedExternalIp, - CheckAssignedInternalIp, get_vm_ip, - save_gateway_configuration, getFreeIP, - CREATE, DELETE, PUBLIC_IP, get_gateway, - get_public_ip, del_ondemand_public_ip) - - -@operation + is_subscription, is_ondemand, get_mandatory, + combine_properties) +from vcloud_network_plugin import (check_ip, CheckAssignedExternalIp, + CheckAssignedInternalIp, get_vm_ip, + save_gateway_configuration, getFreeIP, + CREATE, DELETE, PUBLIC_IP, get_gateway, + SSH_PUBLIC_IP, SSH_PORT, + save_ssh_parameters, get_public_ip, + del_ondemand_public_ip, set_retry, + lock_gateway) + + +@operation(resumable=True) @with_vca_client -def connect_floatingip(vca_client, **kwargs): +@lock_gateway +def connect_floatingip(ctx, vca_client, **kwargs): """ create new floating ip for node """ - _floatingip_operation(CREATE, vca_client, ctx) + if not _floatingip_operation(CREATE, vca_client, ctx): + return set_retry(ctx) -@operation +@operation(resumable=True) @with_vca_client -def disconnect_floatingip(vca_client, **kwargs): +@lock_gateway +def disconnect_floatingip(ctx, vca_client, **kwargs): """ release floating ip """ - _floatingip_operation(DELETE, vca_client, ctx) + if not _floatingip_operation(DELETE, vca_client, ctx): + return set_retry(ctx) -@operation +@operation(resumable=True) @with_vca_client -def creation_validation(vca_client, **kwargs): +def creation_validation(ctx, vca_client, **kwargs): """ validate node context, fields from floatingip dict: @@ -41,7 +62,10 @@ def creation_validation(vca_client, **kwargs): also check availability of public ip if set or exist some free ip in subscription case """ - floatingip = get_mandatory(ctx.node.properties, 'floatingip') + # combine properties + obj = combine_properties(ctx, kwargs=kwargs, names=['floatingip']) + # get floatingip + floatingip = get_mandatory(obj, 'floatingip') edge_gateway = get_mandatory(floatingip, 'edge_gateway') gateway = get_gateway(vca_client, edge_gateway) service_type = get_vcloud_config().get('service_type') @@ -61,13 +85,19 @@ def _floatingip_operation(operation, vca_client, ctx): save selected public_ip in runtime properties """ service_type = get_vcloud_config().get('service_type') + # combine properties + obj = combine_properties(ctx.target, names=['floatingip'], copy_back=False) + gateway = get_gateway( - vca_client, ctx.target.node.properties['floatingip']['edge_gateway']) + vca_client, obj['floatingip']['edge_gateway']) internal_ip = get_vm_ip(vca_client, ctx, gateway) nat_operation = None - public_ip = (ctx.target.instance.runtime_properties.get(PUBLIC_IP) - or ctx.target.node.properties['floatingip'].get(PUBLIC_IP)) + public_ip = ( + ctx.target.instance.runtime_properties.get(PUBLIC_IP) + ) or ( + obj['floatingip'].get(PUBLIC_IP) + ) if operation == CREATE: CheckAssignedInternalIp(internal_ip, gateway) if public_ip: @@ -78,8 +108,8 @@ def _floatingip_operation(operation, vca_client, ctx): nat_operation = _add_nat_rule elif operation == DELETE: if not public_ip: - ctx.logger.info("Can't get external IP".format(public_ip)) - return + ctx.logger.info("Can't get external IP {0}".format(public_ip)) + return True nat_operation = _del_nat_rule else: raise cfy_exc.NonRecoverableError( @@ -88,26 +118,32 @@ def _floatingip_operation(operation, vca_client, ctx): external_ip = check_ip(public_ip) - nat_operation(gateway, "SNAT", internal_ip, external_ip) - nat_operation(gateway, "DNAT", external_ip, internal_ip) - if not save_gateway_configuration(gateway, vca_client): - return ctx.operation.retry(message='Waiting for gateway.', - retry_after=10) - + nat_operation(ctx, gateway, "SNAT", internal_ip, external_ip) + nat_operation(ctx, gateway, "DNAT", external_ip, internal_ip) + success = save_gateway_configuration(gateway, vca_client, ctx) + if not success: + return False if operation == CREATE: ctx.target.instance.runtime_properties[PUBLIC_IP] = external_ip + save_ssh_parameters(ctx, '22', external_ip) else: if is_ondemand(service_type): - if not ctx.target.node.properties['floatingip'].get(PUBLIC_IP): + if not obj['floatingip'].get(PUBLIC_IP): del_ondemand_public_ip( vca_client, gateway, ctx.target.instance.runtime_properties[PUBLIC_IP], ctx) - del ctx.target.instance.runtime_properties[PUBLIC_IP] + if PUBLIC_IP in ctx.target.instance.runtime_properties: + del ctx.target.instance.runtime_properties[PUBLIC_IP] + if SSH_PUBLIC_IP in ctx.source.instance.runtime_properties: + del ctx.source.instance.runtime_properties[SSH_PUBLIC_IP] + if SSH_PORT in ctx.source.instance.runtime_properties: + del ctx.source.instance.runtime_properties[SSH_PORT] + return True -def _add_nat_rule(gateway, rule_type, original_ip, translated_ip): +def _add_nat_rule(ctx, gateway, rule_type, original_ip, translated_ip): """ add nat rule with enable any types of trafic from translated_ip to origin_ip @@ -122,7 +158,7 @@ def _add_nat_rule(gateway, rule_type, original_ip, translated_ip): rule_type, original_ip, any_type, translated_ip, any_type, any_type) -def _del_nat_rule(gateway, rule_type, original_ip, translated_ip): +def _del_nat_rule(ctx, gateway, rule_type, original_ip, translated_ip): """ drop rule created by add_nat_rule """ diff --git a/vcloud_network_plugin/keypair.py b/vcloud_network_plugin/keypair.py new file mode 100644 index 0000000..f5dc23a --- /dev/null +++ b/vcloud_network_plugin/keypair.py @@ -0,0 +1,170 @@ +# Copyright (c) 2015-2020 Cloudify Platform Ltd. All rights reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from cloudify import exceptions as cfy_exc +from cloudify.decorators import operation +from vcloud_plugin_common import (combine_properties, delete_properties) +import os.path +from os import chmod +from Crypto.PublicKey import RSA +from Crypto import Random + +AUTO_GENERATE = 'auto_generate' +PRIVATE_KEY = 'private_key' +PUBLIC_KEY = 'public_key' +CREATE_PRIVATE_KEY_FILE = 'create_file' +AGENT_CONFIG = 'agent_config' +PATH = 'path' +KEY = 'key' +USER = 'user' +HOME = 'home' +SSH_KEY = 'ssh_key' + + +@operation(resumable=True) +def creation_validation(ctx, **kwargs): + """ + check availability of path used in field private key path of + node properties + """ + # combine properties + obj = combine_properties(ctx, kwargs=kwargs, + names=[PRIVATE_KEY, PUBLIC_KEY], + properties=['auto_generate']) + # get key + key_path = obj.get(PRIVATE_KEY, {}).get(PATH) + if key_path: + key_path = os.path.expanduser(key_path) + if not os.path.isfile(key_path): + raise cfy_exc.NonRecoverableError( + "Private key file {0} is absent".format(key_path)) + + +@operation(resumable=True) +def create(ctx, **kwargs): + ctx.instance.runtime_properties[PUBLIC_KEY] = {} + ctx.instance.runtime_properties[PRIVATE_KEY] = {} + ctx.instance.runtime_properties[PUBLIC_KEY][USER] = \ + ctx.node.properties.get(PUBLIC_KEY, {}).get(USER) + ctx.instance.runtime_properties[PUBLIC_KEY][HOME] = \ + ctx.node.properties.get(PUBLIC_KEY, {}).get(HOME) + # combine properties + obj = combine_properties(ctx, kwargs=kwargs, + names=[PRIVATE_KEY, PUBLIC_KEY], + properties=['auto_generate']) + # get key + if obj.get(AUTO_GENERATE): + ctx.logger.info("Generating ssh keypair") + public, private = _generate_pair() + ctx.instance.runtime_properties[PRIVATE_KEY][KEY] = private + ctx.instance.runtime_properties[PUBLIC_KEY][KEY] = public + if obj.get(PRIVATE_KEY, {}).get(CREATE_PRIVATE_KEY_FILE): + ctx.instance.runtime_properties[ + PRIVATE_KEY][PATH] = _create_path(ctx) + _save_key_file(ctx.instance.runtime_properties[PRIVATE_KEY][PATH], + ctx.instance.runtime_properties[PRIVATE_KEY][KEY]) + else: + ctx.instance.runtime_properties[PUBLIC_KEY][KEY] = \ + obj.get(PUBLIC_KEY, {}).get(KEY) + ctx.instance.runtime_properties[PRIVATE_KEY][KEY] = \ + obj.get(PRIVATE_KEY, {}).get(KEY) + ctx.instance.runtime_properties[PRIVATE_KEY][PATH] = \ + obj.get(PRIVATE_KEY, {}).get(PATH) + if obj.get(PRIVATE_KEY, {}).get(CREATE_PRIVATE_KEY_FILE): + if obj.get(PRIVATE_KEY, {}).get(KEY): + ctx.instance.runtime_properties[ + PRIVATE_KEY][PATH] = _create_path(ctx) + _save_key_file( + ctx.instance.runtime_properties[PRIVATE_KEY][PATH], + ctx.instance.runtime_properties[PRIVATE_KEY][KEY]) + + +@operation(resumable=True) +def delete(ctx, **kwargs): + # combine properties + obj = combine_properties(ctx, kwargs=kwargs, + names=[PRIVATE_KEY, PUBLIC_KEY], + properties=['auto_generate']) + # get key + if obj[AUTO_GENERATE]: + if obj.get(PRIVATE_KEY, {}).get(CREATE_PRIVATE_KEY_FILE): + _delete_key_file(ctx.instance.runtime_properties) + else: + if obj.get(PRIVATE_KEY, {}).get(KEY): + if obj.get(PRIVATE_KEY, {}).get(CREATE_PRIVATE_KEY_FILE): + _delete_key_file(ctx.instance.runtime_properties) + if PRIVATE_KEY in ctx.instance.runtime_properties: + del ctx.instance.runtime_properties[PRIVATE_KEY] + if PUBLIC_KEY in ctx.instance.runtime_properties: + del ctx.instance.runtime_properties[PUBLIC_KEY] + delete_properties(ctx) + + +@operation(resumable=True) +def server_connect_to_keypair(ctx, **kwargs): + host_rt_properties = ctx.source.instance.runtime_properties + target_rt_properties = ctx.target.instance.runtime_properties + if SSH_KEY not in host_rt_properties: + host_rt_properties[SSH_KEY] = {} + if PRIVATE_KEY in target_rt_properties: + host_rt_properties[SSH_KEY][PATH] = target_rt_properties[ + PRIVATE_KEY].get(PATH) + host_rt_properties[SSH_KEY][KEY] = target_rt_properties[ + PRIVATE_KEY].get(KEY) + if PUBLIC_KEY in target_rt_properties: + host_rt_properties[SSH_KEY][USER] = target_rt_properties[ + PUBLIC_KEY].get(USER) + if target_rt_properties[PRIVATE_KEY].get(PATH): + host_rt_properties[AGENT_CONFIG] = {} + host_rt_properties[AGENT_CONFIG][KEY] = target_rt_properties[ + PRIVATE_KEY].get(PATH) + ctx.source.instance.update() + + +@operation(resumable=True) +def server_disconnect_from_keypair(ctx, **kwargs): + host_rt_properties = ctx.source.instance.runtime_properties + if SSH_KEY in host_rt_properties: + del host_rt_properties[SSH_KEY] + if AGENT_CONFIG in host_rt_properties: + del host_rt_properties[AGENT_CONFIG] + + +def _generate_pair(): + Random.atfork() # uses for strong key generation + key = RSA.generate(2048) + private_value = key.exportKey('PEM') + public_value = key.publickey().exportKey('OpenSSH') + return public_value, private_value + + +def _create_path(ctx): + if ctx._local: + key_dir = ctx._context['storage']._storage_dir + else: + key_dir = os.path.dirname(os.environ['VIRTUALENV']) + return '{}/{}_private.key'.format(key_dir, ctx.instance.id) + + +def _save_key_file(path, value): + path = os.path.expanduser(path) + with open(path, 'w') as content_file: + chmod(path, 0600) + content_file.write(value) + + +def _delete_key_file(properties): + if PRIVATE_KEY in properties and PATH in properties[PRIVATE_KEY]: + path = properties[PRIVATE_KEY][PATH] + os.unlink(os.path.expanduser(path)) diff --git a/network_plugin/network.py b/vcloud_network_plugin/network.py similarity index 53% rename from network_plugin/network.py rename to vcloud_network_plugin/network.py index 0319284..15acb7c 100644 --- a/network_plugin/network.py +++ b/vcloud_network_plugin/network.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved +# Copyright (c) 2014-2020 Cloudify Platform Ltd. All rights reserved # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -8,29 +8,32 @@ # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, -# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# * See the License for the specific language governing permissions and -# * limitations under the License. +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. -from cloudify import ctx from cloudify import exceptions as cfy_exc from cloudify.decorators import operation from vcloud_plugin_common import (with_vca_client, wait_for_task, - get_vcloud_config, get_mandatory) + get_vcloud_config, get_mandatory, + delete_properties, combine_properties) import collections -from network_plugin import (check_ip, is_valid_ip_range, is_separate_ranges, - is_ips_in_same_subnet, save_gateway_configuration, - get_network_name, is_network_exists) - +from vcloud_network_plugin import (check_ip, is_valid_ip_range, + is_separate_ranges, is_ips_in_same_subnet, + save_gateway_configuration, + get_network_name, is_network_exists, + get_gateway, set_retry) VCLOUD_NETWORK_NAME = 'vcloud_network_name' +SKIP_CREATE_NETWORK = 'skip_create_network' ADD_POOL = 1 DELETE_POOL = 2 +CANT_DELETE = "cannot be deleted, because it is in use" -@operation +@operation(resumable=True) @with_vca_client -def create(vca_client, **kwargs): +def create(ctx, vca_client, **kwargs): """ create new vcloud air network, e.g.: { @@ -50,8 +53,11 @@ def create(vca_client, **kwargs): } """ vdc_name = get_vcloud_config()['vdc'] - if ctx.node.properties['use_external_resource']: - network_name = ctx.node.properties['resource_id'] + # combine properties + obj = combine_properties(ctx, kwargs=kwargs, names=['network']) + # check external resource + if obj['use_external_resource']: + network_name = obj['resource_id'] if not is_network_exists(vca_client, network_name): raise cfy_exc.NonRecoverableError( "Can't find external resource: {0}".format(network_name)) @@ -59,77 +65,95 @@ def create(vca_client, **kwargs): ctx.logger.info( "External resource {0} has been used".format(network_name)) return - net_prop = ctx.node.properties["network"] - network_name = get_network_name(ctx.node.properties) - if network_name in _get_network_list(vca_client, - get_vcloud_config()['vdc']): - raise cfy_exc.NonRecoverableError( - "Network {0} already exists, but parameter " - "'use_external_resource' is 'false' or absent" - .format(network_name)) + network_name = get_network_name(obj) + if not ctx.instance.runtime_properties.get(SKIP_CREATE_NETWORK): + net_prop = obj["network"] + if network_name in _get_network_list(vca_client, + get_vcloud_config()['vdc']): + raise cfy_exc.NonRecoverableError( + "Network {0} already exists, but parameter " + "'use_external_resource' is 'false' or absent" + .format(network_name)) - ip = _split_adresses(net_prop['static_range']) - gateway_name = net_prop['edge_gateway'] - if not vca_client.get_gateway(vdc_name, gateway_name): - raise cfy_exc.NonRecoverableError( - "Gateway {0} not found".format(gateway_name)) - start_address = ip.start - end_address = ip.end - gateway_ip = net_prop["gateway_ip"] - netmask = net_prop["netmask"] - dns1 = "" - dns2 = "" - dns_list = net_prop.get("dns") - if dns_list: - dns1 = dns_list[0] - if len(dns_list) > 1: - dns2 = dns_list[1] - dns_suffix = net_prop.get("dns_suffix") - success, result = vca_client.create_vdc_network( - vdc_name, network_name, gateway_name, start_address, - end_address, gateway_ip, netmask, dns1, dns2, dns_suffix) - if success: - ctx.logger.info("Network {0} has been successfully created." + static_range = get_mandatory(net_prop, 'static_range') + ip = _split_adresses(static_range) + gateway_name = net_prop['edge_gateway'] + get_gateway(vca_client, gateway_name) + start_address = ip.start + end_address = ip.end + gateway_ip = net_prop["gateway_ip"] + netmask = net_prop["netmask"] + dns1 = "" + dns2 = "" + dns_list = net_prop.get("dns") + if dns_list: + dns1 = dns_list[0] + if len(dns_list) > 1: + dns2 = dns_list[1] + dns_suffix = net_prop.get("dns_suffix") + ctx.logger.info("Create network {0}." .format(network_name)) - else: - raise cfy_exc.NonRecoverableError( - "Could not create network {0}: {1}".format(network_name, result)) - wait_for_task(vca_client, result) - ctx.instance.runtime_properties[VCLOUD_NETWORK_NAME] = network_name - _dhcp_operation(vca_client, network_name, ADD_POOL) + success, result = vca_client.create_vdc_network( + vdc_name, network_name, gateway_name, start_address, + end_address, gateway_ip, netmask, dns1, dns2, dns_suffix) + if success: + wait_for_task(vca_client, result) + ctx.logger.info("Network {0} has been successfully created." + .format(network_name)) + else: + raise cfy_exc.NonRecoverableError( + "Could not create network {0}: {1}". + format(network_name, result)) + ctx.instance.runtime_properties[VCLOUD_NETWORK_NAME] = network_name + if not _dhcp_operation(ctx, vca_client, obj, network_name, ADD_POOL): + ctx.instance.runtime_properties[SKIP_CREATE_NETWORK] = True + return set_retry(ctx) -@operation +@operation(resumable=True) @with_vca_client -def delete(vca_client, **kwargs): +def delete(ctx, vca_client, **kwargs): """ delete vcloud air network """ - if ctx.node.properties['use_external_resource'] is True: + # combine properties + obj = combine_properties(ctx, kwargs=kwargs, names=['network']) + # check external resource + if obj['use_external_resource'] is True: del ctx.instance.runtime_properties[VCLOUD_NETWORK_NAME] ctx.logger.info("Network was not deleted - external resource has" " been used") return - network_name = get_network_name(ctx.node.properties) - _dhcp_operation(vca_client, network_name, DELETE_POOL) + network_name = get_network_name(obj) + if not _dhcp_operation(ctx, vca_client, obj, network_name, DELETE_POOL): + return set_retry(ctx) + ctx.logger.info("Delete network '{0}'".format(network_name)) success, task = vca_client.delete_vdc_network( get_vcloud_config()['vdc'], network_name) if success: + wait_for_task(vca_client, task) ctx.logger.info( - "Network {0} has been successful deleted.".format(network_name)) + "Network '{0}' has been successful deleted.".format(network_name)) else: + if task and CANT_DELETE in task: + ctx.logger.info("Network {} in use. Deleting the network skipped.". + format(network_name)) + return raise cfy_exc.NonRecoverableError( - "Could not delete network {0}".format(network_name)) - wait_for_task(vca_client, task) + "Could not delete network '{0}': {1}".format(network_name, task)) + delete_properties(ctx) -@operation +@operation(resumable=True) @with_vca_client -def creation_validation(vca_client, **kwargs): +def creation_validation(ctx, vca_client, **kwargs): """ check network description from node description """ - network_name = get_network_name(ctx.node.properties) + # combine properties + obj = combine_properties(ctx, kwargs=kwargs, names=['network']) + # check external resource + network_name = get_network_name(obj) ctx.logger.info("Validation cloudify.vcloud.nodes.Network node: {0}" .format(network_name)) if is_network_exists(vca_client, network_name): @@ -140,7 +164,8 @@ def creation_validation(vca_client, **kwargs): raise cfy_exc.NonRecoverableError( "Network already exsists: {0}".format(network_name)) - net_prop = get_mandatory(ctx.node.properties, "network") + # get net + net_prop = get_mandatory(obj, "network") gateway_name = get_mandatory(net_prop, 'edge_gateway') if not vca_client.get_gateway(get_vcloud_config()['vdc'], gateway_name): raise cfy_exc.NonRecoverableError( @@ -166,23 +191,21 @@ def creation_validation(vca_client, **kwargs): "Static_range and dhcp_range is overlapped.") ips.extend([dhcp_ip.start, dhcp_ip.end]) if not is_ips_in_same_subnet(ips, netmask): - raise cfy_exc.NonRecoverableError( - "IP addresses in different subnets.") + raise cfy_exc.NonRecoverableError( + "IP addresses in different subnets.") -def _dhcp_operation(vca_client, network_name, operation): +def _dhcp_operation(ctx, vca_client, obj, network_name, operation): """ update dhcp setting for network """ - dhcp_settings = ctx.node.properties['network'].get('dhcp') + dhcp_settings = obj['network'].get('dhcp') if dhcp_settings is None: - return - gateway_name = ctx.node.properties["network"]['edge_gateway'] - gateway = vca_client.get_gateway(get_vcloud_config()['vdc'], gateway_name) - if not gateway: - raise cfy_exc.NonRecoverableError( - "Gateway {0} not found!".format(gateway_name)) - + return True + gateway_name = obj["network"]['edge_gateway'] + gateway = get_gateway(vca_client, gateway_name) + if gateway.is_busy(): + return False if operation == ADD_POOL: ip = _split_adresses(dhcp_settings['dhcp_range']) low_ip_address = check_ip(ip.start) @@ -191,17 +214,18 @@ def _dhcp_operation(vca_client, network_name, operation): max_lease = dhcp_settings.get('max_lease') gateway.add_dhcp_pool(network_name, low_ip_address, hight_ip_address, default_lease, max_lease) - ctx.logger.info("DHCP rule successful created for network {0}" - .format(network_name)) + if save_gateway_configuration(gateway, vca_client, ctx): + ctx.logger.info("DHCP rule successful created for network {0}" + .format(network_name)) + return True if operation == DELETE_POOL: gateway.delete_dhcp_pool(network_name) - ctx.logger.info("DHCP rule successful deleted for network {0}" - .format(network_name)) - - if not save_gateway_configuration(gateway, vca_client): - return ctx.operation.retry(message='Waiting for gateway.', - retry_after=10) + if save_gateway_configuration(gateway, vca_client, ctx): + ctx.logger.info("DHCP rule successful deleted for network {0}" + .format(network_name)) + return True + return False def _split_adresses(address_range): diff --git a/vcloud_network_plugin/port.py b/vcloud_network_plugin/port.py new file mode 100644 index 0000000..470e1aa --- /dev/null +++ b/vcloud_network_plugin/port.py @@ -0,0 +1,47 @@ +# Copyright (c) 2015-2020 Cloudify Platform Ltd. All rights reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from cloudify import exceptions as cfy_exc +from cloudify.decorators import operation +from vcloud_plugin_common import (with_vca_client, get_mandatory, + combine_properties, delete_properties) +from vcloud_network_plugin import check_ip + + +@operation(resumable=True) +@with_vca_client +def creation_validation(ctx, vca_client, **kwargs): + """ + validate port settings, + ip_allocation_mode must be in 'manual', 'dhcp', 'pool', + and valid ip_address if set + """ + # combine properties + obj = combine_properties(ctx, kwargs=kwargs, names=['port']) + # get port + port = get_mandatory(obj, 'port') + ip_allocation_mode = port.get('ip_allocation_mode') + if ip_allocation_mode: + if ip_allocation_mode.lower() not in ['manual', 'dhcp', 'pool']: + raise cfy_exc.NonRecoverableError( + "Unknown allocation mode {0}".format(ip_allocation_mode)) + ip_address = port.get('ip_address') + if ip_address: + check_ip(ip_address) + + +@operation(resumable=True) +@with_vca_client +def delete(ctx, vca_client, **kwargs): + delete_properties(ctx) diff --git a/network_plugin/public_nat.py b/vcloud_network_plugin/public_nat.py similarity index 59% rename from network_plugin/public_nat.py rename to vcloud_network_plugin/public_nat.py index 70a92ee..a6f1458 100644 --- a/network_plugin/public_nat.py +++ b/vcloud_network_plugin/public_nat.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved +# Copyright (c) 2014-2020 Cloudify Platform Ltd. All rights reserved # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -8,74 +8,111 @@ # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, -# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# * See the License for the specific language governing permissions and -# * limitations under the License. +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. -from cloudify import ctx from cloudify import exceptions as cfy_exc from cloudify.decorators import operation from vcloud_plugin_common import (with_vca_client, get_vcloud_config, - get_mandatory, is_subscription, is_ondemand) -from network_plugin import (check_ip, save_gateway_configuration, - get_vm_ip, get_public_ip, - get_gateway, getFreeIP, CREATE, DELETE, PUBLIC_IP, - del_ondemand_public_ip, utils) -from network_plugin.network import VCLOUD_NETWORK_NAME + get_mandatory, is_subscription, + combine_properties, is_ondemand) +from vcloud_network_plugin import (check_ip, save_gateway_configuration, + get_vm_ip, get_public_ip, + get_gateway, getFreeIP, CREATE, DELETE, + PUBLIC_IP, SSH_PUBLIC_IP, SSH_PORT, + save_ssh_parameters, del_ondemand_public_ip, + utils, set_retry, lock_gateway) +from vcloud_network_plugin.network import VCLOUD_NETWORK_NAME from IPy import IP PORT_REPLACEMENT = 'port_replacement' +DEFAULT_SSH_PORT = '22' -@operation +@operation(resumable=True) @with_vca_client -def net_connect_to_nat(vca_client, **kwargs): +def net_connect_to_nat_preconfigure(ctx, vca_client, **kwargs): + # combine properties + obj = combine_properties( + ctx.target, names=['nat'], properties=['rules'], copy_back=False) + rules = obj['rules'] + if not rules or len(rules) != 1: + raise cfy_exc.NonRecoverableError( + "Rules list must contains only one element") + if _is_dnat(rules[0]['type']): + raise cfy_exc.NonRecoverableError( + "In 'cloudify.vcloud.net_connected_to_public_nat' relationship" + " you can use only 'SNAT' rule.") + + +@operation(resumable=True) +@with_vca_client +@lock_gateway +def net_connect_to_nat(ctx, vca_client, **kwargs): """ create nat rule for current node """ - if ctx.target.node.properties.get('use_external_resource', False): + # combine properties + obj = combine_properties( + ctx.target, names=['nat'], properties=['rules'], copy_back=False) + if obj.get('use_external_resource', False): ctx.logger.info("Using existing Public NAT.") return - prepare_network_operation(vca_client, CREATE) + if not prepare_network_operation(ctx, vca_client, CREATE): + return set_retry(ctx) -@operation +@operation(resumable=True) @with_vca_client -def net_disconnect_from_nat(vca_client, **kwargs): +@lock_gateway +def net_disconnect_from_nat(ctx, vca_client, **kwargs): """ drop nat rule for current node """ - if ctx.target.node.properties.get('use_external_resource', False): + # combine properties + obj = combine_properties( + ctx.target, names=['nat'], properties=['rules'], copy_back=False) + if obj.get('use_external_resource', False): ctx.logger.info("Using existing Public NAT.") return - prepare_network_operation(vca_client, DELETE) + if not prepare_network_operation(ctx, vca_client, DELETE): + return set_retry(ctx) -@operation +@operation(resumable=True) @with_vca_client -def server_connect_to_nat(vca_client, **kwargs): +@lock_gateway +def server_connect_to_nat(ctx, vca_client, **kwargs): """ create nat rules for server """ - prepare_server_operation(vca_client, CREATE) + if not prepare_server_operation(ctx, vca_client, CREATE): + return set_retry(ctx) -@operation +@operation(resumable=True) @with_vca_client -def server_disconnect_from_nat(vca_client, **kwargs): +@lock_gateway +def server_disconnect_from_nat(ctx, vca_client, **kwargs): """ drop nat rules for server """ - prepare_server_operation(vca_client, DELETE) + if not prepare_server_operation(ctx, vca_client, DELETE): + return set_retry(ctx) -@operation +@operation(resumable=True) @with_vca_client -def creation_validation(vca_client, **kwargs): +def creation_validation(ctx, vca_client, **kwargs): """ validate nat rules in node properties """ - nat = get_mandatory(ctx.node.properties, 'nat') + # combine properties + obj = combine_properties( + ctx, kwargs=kwargs, names=['nat'], properties=['rules']) + # get net + nat = get_mandatory(obj, 'nat') gateway = get_gateway(vca_client, get_mandatory(nat, 'edge_gateway')) service_type = get_vcloud_config().get('service_type') public_ip = nat.get(PUBLIC_IP) @@ -84,8 +121,10 @@ def creation_validation(vca_client, **kwargs): else: if is_subscription(service_type): getFreeIP(gateway) - for rule in get_mandatory(ctx.node.properties, 'rules'): - if rule['type'] == "DNAT": + # get rules + rules = get_mandatory(obj, 'rules') + for rule in rules: + if _is_dnat(rule['type']): utils.check_protocol(rule.get('protocol')) original_port = rule.get('original_port') if original_port and not isinstance(original_port, int): @@ -97,7 +136,7 @@ def creation_validation(vca_client, **kwargs): "Parameter 'translated_port' must be integer") -def prepare_network_operation(vca_client, operation): +def prepare_network_operation(ctx, vca_client, operation): """ create nat rules by rules from network node """ @@ -105,58 +144,79 @@ def prepare_network_operation(vca_client, operation): gateway = get_gateway( vca_client, ctx.target.node.properties['nat']['edge_gateway']) public_ip = _obtain_public_ip(vca_client, ctx, gateway, operation) - private_ip = _create_ip_range(vca_client, gateway) + if not public_ip: + ctx.logger.info("We dont have public ip. Retrying...") + return False + # if no private ip_range - raised error + private_ip = _create_ip_range(ctx, vca_client, gateway) for rule in ctx.target.node.properties['rules']: rule_type = rule['type'] nat_network_operation( - vca_client, gateway, operation, + ctx, vca_client, gateway, operation, rule_type, public_ip, private_ip, "any", "any", "any") except KeyError as e: raise cfy_exc.NonRecoverableError( "Parameter not found: {0}".format(e) ) - _save_configuration(gateway, vca_client, operation, public_ip) + return _save_configuration(ctx, gateway, vca_client, operation, public_ip) -def prepare_server_operation(vca_client, operation): +def prepare_server_operation(ctx, vca_client, operation): """ generate nat rules by current list of rules in node """ try: + # combine properties + obj = combine_properties( + ctx.target, names=['nat'], properties=['rules'], copy_back=False) gateway = get_gateway( - vca_client, ctx.target.node.properties['nat']['edge_gateway']) + vca_client, obj['nat']['edge_gateway']) public_ip = _obtain_public_ip(vca_client, ctx, gateway, operation) + if not public_ip: + ctx.logger.info("We dont have public ip. Retrying...") + return False private_ip = get_vm_ip(vca_client, ctx, gateway) - for rule in ctx.target.node.properties['rules']: + if not private_ip: + ctx.logger.info("We dont have private ip. Retrying...") + return False + has_snat = False + for rule in obj['rules']: rule_type = rule['type'] + if has_snat and _is_snat(rule_type): + ctx.logger.info("Rules list must contains only one SNAT rule.") + continue protocol = rule.get('protocol', "any") original_port = rule.get('original_port', "any") translated_port = rule.get('translated_port', "any") nat_network_operation( - vca_client, gateway, operation, + ctx, vca_client, gateway, operation, rule_type, public_ip, private_ip, original_port, translated_port, protocol) + if _is_snat(rule_type): + has_snat = True except KeyError as e: raise cfy_exc.NonRecoverableError("Parameter not found: {0}".format(e)) - _save_configuration(gateway, vca_client, operation, public_ip) + return _save_configuration(ctx, gateway, vca_client, operation, public_ip) -def nat_network_operation(vca_client, gateway, operation, rule_type, public_ip, - private_ip, original_port, translated_port, - protocol): +def nat_network_operation(ctx, vca_client, gateway, operation, rule_type, + public_ip, private_ip, original_port, + translated_port, protocol): """ create/drop nat rule for current network """ if operation == CREATE: new_original_port = _get_original_port_for_create( - gateway, rule_type, public_ip, original_port, + ctx, gateway, rule_type, public_ip, original_port, private_ip, translated_port, protocol) function = gateway.add_nat_rule message = "Add" + if _is_dnat(rule_type) and str(translated_port) == DEFAULT_SSH_PORT: + save_ssh_parameters(ctx, str(new_original_port), public_ip) elif operation == DELETE: new_original_port = _get_original_port_for_delete( - public_ip, original_port) + ctx, public_ip, original_port) function = gateway.del_nat_rule message = "Remove" else: @@ -166,7 +226,7 @@ def nat_network_operation(vca_client, gateway, operation, rule_type, public_ip, info_message = ("{6} NAT rule: rule type '{2}', original_ip '{0}', " "translated_ip '{1}',protocol '{3}', " "original_port '{4}', translated_port '{5}'") - if rule_type == "SNAT": + if _is_snat(rule_type): # for SNAT type ports and protocol must by "any", # because they are not configurable ctx.logger.info( @@ -175,23 +235,27 @@ def nat_network_operation(vca_client, gateway, operation, rule_type, public_ip, message)) function( rule_type, private_ip, "any", public_ip, "any", "any") - elif rule_type == "DNAT": + elif _is_dnat(rule_type): ctx.logger.info( info_message.format(public_ip, private_ip, rule_type, protocol, new_original_port, translated_port, message)) function(rule_type, public_ip, str(new_original_port), private_ip, str(translated_port), protocol) + else: + raise cfy_exc.NonRecoverableError( + "Unknown rule type: {0}".format(rule_type)) -def _save_configuration(gateway, vca_client, operation, public_ip): +def _save_configuration(ctx, gateway, vca_client, operation, public_ip): """ save/refresh nat rules on gateway """ - if not save_gateway_configuration(gateway, vca_client): - return ctx.operation.retry(message='Waiting for gateway.', - retry_after=10) - ctx.logger.info("NAT configuration has been saved") + ctx.logger.info("Save NAT configuration.") + success = save_gateway_configuration(gateway, vca_client, ctx) + if not success: + return False + ctx.logger.info("NAT configuration has been saved.") if operation == CREATE: ctx.target.instance.runtime_properties[PUBLIC_IP] = public_ip else: @@ -203,16 +267,25 @@ def _save_configuration(gateway, vca_client, operation, public_ip): ctx.target.instance.runtime_properties[PUBLIC_IP], ctx ) - del ctx.target.instance.runtime_properties[PUBLIC_IP] + if PUBLIC_IP in ctx.target.instance.runtime_properties: + del ctx.target.instance.runtime_properties[PUBLIC_IP] + if PORT_REPLACEMENT in ctx.target.instance.runtime_properties: + del ctx.target.instance.runtime_properties[PORT_REPLACEMENT] + if SSH_PORT in ctx.target.instance.runtime_properties: + del ctx.target.instance.runtime_properties[SSH_PORT] + if SSH_PUBLIC_IP in ctx.target.instance.runtime_properties: + del ctx.target.instance.runtime_properties[SSH_PUBLIC_IP] + return True -def _create_ip_range(vca_client, gateway): +def _create_ip_range(ctx, vca_client, gateway): """ - return ip range by avaible ranges from gateway and current network + return ip range by avaible ranges from gateway and current network, + on error - raise error, never return None """ network_name = ctx.source.instance.runtime_properties[VCLOUD_NETWORK_NAME] - org_name = get_vcloud_config()['org'] - net = _get_network_ip_range(vca_client, org_name, network_name) + vdc_name = get_vcloud_config()['vdc'] + net = _get_network_ip_range(vca_client, vdc_name, network_name) gate = _get_gateway_ip_range(gateway, network_name) if not net: raise cfy_exc.NonRecoverableError( @@ -223,11 +296,11 @@ def _create_ip_range(vca_client, gateway): return "{} - {}".format(min(net), max(net)) -def _get_network_ip_range(vca_client, org_name, network_name): +def _get_network_ip_range(vca_client, vdc_name, network_name): """ return ips for network from network configuration ipscopes """ - networks = vca_client.get_networks(org_name) + networks = vca_client.get_networks(vdc_name) ip_scope = [net.Configuration.IpScopes.IpScope for net in networks if network_name == net.get_name()] addresses = [] @@ -284,13 +357,14 @@ def _obtain_public_ip(vca_client, ctx, gateway, operation): def _get_original_port_for_create( - gateway, rule_type, original_ip, original_port, translated_ip, + ctx, gateway, rule_type, original_ip, original_port, translated_ip, translated_port, protocol ): """ return port that can be used in rule, if port have already used return new port that is next free port after current """ + ctx.target.instance.runtime_properties.setdefault(PORT_REPLACEMENT, {}) nat_rules = gateway.get_nat_rules() if isinstance( original_port, basestring) and original_port.lower() == 'any': @@ -319,30 +393,24 @@ def _get_original_port_for_create( ctx.logger.info( "For IP {} replace original port {} -> {}" .format(original_ip, original_port, port)) - if (PORT_REPLACEMENT not in - ctx.target.instance.runtime_properties): - ctx.target.instance.runtime_properties[ - PORT_REPLACEMENT] = {} + key = '{}:{}'.format(original_ip, original_port) ctx.target.instance.runtime_properties[ - PORT_REPLACEMENT][ - (original_ip, original_port)] = port + PORT_REPLACEMENT][key] = port return port raise cfy_exc.NonRecoverableError( "Can't create NAT rule because maximum port number was reached") -def _get_original_port_for_delete(original_ip, original_port): +def _get_original_port_for_delete(ctx, original_ip, original_port): """ check may be we already replaced port by some new free port """ - if PORT_REPLACEMENT in ctx.target.instance.runtime_properties: - runtime_properties = ctx.target.instance.runtime_properties - port = runtime_properties[PORT_REPLACEMENT].get( - (original_ip, original_port) - ) - return port if port else original_port - else: + runtime_properties = ctx.target.instance.runtime_properties + if PORT_REPLACEMENT not in runtime_properties: return original_port + key = '{}:{}'.format(original_ip, original_port) + port = runtime_properties[PORT_REPLACEMENT].get(key) + return port if port else original_port def _is_rule_exists(nat_rules, rule_type, @@ -353,17 +421,24 @@ def _is_rule_exists(nat_rules, rule_type, """ # gatewayNatRule properties may be None or string # convert to str, bacause port can be int - cicmp = lambda t: t[1] and (str(t[0]).lower() == str(t[1]).lower()) + def cicmp(t): + return t[1] and (str(t[0]).lower() == str(t[1]).lower()) + for natRule in nat_rules: gatewayNatRule = natRule.get_GatewayNatRule() if (all(map(cicmp, [ (rule_type, natRule.get_RuleType()), (original_ip, gatewayNatRule.get_OriginalIp()), - (str(original_port), gatewayNatRule.get_OriginalPort()), - (translated_ip, gatewayNatRule.get_TranslatedIp()), - (str(translated_port), gatewayNatRule.get_TranslatedPort()), - (protocol, gatewayNatRule.get_Protocol())]))): + (str(original_port), gatewayNatRule.get_OriginalPort())]))): break else: return False return True + + +def _is_snat(value): + return value.upper() == 'SNAT' + + +def _is_dnat(value): + return value.upper() == 'DNAT' diff --git a/network_plugin/security_group.py b/vcloud_network_plugin/security_group.py similarity index 66% rename from network_plugin/security_group.py rename to vcloud_network_plugin/security_group.py index dfb6b71..05dc834 100644 --- a/network_plugin/security_group.py +++ b/vcloud_network_plugin/security_group.py @@ -1,10 +1,25 @@ -from cloudify import ctx +# Copyright (c) 2015-2020 Cloudify Platform Ltd. All rights reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + from cloudify import exceptions as cfy_exc from cloudify.decorators import operation from vcloud_plugin_common import (with_vca_client, get_mandatory, + combine_properties, delete_properties, get_vcloud_config) -from network_plugin import (check_ip, get_vm_ip, save_gateway_configuration, - get_gateway, utils) +from vcloud_network_plugin import (check_ip, get_vm_ip, + save_gateway_configuration, get_gateway, + utils, set_retry, lock_gateway) CREATE_RULE = 1 @@ -14,37 +29,47 @@ ACTIONS = ("allow", "deny") -@operation +@operation(resumable=True) @with_vca_client -def create(vca_client, **kwargs): +@lock_gateway +def create(ctx, vca_client, **kwargs): """ create firewall rules for node """ - _rule_operation(CREATE_RULE, vca_client) + if not _rule_operation(ctx, CREATE_RULE, vca_client): + return set_retry(ctx) -@operation +@operation(resumable=True) @with_vca_client -def delete(vca_client, **kwargs): +@lock_gateway +def delete(ctx, vca_client, **kwargs): """ drop firewall rules for node """ - _rule_operation(DELETE_RULE, vca_client) + if not _rule_operation(ctx, DELETE_RULE, vca_client): + return set_retry(ctx) + delete_properties(ctx.target) -@operation +@operation(resumable=True) @with_vca_client -def creation_validation(vca_client, **kwargs): +def creation_validation(ctx, vca_client, **kwargs): """ validate firewall rules for node """ + # combine properties + obj = combine_properties( + ctx, kwargs=kwargs, names=['security_group'], + properties=['rules']) getaway = get_gateway( vca_client, _get_gateway_name(ctx.node.properties) ) if not getaway.is_fw_enabled(): raise cfy_exc.NonRecoverableError( "Gateway firewall is disabled. Please, enable firewall.") - rules = get_mandatory(ctx.node.properties, 'rules') + # get rules + rules = get_mandatory(obj, 'rules') for rule in rules: description = rule.get("description") if description and not isinstance(description, basestring): @@ -74,8 +99,9 @@ def creation_validation(vca_client, **kwargs): utils.check_protocol(rule.get('protocol')) action = get_mandatory(rule, "action") - if (not isinstance(action, basestring) - or action.lower() not in ACTIONS): + if ( + not isinstance(action, basestring) or action.lower() not in ACTIONS + ): raise cfy_exc.NonRecoverableError( "Action must be on of{0}.".format(ACTIONS)) @@ -85,13 +111,17 @@ def creation_validation(vca_client, **kwargs): "Parameter 'log_traffic' must be boolean.") -def _rule_operation(operation, vca_client): +def _rule_operation(ctx, operation, vca_client): """ create/delete firewall rules in gateway for current node """ - gateway = get_gateway( - vca_client, _get_gateway_name(ctx.target.node.properties)) - for rule in ctx.target.node.properties['rules']: + gateway_name = _get_gateway_name(ctx.target.node.properties) + gateway = get_gateway(vca_client, gateway_name) + # combine properties + obj = combine_properties( + ctx.target, names=['security_group'], properties=['rules'], + copy_back=False) + for rule in obj['rules']: description = rule.get('description', "Rule added by pyvcloud").strip() source_ip = rule.get("source", "external") if not _is_literal_ip(source_ip): @@ -121,9 +151,8 @@ def _rule_operation(operation, vca_client): ctx.logger.info( "Firewall rule has been deleted: {0}".format(description)) - if not save_gateway_configuration(gateway, vca_client): - return ctx.operation.retry(message='Waiting for gateway.', - retry_after=10) + ctx.logger.info("Saving security group configuration") + return save_gateway_configuration(gateway, vca_client, ctx) def _get_gateway_name(properties): diff --git a/network_plugin/utils.py b/vcloud_network_plugin/utils.py similarity index 86% rename from network_plugin/utils.py rename to vcloud_network_plugin/utils.py index 747f5cc..9151e13 100644 --- a/network_plugin/utils.py +++ b/vcloud_network_plugin/utils.py @@ -1,4 +1,4 @@ -# Copyright (c) 2015 GigaSpaces Technologies Ltd. All rights reserved +# Copyright (c) 2015-2020 Cloudify Platform Ltd. All rights reserved # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -8,9 +8,9 @@ # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, -# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# * See the License for the specific language governing permissions and -# * limitations under the License. +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. from cloudify import exceptions as cfy_exc diff --git a/vcloud_plugin_common/__init__.py b/vcloud_plugin_common/__init__.py index e493ad1..a22d9fd 100644 --- a/vcloud_plugin_common/__init__.py +++ b/vcloud_plugin_common/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved +# Copyright (c) 2014-2020 Cloudify Platform Ltd. All rights reserved # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -8,9 +8,9 @@ # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, -# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# * See the License for the specific language governing permissions and -# * limitations under the License. +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. import atexit from functools import wraps @@ -18,16 +18,20 @@ import os import requests import time +import collections from pyvcloud import vcloudair from pyvcloud.schema.vcd.v1_5.schemas.vcloud import taskType +from cloudify.context import ImmutableProperties from cloudify import ctx from cloudify import context from cloudify import exceptions as cfy_exc -TASK_RECHECK_TIMEOUT = 2 -RELOGIN_TIMEOUT = 3 + +TASK_RECHECK_TIMEOUT = 5 +RELOGIN_TIMEOUT = 5 +LOGIN_RETRY_NUM = 15 TASK_STATUS_SUCCESS = 'success' TASK_STATUS_ERROR = 'error' @@ -66,6 +70,12 @@ SUBSCRIPTION_SERVICE_TYPE = 'subscription' ONDEMAND_SERVICE_TYPE = 'ondemand' PRIVATE_SERVICE_TYPE = 'vcd' +SESSION_TOKEN = 'session_token' +ORG_URL = 'org_url' +VCLOUD_CONFIG = 'vcloud_config' + +local_session_token = None +local_org_url = None def transform_resource_name(res, ctx): @@ -117,6 +127,8 @@ def get(self): try: with open(config_path) as f: cfg = yaml.load(f.read()) + if not cfg: + cfg = {} except IOError: pass return cfg @@ -125,7 +137,6 @@ def get(self): class VcloudAirClient(object): config = Config - LOGIN_RETRY_NUM = 5 def get(self, config=None, *args, **kw): """ @@ -150,11 +161,16 @@ def connect(self, cfg): org_name = cfg.get('org') service_type = cfg.get('service_type', SUBSCRIPTION_SERVICE_TYPE) instance = cfg.get('instance') - org_url = cfg.get('org_url', None) + org_url = cfg.get(ORG_URL, None) + verify = cfg.get('ssl_verify', True) api_version = cfg.get('api_version', '5.6') - if not (all([url, token]) or all([url, username, password])): + session_token = cfg.get(SESSION_TOKEN) + org_url = cfg.get(ORG_URL) + if not (all([url, token]) or + all([url, username, password]) + or session_token): raise cfy_exc.NonRecoverableError( - "Login credentials must be specified") + "Login credentials must be specified.") if (service_type == SUBSCRIPTION_SERVICE_TYPE and not ( service and org_name )): @@ -163,81 +179,83 @@ def connect(self, cfg): if service_type == SUBSCRIPTION_SERVICE_TYPE: vcloud_air = self._subscription_login( - url, username, password, token, service, org_name) + url, username, password, token, service, org_name, + session_token, org_url) elif service_type == ONDEMAND_SERVICE_TYPE: vcloud_air = self._ondemand_login( - url, username, password, token, instance) + url, username, password, token, instance, + session_token, org_url) # The actual service type for private is 'vcd', but we should accept # 'private' as well, for user friendliness of inputs elif service_type in (PRIVATE_SERVICE_TYPE, 'private'): vcloud_air = self._private_login( - url, username, password, token, org_name, org_url, api_version) + url, username, password, token, org_name, org_url, + api_version, verify) else: raise cfy_exc.NonRecoverableError( "Unrecognized service type: {0}".format(service_type)) return vcloud_air def _subscription_login(self, url, username, password, token, service, - org_name): + org_name, session_token=None, org_url=None): """ login to subscription service """ + version = '5.6' logined = False vdc_logined = False - vca = vcloudair.VCA( url, username, service_type=SUBSCRIPTION_SERVICE_TYPE, - version='5.6') + version=version) + + if session_token: + vca = login_to_vca_with_token(vca, org_url, session_token, version) + if vca: + return vca + else: + raise cfy_exc.NonRecoverableError( + "Invalid session credentials") + + global local_org_url + global local_session_token + if local_session_token: + vca = login_to_vca_with_token(vca, local_org_url, + local_session_token, version) + if vca: + return vca # login with token if token: - for _ in range(self.LOGIN_RETRY_NUM): - logined = vca.login(token=token) - if logined is False: - ctx.logger.info("Login using token failed.") - time.sleep(RELOGIN_TIMEOUT) - continue - else: - ctx.logger.info("Login using token successful.") - break + logined = login_with_retry(vca.login, [None, token], + "Login using token") # outdated token, try login by password if logined is False and password: - for _ in range(self.LOGIN_RETRY_NUM): - logined = vca.login(password) - if logined is False: - ctx.logger.info("Login using password failed. Retrying...") - time.sleep(RELOGIN_TIMEOUT) - continue - else: - ctx.logger.info("Login using password successful.") - break + logined = login_with_retry(vca.login, [password, None], + "Login using token") # can't login to system at all if logined is False: raise cfy_exc.NonRecoverableError("Invalid login credentials") - for _ in range(self.LOGIN_RETRY_NUM): - vdc_logined = vca.login_to_org(service, org_name) - if vdc_logined is False: - ctx.logger.info("Login to VDC failed. Retrying...") - time.sleep(RELOGIN_TIMEOUT) - continue - else: - ctx.logger.info("Login to VDC successful.") - break + vdc_logined = login_with_retry(vca.login_to_org, [service, org_name], + "Login to org") # we can login to system, # but have some troubles with login to organization, # lets retry later - if vdc_logined is False: + if vdc_logined: + local_session_token = vca.vcloud_session.token + local_org_url = vca.vcloud_session.org_url + else: raise cfy_exc.RecoverableError(message="Could not login to VDC", retry_after=RELOGIN_TIMEOUT) atexit.register(vca.logout) return vca - def _ondemand_login(self, url, username, password, token, instance_id): + def _ondemand_login(self, url, username, password, token, instance_id, + session_token=None, org_url=None): """ login to ondemand service """ @@ -247,6 +265,7 @@ def get_instance(vca, instance_id): if instance['id'] == instance_id: return instance + version = '5.7' if instance_id is None: raise cfy_exc.NonRecoverableError( "Instance ID should be specified for OnDemand login") @@ -254,31 +273,32 @@ def get_instance(vca, instance_id): instance_logined = False vca = vcloudair.VCA( - url, username, service_type=ONDEMAND_SERVICE_TYPE, version='5.7') + url, username, service_type=ONDEMAND_SERVICE_TYPE, version=version) + if session_token: + vca = login_to_vca_with_token(vca, org_url, session_token, version) + if vca: + return vca + else: + raise cfy_exc.NonRecoverableError( + "Invalid session credentials") + + global local_org_url + global local_session_token + if local_session_token: + vca = login_to_vca_with_token(vca, local_org_url, + local_session_token, version) + if vca: + return vca # login with token if token: - for _ in range(self.LOGIN_RETRY_NUM): - logined = vca.login(token=token) - if logined is False: - ctx.logger.info("Login using token failed.") - time.sleep(RELOGIN_TIMEOUT) - continue - else: - ctx.logger.info("Login using token successful.") - break + logined = login_with_retry(vca.login, [None, token], + "Login using token") # outdated token, try login by password if logined is False and password: - for _ in range(self.LOGIN_RETRY_NUM): - logined = vca.login(password) - if logined is False: - ctx.logger.info("Login using password failed. Retrying...") - time.sleep(RELOGIN_TIMEOUT) - continue - else: - ctx.logger.info("Login using password successful.") - break + logined = login_with_retry(vca.login, [password, None], + "Login using password") # can't login to system at all if logined is False: @@ -289,36 +309,24 @@ def get_instance(vca, instance_id): raise cfy_exc.NonRecoverableError( "Instance {0} could not be found.".format(instance_id)) - for _ in range(self.LOGIN_RETRY_NUM): - instance_logined = vca.login_to_instance( - instance_id, password, token, None) - if instance_logined is False: - ctx.logger.info("Login to instance failed. Retrying...") - time.sleep(RELOGIN_TIMEOUT) - continue - else: - ctx.logger.info("Login to instance successful.") - break - - for _ in range(self.LOGIN_RETRY_NUM): - - instance_logined = vca.login_to_instance( - instance_id, - None, - vca.vcloud_session.token, - vca.vcloud_session.org_url) - if instance_logined is False: - ctx.logger.info("Login to instance failed. Retrying...") - time.sleep(RELOGIN_TIMEOUT) - continue - else: - ctx.logger.info("Login to instance successful.") - break + instance_logined = login_with_retry(vca.login_to_instance, + [instance_id, password], + "Login to instance with password") + + if instance_logined: + instance_logined = login_with_retry(vca.login_to_instance, + [instance_id, None, + vca.vcloud_session.token, + vca.vcloud_session.org_url], + "Login to instance with token") # we can login to system, # but have some troubles with login to instance, # lets retry later - if instance_logined is False: + if instance_logined: + local_session_token = vca.vcloud_session.token + local_org_url = vca.vcloud_session.org_url + else: raise cfy_exc.RecoverableError( message="Could not login to instance", retry_after=RELOGIN_TIMEOUT) @@ -327,7 +335,7 @@ def get_instance(vca, instance_id): return vca def _private_login(self, url, username, password, token, org_name, - org_url=None, api_version='5.6'): + org_url=None, api_version='5.6', verify=True): """ login to private instance """ @@ -337,10 +345,11 @@ def _private_login(self, url, username, password, token, org_name, host=url, username=username, service_type=PRIVATE_SERVICE_TYPE, - version=api_version) + version=api_version, + verify=verify) if logined is False and password: - for _ in range(self.LOGIN_RETRY_NUM): + for _ in xrange(LOGIN_RETRY_NUM): logined = vca.login(password, org=org_name) if logined is False: ctx.logger.info("Login using password failed. Retrying...") @@ -358,7 +367,7 @@ def _private_login(self, url, username, password, token, org_name, # Private mode requires being logged in with a token otherwise you # don't seem to be able to retrieve any VDCs if token: - for _ in range(self.LOGIN_RETRY_NUM): + for _ in xrange(LOGIN_RETRY_NUM): logined = vca.login(token=token, org_url=org_url) if logined is False: ctx.logger.info("Login using token failed.") @@ -375,6 +384,24 @@ def _private_login(self, url, username, password, token, org_name, return vca +def _update_nested(d, u): + for k, v in u.iteritems(): + if isinstance(v, collections.Mapping): + r = _update_nested(d.get(k, {}), v) + d[k] = r + else: + d[k] = u[k] + return d + + +def _update_static_properties(node, kw, element): + if element in kw: + node._node = node._endpoint.get_node(node.id) + props = node._node.get('properties', {}) + _update_nested(props, kw[element]) + node._node['properties'] = ImmutableProperties(props) + + def with_vca_client(f): """ add vca client to function params @@ -382,12 +409,25 @@ def with_vca_client(f): @wraps(f) def wrapper(*args, **kw): config = None + prop = None if ctx.type == context.NODE_INSTANCE: - config = ctx.node.properties.get('vcloud_config') + _update_static_properties(ctx.node, kw, 'properties') + config = ctx.node.properties.get(VCLOUD_CONFIG) + prop = ctx.instance.runtime_properties elif ctx.type == context.RELATIONSHIP_INSTANCE: - config = ctx.source.node.properties.get('vcloud_config') + _update_static_properties(ctx.source.node, kw, 'source') + _update_static_properties(ctx.target.node, kw, 'target') + config = ctx.source.node.properties.get(VCLOUD_CONFIG) + if config: + prop = ctx.source.instance.runtime_properties + else: + config = ctx.target.node.properties.get(VCLOUD_CONFIG) + prop = ctx.target.instance.runtime_properties else: raise cfy_exc.NonRecoverableError("Unsupported context") + if config and prop: + config[SESSION_TOKEN] = prop.get(SESSION_TOKEN) + config[ORG_URL] = prop.get(ORG_URL) client = VcloudAirClient().get(config=config) kw['vca_client'] = client return f(*args, **kw) @@ -398,21 +438,34 @@ def wait_for_task(vca_client, task): """ check status of current task and make request for recheck task status in case when we have not well defined state - (not error and not success) + (not error and not success or by timeout) """ + WAIT_TIME_MAX_MINUTES = 30 + MAX_ATTEMPTS = WAIT_TIME_MAX_MINUTES * 60 / TASK_RECHECK_TIMEOUT + ctx.logger.debug('Maximun task wait time {0} minutes.' + .format(WAIT_TIME_MAX_MINUTES)) + ctx.logger.debug('Task recheck after {0} seconds.' + .format(TASK_RECHECK_TIMEOUT)) status = task.get_status() - while status != TASK_STATUS_SUCCESS: + config = get_vcloud_config() + for attempt in xrange(MAX_ATTEMPTS): + ctx.logger.debug('Attempt: {0}/{1}.'.format(attempt + 1, MAX_ATTEMPTS)) + if status == TASK_STATUS_SUCCESS: + ctx.logger.debug('Task completed in {0} seconds' + .format(attempt * TASK_RECHECK_TIMEOUT)) + return if status == TASK_STATUS_ERROR: error = task.get_Error() raise cfy_exc.NonRecoverableError( "Error during task execution: {0}".format(error.get_message())) - else: - time.sleep(TASK_RECHECK_TIMEOUT) - response = requests.get( - task.get_href(), - headers=vca_client.vcloud_session.get_vcloud_headers()) - task = taskType.parseString(response.content, True) - status = task.get_status() + time.sleep(TASK_RECHECK_TIMEOUT) + response = requests.get( + task.get_href(), + headers=vca_client.vcloud_session.get_vcloud_headers(), + verify=config.get('ssl_verify', True)) + task = taskType.parseString(response.content, True) + status = task.get_status() + raise cfy_exc.NonRecoverableError("Wait for task timeout.") def get_vcloud_config(): @@ -421,9 +474,11 @@ def get_vcloud_config(): """ config = None if ctx.type == context.NODE_INSTANCE: - config = ctx.node.properties.get('vcloud_config') + config = ctx.node.properties.get(VCLOUD_CONFIG) elif ctx.type == context.RELATIONSHIP_INSTANCE: - config = ctx.source.node.properties.get('vcloud_config') + config = ctx.source.node.properties.get(VCLOUD_CONFIG) + if not config: + config = ctx.target.node.properties.get(VCLOUD_CONFIG) else: raise cfy_exc.NonRecoverableError("Unsupported context") static_config = Config().get() @@ -456,3 +511,99 @@ def is_ondemand(service_type): check service type is ondemand """ return service_type == ONDEMAND_SERVICE_TYPE + + +def error_response(obj): + """ + return description of response error + """ + try: + return obj.response.content + except AttributeError: + return '' + + +def session_login(vca, org_url, session_token, version): + vcs = vcloudair.VCS(org_url, None, None, None, org_url, org_url, version) + for _ in xrange(LOGIN_RETRY_NUM): + if not vcs.login(token=session_token): + ctx.logger.info("Login using session token failed.") + time.sleep(RELOGIN_TIMEOUT) + continue + else: + vca.vcloud_session = vcs + ctx.logger.info("Login using session token successful.") + return True + return False + + +def login_to_vca_with_token(vca, org_url, session_token, version): + for _ in xrange(LOGIN_RETRY_NUM): + logined = session_login(vca, org_url, session_token, version) + if logined is False: + ctx.logger.info("Login using session token failed.") + time.sleep(RELOGIN_TIMEOUT) + continue + else: + return vca + + +def login_with_retry(function, arguments, message): + for _ in xrange(LOGIN_RETRY_NUM): + logined = function(*arguments) + if logined is False: + ctx.logger.info("{0} failed. Retrying...".format(message)) + time.sleep(RELOGIN_TIMEOUT) + continue + else: + ctx.logger.info("{0} successful.".format(message)) + return True + return False + + +def delete_properties(ctx): + # cleanup runtime properties + # need to convert generaton to list, python 3 + keys = [key for key in ctx.instance.runtime_properties.keys()] + for key in keys: + del ctx.instance.runtime_properties[key] + + +def combine_properties(ctx, kwargs=None, names=None, properties=None, + copy_back=True): + """combine properties + runtime properties + kwargs""" + if not kwargs: + kwargs = {} + if not properties: + properties = [] + # add default properties names (uncombined things) + properties += ["use_external_resource", "resource_id"] + obj = {} + # use node properties as base + obj.update(ctx.node.properties) + if names: + # update base properties with runtime properties + for name in names: + prop_value = obj.get(name, {}) + prop_value.update(ctx.instance.runtime_properties.get(name, {})) + obj[name] = prop_value + # update base properties with kwargs + for name in names: + prop_value = obj.get(name, {}) + prop_value.update(kwargs.get(name, {})) + obj[name] = prop_value + # combine by priority + for name in ["use_external_resource", "resource_id"]: + obj[name] = kwargs.get( + name, + ctx.instance.runtime_properties.get( + name, + ctx.node.properties.get(name) + ) + ) + if copy_back: + # update runtime properties back + for name in obj: + if "vcloud_config" != name: + ctx.instance.runtime_properties[name] = obj[name] + return obj diff --git a/server_plugin/__init__.py b/vcloud_server_plugin/__init__.py similarity index 100% rename from server_plugin/__init__.py rename to vcloud_server_plugin/__init__.py diff --git a/vcloud_server_plugin/server.py b/vcloud_server_plugin/server.py new file mode 100644 index 0000000..376282f --- /dev/null +++ b/vcloud_server_plugin/server.py @@ -0,0 +1,810 @@ +# Copyright (c) 2014-2020 Cloudify Platform Ltd. All rights reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import time + +from cloudify.decorators import operation +from cloudify import exceptions as cfy_exc + +from vcloud_plugin_common import (get_vcloud_config, + transform_resource_name, + wait_for_task, + with_vca_client, + error_response, + combine_properties, + delete_properties, + STATUS_POWERED_ON) +from vcloud_network_plugin import (get_network_name, get_network, + is_network_exists, + get_vapp_name, GATEWAY_TIMEOUT, RETRY_COUNT) +from vcloud_network_plugin.keypair import PUBLIC_KEY, SSH_KEY + +VCLOUD_VAPP_NAME = 'vcloud_vapp_name' +GUEST_CUSTOMIZATION = 'guest_customization' +HARDWARE = 'hardware' +DEFAULT_EXECUTOR = "/bin/bash" +DEFAULT_USER = "ubuntu" +DEFAULT_HOME = "/home" + + +@operation(resumable=True) +@with_vca_client +def creation_validation(ctx, vca_client, **kwargs): + """ + validate server settings, look to template in catalog + """ + def get_catalog(catalog_name): + catalogs = vca_client.get_catalogs() + for catalog in catalogs: + if catalog.get_name() == catalog_name: + return catalog + + def get_template(catalog, template_name): + for template in catalog.get_CatalogItems().get_CatalogItem(): + if template.get_name() == template_name: + return template + + # combine properties + obj = combine_properties( + ctx, kwargs=kwargs, names=['server'], + properties=[VCLOUD_VAPP_NAME, 'management_network']) + # get external + if obj.get('use_external_resource'): + if not obj.get('resource_id'): + raise cfy_exc.NonRecoverableError( + "resource_id server properties must be specified" + ) + return + + server_dict = obj['server'] + required_params = ('catalog', 'template') + missed_params = set(required_params) - set(server_dict.keys()) + if len(missed_params) > 0: + raise cfy_exc.NonRecoverableError( + "{0} server properties must be specified" + .format(list(missed_params))) + + catalog = get_catalog(server_dict['catalog']) + if catalog is None: + raise cfy_exc.NonRecoverableError( + "Catalog '{0}' could not be found".format(server_dict['catalog'])) + + template = get_template(catalog, server_dict['template']) + if template is None: + raise cfy_exc.NonRecoverableError( + "Template '{0}' could not be found". + format(server_dict['template'])) + + +@operation(resumable=True) +@with_vca_client +def create(ctx, vca_client, **kwargs): + """ + create server by template, + if external_resource set return without creation, + e.g.: + { + 'management_network': '_management_network', + 'server': { + 'template': 'template', + 'catalog': 'catalog', + 'guest_customization': { + 'pre_script': 'pre_script', + 'post_script': 'post_script', + 'admin_password': 'pass', + 'computer_name': 'computer' + + } + } + } + """ + config = get_vcloud_config() + server = { + 'name': ctx.instance.id, + } + # combine properties + obj = combine_properties( + ctx, kwargs=kwargs, names=['server'], + properties=[VCLOUD_VAPP_NAME, 'management_network']) + server.update(obj.get('server', {})) + transform_resource_name(server, ctx) + # get external + if obj.get('use_external_resource'): + res_id = obj['resource_id'] + ctx.instance.runtime_properties[VCLOUD_VAPP_NAME] = res_id + vdc = vca_client.get_vdc(config['vdc']) + if not vca_client.get_vapp(vdc, res_id): + raise cfy_exc.NonRecoverableError( + "Unable to find external vAPP server resource {0}." + .format(res_id)) + server.update({'name': res_id}) + ctx.logger.info( + "External resource {0} has been used".format(res_id)) + else: + _create(ctx, vca_client, config, server) + + +def _create(ctx, vca_client, config, server): + """ + create server by template, + customize: + * hardware: memmory/cpu + * software: root password, computer internal hostname + connect vm to network + """ + vapp_name = server['name'] + vapp_template = server['template'] + vapp_catalog = server['catalog'] + connections = _create_connections_list(ctx, vca_client) + ctx.logger.info("Creating VApp with parameters: {0}".format(server)) + task = vca_client.create_vapp(config['vdc'], + vapp_name, + vapp_template, + vapp_catalog) + if not task: + raise cfy_exc.NonRecoverableError("Could not create vApp: {0}" + .format(error_response(vca_client))) + wait_for_task(vca_client, task) + + vdc = vca_client.get_vdc(config['vdc']) + vapp = vca_client.get_vapp(vdc, vapp_name) + if vapp is None: + raise cfy_exc.NonRecoverableError( + "vApp '{0}' could not be found".format(vapp_name)) + + task = vapp.modify_vm_name(1, vapp_name) + if not task: + raise cfy_exc.NonRecoverableError( + "Can't modyfy VM name: {0}".format(vapp_name)) + wait_for_task(vca_client, task) + ctx.logger.info("VM '{0}' has been renamed.".format(vapp_name)) + + # reread vapp + vapp = vca_client.get_vapp(vdc, vapp_name) + ctx.instance.runtime_properties[VCLOUD_VAPP_NAME] = vapp_name + + # we allways have connection to management_network_name + if connections: + for index, connection in enumerate(connections): + network_name = connection.get('network') + network = get_network(vca_client, network_name) + ctx.logger.info("Connect network '{0}' to server '{1}'." + .format(network_name, vapp_name)) + task = vapp.connect_to_network(network_name, network.get_href()) + if not task: + raise cfy_exc.NonRecoverableError( + "Could not add network {0} to VApp {1}. {2}" + .format(network_name, vapp_name, error_response(vapp))) + wait_for_task(vca_client, task) + + connections_primary_index = None + if connection.get('primary_interface'): + connections_primary_index = index + ip_address = connection.get('ip_address') + mac_address = connection.get('mac_address') + ip_allocation_mode = connection.get('ip_allocation_mode', + 'POOL').upper() + connection_args = { + 'network_name': network_name, + 'connection_index': index, + 'connections_primary_index': connections_primary_index, + 'ip_allocation_mode': ip_allocation_mode, + 'mac_address': mac_address, + 'ip_address': ip_address + } + ctx.logger.info("Connecting network with parameters {0}" + .format(str(connection_args))) + task = vapp.connect_vms(**connection_args) + if task is None: + raise cfy_exc.NonRecoverableError( + "Could not connect vApp {0} to network {1}. {2}" + .format(vapp_name, network_name, error_response(vapp))) + wait_for_task(vca_client, task) + + +def _power_on_vm(ctx, vca_client, vapp, vapp_name): + """Poweron VM""" + if _vapp_is_on(vapp) is False: + ctx.logger.info("Power-on VApp {0}".format(vapp_name)) + task = vapp.poweron() + if not task: + raise cfy_exc.NonRecoverableError( + "Could not power-on vApp. {0}". + format(error_response(vapp))) + wait_for_task(vca_client, task) + + +@operation(resumable=True) +@with_vca_client +def start(ctx, vca_client, **kwargs): + """ + power on server and wait network connection availability for host + """ + # combine properties + obj = combine_properties( + ctx, kwargs=kwargs, names=['server'], + properties=[VCLOUD_VAPP_NAME, 'management_network']) + # get external + if obj.get('use_external_resource'): + ctx.logger.info('not starting server since an external server is ' + 'being used') + else: + vapp_name = get_vapp_name(ctx.instance.runtime_properties) + config = get_vcloud_config() + vdc = vca_client.get_vdc(config['vdc']) + vapp = vca_client.get_vapp(vdc, vapp_name) + _power_on_vm(ctx, vca_client, vapp, vapp_name) + + if not _get_state(ctx=ctx, vca_client=vca_client): + return ctx.operation.retry( + message="Waiting for VM's configuration to complete", + retry_after=5) + + +@operation(resumable=True) +@with_vca_client +def stop(ctx, vca_client, **kwargs): + """ + poweroff server, if external resource - server stay poweroned + """ + # combine properties + obj = combine_properties( + ctx, kwargs=kwargs, names=['server'], + properties=[VCLOUD_VAPP_NAME, 'management_network']) + # get external + if obj.get('use_external_resource'): + ctx.logger.info('not stopping server since an external server is ' + 'being used') + else: + vapp_name = get_vapp_name(ctx.instance.runtime_properties) + config = get_vcloud_config() + vdc = vca_client.get_vdc(config['vdc']) + vapp = vca_client.get_vapp(vdc, vapp_name) + ctx.logger.info("Power-off and undeploy VApp {0}".format(vapp_name)) + task = vapp.undeploy() + if not task: + raise cfy_exc.NonRecoverableError("Could not undeploy vApp {0}". + format(error_response(vapp))) + wait_for_task(vca_client, task) + + +@operation(resumable=True) +@with_vca_client +def delete(ctx, vca_client, **kwargs): + """ + delete server + """ + # combine properties + obj = combine_properties( + ctx, kwargs=kwargs, names=['server'], + properties=[VCLOUD_VAPP_NAME, 'management_network']) + # get external + if obj.get('use_external_resource'): + ctx.logger.info('not deleting server since an external server is ' + 'being used') + else: + vapp_name = get_vapp_name(ctx.instance.runtime_properties) + config = get_vcloud_config() + vdc = vca_client.get_vdc(config['vdc']) + vapp = vca_client.get_vapp(vdc, vapp_name) + ctx.logger.info("Deleting VApp {0}".format(vapp_name)) + task = vapp.delete() + if not task: + raise cfy_exc.NonRecoverableError("Could not delete vApp {0}". + format(error_response(vapp))) + wait_for_task(vca_client, task) + + delete_properties(ctx) + + +def _is_primary_connection_has_ip(vapp): + """Return True in case when primary interface has some ip""" + network_info = vapp.get_vms_network_info() + # we dont have any network, skip checks + if not network_info: + return True + if not network_info[0]: + return True + # we have some networks + for conn in network_info[0]: + if conn['is_connected'] and conn['is_primary'] and conn['ip']: + return True + return False + + +@operation(resumable=True) +@with_vca_client +def configure(ctx, vca_client, **kwargs): + # combine properties + obj = combine_properties( + ctx, kwargs=kwargs, names=['server'], + properties=[VCLOUD_VAPP_NAME, 'management_network']) + # get external + if obj.get('use_external_resource'): + ctx.logger.info('Avoiding external resource configuration.') + else: + ctx.logger.info("Configure server") + server = {'name': ctx.instance.id} + server.update(ctx.node.properties.get('server', {})) + server.update(kwargs.get('server', {})) + ctx.logger.info("Server properties: {0}" + .format(str(server))) + vapp_name = server['name'] + config = get_vcloud_config() + custom = server.get(GUEST_CUSTOMIZATION, {}) + public_keys = _get_connected_keypairs(ctx) + + vdc = vca_client.get_vdc(config['vdc']) + vapp = vca_client.get_vapp(vdc, vapp_name) + if not vapp: + raise cfy_exc.NonRecoverableError( + "Unable to find vAPP server " + "by its name {0}.".format(vapp_name)) + ctx.logger.info("Using vAPP {0}".format(str(vapp_name))) + + hardware = server.get('hardware') + if hardware: + cpu = hardware.get('cpu') + memory = hardware.get('memory') + _check_hardware(cpu, memory) + if memory: + task = None + try: + ctx.logger.info( + "Customize VM memory: '{0}'.".format(memory) + ) + task = vapp.modify_vm_memory(vapp_name, memory) + wait_for_task(vca_client, task) + except Exception as e: + raise cfy_exc.NonRecoverableError( + "Customize VM memory failed: {task}. " + "Vapp: {app} Error: {e} ". + format(e=e, task=task, app=error_response(vapp))) + if cpu: + task = None + try: + ctx.logger.info( + "Customize VM cpu: '{0}'.".format(cpu) + ) + task = vapp.modify_vm_cpu(vapp_name, cpu) + wait_for_task(vca_client, task) + except Exception as e: + raise cfy_exc.NonRecoverableError( + "Customize VM cpu failed: {task}. " + "Vapp: {app} Error: {e} ". + format(e=e, task=task, app=error_response(vapp))) + if custom or public_keys: + script = _build_script(custom, public_keys) + password = custom.get('admin_password') + computer_name = custom.get('computer_name') + ctx.logger.info("Customizing guest OS.") + task = vapp.customize_guest_os( + vapp_name, + customization_script=script, + computer_name=computer_name, + admin_password=password + ) + ctx.logger.debug( + "VM {vapp_name} Customized with sript:\n{script}\n" + "computer_name:\n{computer_name}\n" + "password:\n{password}\n".format( + vapp_name=vapp_name, + script=script, + computer_name=computer_name, + password=password)) + if task is None: + raise cfy_exc.NonRecoverableError( + "Could not set guest customization parameters. {0}". + format(error_response(vapp))) + wait_for_task(vca_client, task) + if vapp.customize_on_next_poweron(): + ctx.logger.info("Customizations successful") + else: + customization_task = vapp.force_customization(vapp_name) + if customization_task: + ctx.logger.info("Customizations forced") + wait_for_task(vca_client, customization_task) + else: + raise cfy_exc.NonRecoverableError( + "Can't run customization in next power on. {0}". + format(error_response(vapp))) + + if not _is_primary_connection_has_ip(vapp): + ctx.logger.info("Power on server for get dhcp ip.") + # we have to start vapp before continue + _power_on_vm(ctx, vca_client, vapp, vapp_name) + for attempt in xrange(RETRY_COUNT): + vapp = vca_client.get_vapp(vdc, vapp_name) + if _is_primary_connection_has_ip(vapp): + return + ctx.logger.info( + "No ip assigned. Retrying... {}/{} attempt." + .format(attempt + 1, RETRY_COUNT) + ) + time.sleep(GATEWAY_TIMEOUT) + ctx.logger.info("We dont recieve ip, try next time...") + + +@operation(resumable=True) +@with_vca_client +def remove_keys(ctx, vca_client, **kwargs): + ctx.logger.info("Remove public keys from VM.") + relationships = getattr(ctx.target.instance, 'relationships', None) + if relationships: + public_keys = [ + relationship.target.instance.runtime_properties['public_key'] + for relationship in relationships + if 'public_key' in + relationship.target.instance.runtime_properties + ] + else: + return + vdc = vca_client.get_vdc(get_vcloud_config()['vdc']) + vapp_name = ctx.target.instance.id + vapp = vca_client.get_vapp(vdc, vapp_name) + if not vapp: + vapp_name = ctx.target.node.properties['server'].get('name', '') + vapp = vca_client.get_vapp(vdc, vapp_name) + if not vapp: + raise cfy_exc.NonRecoverableError( + "Unable to find vAPP server " + "by its name {0}.".format(vapp_name)) + ctx.logger.info("Using vAPP {0}".format(str(vapp_name))) + script = "#!/bin/sh\n" + _build_public_keys_script(public_keys, + _remove_key_script) + task = vapp.undeploy() + if not task: + raise cfy_exc.NonRecoverableError( + "Can't power off VM. {0}".format(vapp_name)) + wait_for_task(vca_client, task) + task = vapp.customize_guest_os( + vapp_name, + customization_script=script) + if not task: + raise cfy_exc.NonRecoverableError( + "Could not set guest customization parameters. {0}.". + format(error_response(vapp))) + wait_for_task(vca_client, task) + if vapp.customize_on_next_poweron(): + ctx.logger.info("Customizations successful.") + else: + customization_task = vapp.force_customization(vapp_name) + if customization_task: + ctx.logger.info("Customizations forced") + wait_for_task(vca_client, customization_task) + else: + raise cfy_exc.NonRecoverableError( + "Can't run customization on next power on. {0}.". + format(error_response(vapp))) + vapp = vca_client.get_vapp(vdc, vapp_name) + task = vapp.poweron() + if not task: + raise cfy_exc.NonRecoverableError( + "Can't poweron VM. {0}".format(vapp_name)) + wait_for_task(vca_client, task) + ctx.logger.info("Power on after deleting public key successful.") + + ctx.logger.info("Remove keys from properties.") + host_rt_properties = ctx.target.instance.runtime_properties + if SSH_KEY in host_rt_properties: + del host_rt_properties[SSH_KEY] + + +def _remove_key_script(commands, user, ssh_dir, keys_file, public_key): + sed_template = " sed -i /{0}/d {1}" + commands.append(sed_template.format( + public_key.split()[1].replace('/', '[/]'), keys_file) + ) + + +def _get_state(ctx, vca_client): + """ + check network connection availability for host + """ + vapp_name = get_vapp_name(ctx.instance.runtime_properties) + config = get_vcloud_config() + vdc = vca_client.get_vdc(config['vdc']) + vapp = vca_client.get_vapp(vdc, vapp_name) + + nw_connections = _get_vm_network_connections(vapp) + if len(nw_connections) == 0: + ctx.logger.info("No networks connected") + ctx.instance.runtime_properties['ip'] = None + ctx.instance.runtime_properties['networks'] = {} + return True + + if not all([connection['ip'] for connection in nw_connections]): + ctx.logger.info("Network configuration is not finished yet.") + return False + + ctx.instance.runtime_properties['networks'] = { + connection['network_name']: connection['ip'] + for connection in nw_connections} + + for connection in nw_connections: + if connection['is_primary']: + ctx.logger.info("Primary network ip address '{0}' for" + " network '{1}'." + .format(connection['ip'], + connection['network_name'])) + ctx.instance.runtime_properties['ip'] = connection['ip'] + return True + return False + + +def _vapp_is_on(vapp): + """ + server is on + """ + return vapp.me.get_status() == STATUS_POWERED_ON + + +def _get_vm_network_connections(vapp): + """ + get list connected networlks + """ + connections = vapp.get_vms_network_info()[0] + return filter(lambda network: network['is_connected'], connections) + + +def _get_vm_network_connection(vapp, network_name): + """ + return network connection by name + """ + connections = _get_vm_network_connections(vapp) + for connection in connections: + if connection['network_name'] == network_name: + return connection + + +def _build_script(custom, public_keys): + """ + create customization script + """ + pre_script = custom.get('pre_script', "") + post_script = custom.get('post_script', "") + if not pre_script and not post_script and not public_keys: + return None + script_executor = DEFAULT_EXECUTOR + public_keys_script = _build_public_keys_script(public_keys, + _add_key_script) + script_template = """#!{0} +echo performing customization tasks with param $1 \ +at `date "+DATE: %Y-%m-%d - TIME: %H:%M:%S"` >> /root/customization.log +if [ "$1" = "precustomization" ]; +then + echo performing precustomization tasks \ + on `date "+DATE: %Y-%m-%d - TIME: %H:%M:%S"` >> /root/customization.log + {1} + {2} +fi +if [ "$1" = "postcustomization" ]; +then + echo performing postcustomization tasks \ + at `date "+DATE: %Y-%m-%d - TIME: %H:%M:%S"` >> /root/customization.log + {3} +fi + """ + script = script_template.format(script_executor, public_keys_script, + pre_script, post_script) + return script + + +def _get_connected_keypairs(ctx): + """ + return public keys connected to node + """ + relationships = getattr(ctx.instance, 'relationships', None) + if relationships: + return [relationship.target.instance.runtime_properties[PUBLIC_KEY] + for relationship in relationships + if PUBLIC_KEY in + relationship.target.instance.runtime_properties] + else: + return [] + + +def _add_key_script(commands, user, ssh_dir, keys_file, public_key): + """ + return commands for inject public key to node + """ + add_key_template = "echo '{0}\n' >> {1}" + test_ssh_dir_template = """ + if [ ! -d {1} ];then + mkdir {1} + chown {0}:{0} {1} + chmod 700 {1} + touch {2} + chown {0}:{0} {2} + chmod 600 {2} + # make centos with selinux happy + which restorecon && restorecon -Rv {1} + fi + """ + test_ssh_dir = test_ssh_dir_template.format(user, ssh_dir, keys_file) + commands.append(test_ssh_dir) + commands.append(add_key_template.format(public_key, keys_file)) + + +def _build_public_keys_script(public_keys, script_function): + """ + create script for update ssh keys + """ + key_commands = [] + ssh_dir_template = "{0}/{1}/.ssh" + authorized_keys_template = "{0}/authorized_keys" + for key in public_keys: + public_key = key.get('key') + if not public_key: + continue + user = key.get('user') + if not user: + user = DEFAULT_USER + home = key.get('home') + if not home: + home = '' if user == 'root' else DEFAULT_HOME + ssh_dir = ssh_dir_template.format(home, user) + authorized_keys = authorized_keys_template.format(ssh_dir) + script_function( + key_commands, user, ssh_dir, authorized_keys, public_key + ) + return "\n".join(key_commands) + + +def _create_connections_list(ctx, vca_client): + """ + return full list connections for node + """ + connections = [] + ports = _get_connected(ctx.instance, 'port') + networks = _get_connected(ctx.instance, 'network') + + management_network_name = ctx.node.properties.get('management_network') + + for port in ports: + obj = combine_properties(port, names=['port'], copy_back=False) + port_properties = obj['port'] + connections.append( + _create_connection(port_properties['network'], + port_properties.get('ip_address'), + port_properties.get('mac_address'), + port_properties.get('ip_allocation_mode', + 'POOL').upper(), + port_properties.get('primary_interface', False), + port_properties.get('nic_order', 0)) + ) + + for net in networks: + obj = combine_properties(net, names=['network'], copy_back=False) + connections.append( + _create_connection(get_network_name(net.node.properties), + None, None, 'POOL')) + + if management_network_name and not any( + [conn['network'] == management_network_name + for conn in connections]): + connections.append(_create_connection(management_network_name, + None, None, 'POOL')) + + for conn in connections: + if not is_network_exists(vca_client, conn['network']): + raise cfy_exc.NonRecoverableError( + "Network '{0}' could not be found".format(conn['network'])) + + primary_iface_set = len(filter(lambda conn: conn.get('primary_interface', + False), + connections)) > 0 + if not primary_iface_set: + if management_network_name: + primary_name = management_network_name + elif connections: + primary_name = connections[0]['network'] + else: + raise cfy_exc.NonRecoverableError( + "Can't setup primary interface") + + # check list of connections and set managment network as primary + # in case when we dont have any primary networks + for conn in connections: + network_name = conn['network'] + if (conn['ip_allocation_mode'] == 'DHCP' and not _isDhcpAvailable( + vca_client, network_name + )): + ctx.logger.warning( + "DHCP for network {0} is not available" + .format(network_name)) + if not primary_iface_set: + conn['primary_interface'] = \ + (network_name == primary_name) + if conn['primary_interface']: + ctx.logger.info( + "The primary interface has been set to {}".format( + network_name)) + + return sorted(connections, key=lambda k: k['nic_order']) + + +def _get_connected(instance, prop): + """ + get property from instance relationships + """ + relationships = getattr(instance, 'relationships', None) + if relationships: + return [relationship.target for relationship in relationships + if prop in relationship.target.node.properties] + else: + return [] + + +def _create_connection(network, ip_address, mac_address, ip_allocation_mode, + primary_interface=False, nic_order=0): + """ + repack fields to dict + """ + return {'network': network, + 'ip_address': ip_address, + 'mac_address': mac_address, + 'ip_allocation_mode': ip_allocation_mode, + 'primary_interface': primary_interface, + 'nic_order': nic_order} + + +def _isDhcpAvailable(vca_client, network_name): + """ + check dhcp availability for network + """ + vdc_name = get_vcloud_config()['vdc'] + network = vca_client.get_network(vdc_name, network_name) + if network.get_Configuration().get_FenceMode() == "bridged": + # NOTE(nmishkin) Can't tell whether bridged networks have DHCP + # so just hope for the best + return True + # TODO: Why not just get the gateway directly from the network? + admin_href = vca_client.get_admin_network_href(vdc_name, network_name) + for gate in vca_client.get_gateways(vdc_name): + for pool in gate.get_dhcp_pools(): + if admin_href == pool.get_Network().get_href(): + return True + return False + + +def _check_hardware(cpu, memory): + """ + check hardware setting + 1 <= cpu <= 64 + 512M <= memmory <= 512G + """ + if cpu is not None: + if isinstance(cpu, int): + if cpu < 1: + raise cfy_exc.NonRecoverableError( + "Too small quantity of CPU's: {0}".format(cpu)) + if cpu > 64: + raise cfy_exc.NonRecoverableError( + "Too many of CPU's: {0}".format(cpu)) + else: + raise cfy_exc.NonRecoverableError( + "Quantity of CPU's must be integer") + + if memory is not None: + if isinstance(memory, int): + if memory < 512: + raise cfy_exc.NonRecoverableError( + "Too small quantity of memory: {0}".format(memory)) + if memory > (512 * 1024): # 512Gb + raise cfy_exc.NonRecoverableError( + "Too many memory: {0}".format(memory)) + else: + raise cfy_exc.NonRecoverableError( + "Quantity of memory must be integer") diff --git a/vcloud_server_plugin/vdc.py b/vcloud_server_plugin/vdc.py new file mode 100644 index 0000000..79d25e7 --- /dev/null +++ b/vcloud_server_plugin/vdc.py @@ -0,0 +1,127 @@ +# Copyright (c) 2014-2020 Cloudify Platform Ltd. All rights reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from cloudify.decorators import operation +from cloudify import exceptions as cfy_exc + +from vcloud_plugin_common import (get_vcloud_config, + wait_for_task, + with_vca_client, + is_subscription, + combine_properties, + delete_properties, + error_response) + +VDC_NAME = 'vdc_name' +RESOURCE_ID = 'resource_id' +USE_EXTERNAL_RESOURCE = 'use_external_resource' + + +@operation(resumable=True) +@with_vca_client +def creation_validation(ctx, vca_client, **kwargs): + """check params + + e.g.: + { + 'name': 'not_existed' + } + or: + { + 'use_external_resource': True, + 'resource_id': 'not_existed' + } + + """ + # combine properties + obj = combine_properties(ctx, kwargs=kwargs, properties=['name']) + # get external + if obj.get(USE_EXTERNAL_RESOURCE): + if not obj.get(RESOURCE_ID): + raise cfy_exc.NonRecoverableError( + "resource_id server properties must be specified") + res_id = obj[RESOURCE_ID] + vdc = vca_client.get_vdc(res_id) + if not vdc: + raise cfy_exc.NonRecoverableError( + "Unable to find external VDC {0}." + .format(res_id)) + else: + vdc_name = obj.get('name') + if not vdc_name: + raise cfy_exc.NonRecoverableError("'vdc_name' not specified.") + vdc = vca_client.get_vdc(vdc_name) + if vdc: + raise cfy_exc.NonRecoverableError( + "VDC '{0}' already exists." + .format(vdc_name)) + + +@operation(resumable=True) +@with_vca_client +def create(ctx, vca_client, **kwargs): + """create vdc""" + config = get_vcloud_config() + # Subscription service does not support vdc create, + # you must use predefined vdc only + if is_subscription(config['service_type']): + raise cfy_exc.NonRecoverableError( + "Unable create VDC on subscription service.") + # combine properties + obj = combine_properties(ctx, kwargs=kwargs, properties=['name']) + # get external + if obj.get(USE_EXTERNAL_RESOURCE): + # use external resource, does not create anything + res_id = obj[RESOURCE_ID] + ctx.instance.runtime_properties[VDC_NAME] = res_id + vdc = vca_client.get_vdc(res_id) + if not vdc: + raise cfy_exc.NonRecoverableError( + "Unable to find external VDC {0}." + .format(res_id)) + ctx.logger.info( + "External resource {0} has been used".format(res_id)) + else: + # create new vdc + vdc_name = obj.get('name') + if not vdc_name: + raise cfy_exc.NonRecoverableError("'vdc_name' not specified.") + task = vca_client.create_vdc(vdc_name) + if not task: + raise cfy_exc.NonRecoverableError( + "Could not create VDC: {0}".format(error_response(vca_client))) + wait_for_task(vca_client, task) + + +@operation(resumable=True) +@with_vca_client +def delete(ctx, vca_client, **kwargs): + """delete vdc""" + # combine properties + obj = combine_properties(ctx, kwargs=kwargs, properties=['name']) + # get external + # external resource - no actions + if obj.get(USE_EXTERNAL_RESOURCE): + ctx.logger.info('Not deleting VDC since an external VDC is ' + 'being used') + else: + # created in our workflow + vdc_name = obj.get('name') + status, task = vca_client.delete_vdc(vdc_name) + if not status: + raise cfy_exc.NonRecoverableError( + "Could not delete VDC: {0}".format(error_response(vca_client))) + wait_for_task(vca_client, task) + # clean up runtime_properties + delete_properties(ctx) diff --git a/vcloud_storage_plugin/__init__.py b/vcloud_storage_plugin/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/vcloud_storage_plugin/storage.py b/vcloud_storage_plugin/storage.py new file mode 100644 index 0000000..6e31366 --- /dev/null +++ b/vcloud_storage_plugin/storage.py @@ -0,0 +1,14 @@ +######### +# Copyright (c) 2014-2020 Cloudify Platform Ltd. All rights reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/vcloud_storage_plugin/volume.py b/vcloud_storage_plugin/volume.py new file mode 100644 index 0000000..75e18ea --- /dev/null +++ b/vcloud_storage_plugin/volume.py @@ -0,0 +1,207 @@ +# Copyright (c) 2015-2020 Cloudify Platform Ltd. All rights reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import time + +from cloudify import exceptions as cfy_exc +from cloudify.decorators import operation +from vcloud_plugin_common import (wait_for_task, with_vca_client, + get_vcloud_config, get_mandatory, + combine_properties, delete_properties, + error_response) +from vcloud_network_plugin import get_vapp_name, SSH_PUBLIC_IP, SSH_PORT + + +@operation(resumable=True) +@with_vca_client +def create_volume(ctx, vca_client, **kwargs): + """ + create new volume, e.g.: + { + 'use_external_resource': False, + 'volume': { + 'name': 'some-other', + 'size': 11 + } + } + """ + # combine properties + obj = combine_properties(ctx, kwargs=kwargs, names=['volume'], + properties=['device_name']) + # get external + if obj.get('use_external_resource'): + ctx.logger.info("External resource has been used") + return + vdc_name = get_vcloud_config()['vdc'] + name = obj['volume']['name'] + size = obj['volume']['size'] + size_in_bytes = size * 1024 * 1024 + ctx.logger.info("Create volume '{0}' to '{1}' with size {2}Mb." + .format(name, vdc_name, size)) + success, disk = vca_client.add_disk(vdc_name, name, size_in_bytes) + if success: + wait_for_task(vca_client, disk.get_Tasks()[0]) + ctx.logger.info("Volume node '{0}' has been created".format(name)) + else: + raise cfy_exc.NonRecoverableError( + "Disk creation error: {0}".format(disk)) + + +@operation(resumable=True) +@with_vca_client +def delete_volume(ctx, vca_client, **kwargs): + """ + drop volume + """ + # combine properties + obj = combine_properties(ctx, kwargs=kwargs, names=['volume'], + properties=['device_name']) + # get external + if obj.get('use_external_resource'): + ctx.logger.info("External resource has been used") + return + vdc_name = get_vcloud_config()['vdc'] + name = obj['volume']['name'] + ctx.logger.info("Delete volume '{0}' from '{1}'." + .format(name, vdc_name)) + success, task = vca_client.delete_disk(vdc_name, name) + if success: + wait_for_task(vca_client, task) + ctx.logger.info("Volume node '{0}' has been deleted".format(name)) + else: + raise cfy_exc.NonRecoverableError( + "Disk deletion error: {0}".format(task)) + delete_properties(ctx) + + +@operation(resumable=True) +@with_vca_client +def creation_validation(ctx, vca_client, **kwargs): + """ + check volume description + """ + vdc_name = get_vcloud_config()['vdc'] + disks_names = [ + disk.name for [disk, _vms] in vca_client.get_disks(vdc_name) + ] + # combine properties + obj = combine_properties(ctx, kwargs=kwargs, names=['volume'], + properties=['device_name']) + # get external resource flag + if obj.get('use_external_resource'): + # get resource_id + resource_id = get_mandatory(obj, 'resource_id') + if resource_id not in disks_names: + raise cfy_exc.NonRecoverableError( + "Disk {} does't exists".format(resource_id)) + else: + # get volume + volume = get_mandatory(obj, 'volume') + name = get_mandatory(volume, 'name') + if name in disks_names: + raise cfy_exc.NonRecoverableError( + "Disk {} already exists".format(name)) + get_mandatory(volume, 'size') + + +@operation(resumable=True) +@with_vca_client +def attach_volume(ctx, vca_client, **kwargs): + """attach volume""" + _wait_for_boot(ctx) + _volume_operation(ctx, vca_client, "ATTACH") + + +@operation(resumable=True) +@with_vca_client +def detach_volume(ctx, vca_client, **kwargs): + """ + detach volume + """ + _volume_operation(ctx, vca_client, "DETACH") + + +def _volume_operation(ctx, vca_client, operation): + """ + attach/detach volume + """ + vdc_name = get_vcloud_config()['vdc'] + vdc = vca_client.get_vdc(vdc_name) + vmName = get_vapp_name(ctx.target.instance.runtime_properties) + if ctx.source.node.properties.get('use_external_resource'): + volumeName = ctx.source.node.properties['resource_id'] + else: + volumeName = ctx.source.node.properties['volume']['name'] + vapp = vca_client.get_vapp(vdc, vmName) + for ref in vca_client.get_diskRefs(vdc): + if ref.name == volumeName: + if operation == 'ATTACH': + ctx.logger.info("Attach volume node '{0}'." + .format(volumeName)) + task = vapp.attach_disk_to_vm(vmName, ref) + if task: + wait_for_task(vca_client, task) + ctx.logger.info( + "Volume node '{0}' has been attached" + .format(volumeName)) + else: + raise cfy_exc.NonRecoverableError( + "Can't attach disk: '{0}' with error: {1}". + format(volumeName, error_response(vapp))) + + elif operation == 'DETACH': + ctx.logger.info("Detach volume node '{0}'.".format(volumeName)) + task = vapp.detach_disk_from_vm(vmName, ref) + if task: + wait_for_task(vca_client, task) + ctx.logger.info( + "Volume node '{0}' has been detached.". + format(volumeName)) + else: + raise cfy_exc.NonRecoverableError( + "Can't detach disk: '{0}'. With error: {1}". + format(volumeName, error_response(vapp))) + else: + raise cfy_exc.NonRecoverableError( + "Unknown operation '{0}'".format(operation)) + + +def _wait_for_boot(ctx): + """ + Whait for loading os. + This function just check if sshd is available. + After attaching disk system may be unbootable, + therefore user can do some manipulation for setup boot sequence. + """ + from fabric import api as fabric_api + ip = ctx.target.instance.runtime_properties.get(SSH_PUBLIC_IP) + if not ip: + # private ip will be used in case + # when we does not have public ip + ip = ctx.target.instance.runtime_properties['ip'] + port = ctx.target.instance.runtime_properties.get(SSH_PORT, 22) + ctx.logger.info("Using ip '{0}'.".format(ip)) + for i in range(30): + ctx.logger.info("Wait for boot '{0}'.".format(i)) + try: + with fabric_api.settings( + host_string=ip, port=port, warn_only=True, + abort_on_prompts=True + ): + fabric_api.run('id') + time.sleep(5) + except SystemExit: + return + except Exception: + pass + raise cfy_exc.NonRecoverableError("Can't wait for boot")