diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..f93ad88 Binary files /dev/null and b/.DS_Store differ diff --git a/.circleci/config.yml b/.circleci/config.yml index 72a4d5d..e55c2f1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -38,10 +38,10 @@ commands: command: pip install --user tox - run: name: Run Flake8 - command: /home/circleci/.local/bin/tox -e code_quality + command: /home/circleci/.local/bin/tox -e flake8 - run: name: Run nosetest - command: /home/circleci/.local/bin/tox -e unit_tests + command: /home/circleci/.local/bin/tox -e pytest check_py3_compat: steps: @@ -402,10 +402,11 @@ workflows: - unittests_py36 - validate_version - validate_documentation - - rhel_wagon: - filters: - branches: - only: /([0-9\.]*\-build|main|dev)/ + - rhel_wagon +# - rhel_wagon: +# filters: +# branches: +# only: /([0-9\.]*\-build|main|dev)/ # - wagon: # filters: # branches: diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 00d5f15..951d89d 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -2,3 +2,7 @@ - New version. 2.0.1: - Attempt to decrease amount of superfluous API requests, however these calls are almost all inside of the client library. +2.0.2: + - Support some legacy types. +2.0.3: + - Fixed issue with RETRY BAD REQUEST in preconfigure_nic diff --git a/cloudify_vcd/decorators.py b/cloudify_vcd/decorators.py index 1c67c20..69e8980 100644 --- a/cloudify_vcd/decorators.py +++ b/cloudify_vcd/decorators.py @@ -1,6 +1,5 @@ from pyvcloud.vcd.exceptions import ( - AccessForbiddenException, InternalServerException, EntityNotFoundException, diff --git a/cloudify_vcd/disk_tasks.py b/cloudify_vcd/disk_tasks.py index 2ecbc4e..3681425 100644 --- a/cloudify_vcd/disk_tasks.py +++ b/cloudify_vcd/disk_tasks.py @@ -108,7 +108,7 @@ def detach_disk(_, disk_config, disk_ctx.instance.runtime_properties.get('tasks')) if not vapp_name: - ctx.logger.warn('No vapp was found to detach disk {n}.'.format( + ctx.logger.debug('No vapp was found to detach disk {n}.'.format( n=disk_id)) last_task = None else: diff --git a/cloudify_vcd/legacy/__init__.py b/cloudify_vcd/legacy/__init__.py new file mode 100644 index 0000000..95c6c3e --- /dev/null +++ b/cloudify_vcd/legacy/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) 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/cloudify_vcd/legacy/compute/__init__.py b/cloudify_vcd/legacy/compute/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cloudify_vcd/legacy/compute/tasks.py b/cloudify_vcd/legacy/compute/tasks.py new file mode 100644 index 0000000..8e78e1d --- /dev/null +++ b/cloudify_vcd/legacy/compute/tasks.py @@ -0,0 +1,257 @@ +# Copyright (c) 2014-21 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 copy import deepcopy + +from cloudify.exceptions import OperationRetry + +from vcd_plugin_sdk.resources.vapp import VCloudVM, VCloudvApp +from cloudify_common_sdk.utils import ( + get_ctx_instance, + skip_creative_or_destructive_operation as skip) + +from .. import decorators +from ... import vapp_tasks +from ..utils import VM_NIC_REL +from ...utils import ( + expose_props, + get_last_task, + find_rels_by_type, + check_if_task_successful) + + +@decorators.with_vcd_client() +@decorators.with_vm_resource() +def create_server(vm_client, ctx, **_): + if not skip(type(vm_client), + vm_client.name, + ctx, + exists=vm_client.exists, + create_operation=True): + vm_kwargs = deepcopy(vm_client.vapp_object.kwargs) + if 'network' in vm_kwargs: + del vm_kwargs['network'] + del vm_kwargs['network_adapter_type'] + return vapp_tasks._create_vm( + vm_external=False, + vm_id=vm_client.name, + vm_client=vm_client.connection, + vm_vdc=vm_client.vdc_name, + vm_config=vm_kwargs, + vm_class=VCloudVM, + vm_ctx=ctx) + return vm_client.vm, None + + +@decorators.with_vcd_client() +@decorators.with_vm_resource() +def configure_server(vm_client, ctx, **_): + resource, result = vapp_tasks._configure_vm( + vm_external=False, + vm_id=vm_client.name, + vm_client=vm_client.connection, + vm_vdc=vm_client.vdc_name, + vm_config=vm_client.kwargs, + vm_class=VCloudVM, + vm_ctx=ctx) + # operation_name = ctx.operation.name.split('.')[-1] + # expose_props(operation_name, + # resource, + # _ctx=ctx, + # legacy=True) + + +@decorators.with_vcd_client() +@decorators.with_vm_resource() +def power_off_vapp(vm_client, ctx, **_): + if not skip(type(vm_client.vapp_object), + vm_client.vapp_object.name, + ctx, + exists=vm_client.vapp_object.exists, + delete_operation=True): + return vapp_tasks._power_off_vapp( + vapp_ext=False, + vapp_id=vm_client.name, + vapp_client=vm_client.vapp_object.connection, + vapp_vdc=vm_client.vdc_name, + vapp_config=vm_client.kwargs, + vapp_class=VCloudvApp, + __=ctx) + + +@decorators.with_vcd_client() +@decorators.with_vm_resource() +def stop_vapp(vm_client, ctx, **_): + if not skip(type(vm_client.vapp_object), + vm_client.vapp_object.name, + ctx, + exists=vm_client.vapp_object.exists, + delete_operation=True): + return vapp_tasks._stop_vapp( + vapp_ext=False, + vapp_id=vm_client.name, + vapp_client=vm_client.vapp_object.connection, + vapp_vdc=vm_client.vdc_name, + vapp_config=vm_client.kwargs, + vapp_class=VCloudvApp, + __=ctx) + + +@decorators.with_vcd_client() +@decorators.with_vm_resource() +def delete_vapp(vm_client, ctx, **_): + if not skip(type(vm_client.vapp_object), + vm_client.vapp_object.name, + ctx, + exists=vm_client.vapp_object.exists, + delete_operation=True): + return vapp_tasks._delete_vapp( + vapp_ext=False, + vapp_id=vm_client.name, + vapp_client=vm_client.vapp_object.connection, + vapp_vdc=vm_client.vdc_name, + vapp_config=vm_client.kwargs, + vapp_class=VCloudvApp, + __=ctx) + + +@decorators.with_vcd_client() +@decorators.with_vm_resource() +def start_server(vm_client, ctx, **_): + return vapp_tasks._start_vm( + vm_external=False, + vm_id=vm_client.name, + vm_client=vm_client.connection, + vm_vdc=vm_client.vdc_name, + vm_config=vm_client.kwargs, + vm_class=VCloudVM, + vm_ctx=ctx) + + +@decorators.with_vcd_client() +@decorators.with_vm_resource() +def stop_server(vm_client, ctx, **_): + return vapp_tasks._stop_vm( + vm_external=False, + vm_id=vm_client.name, + vm_client=vm_client.connection, + vm_vdc=vm_client.vdc_name, + vm_config=vm_client.kwargs, + vm_class=VCloudVM, + vm_ctx=ctx) + + +@decorators.with_vcd_client() +@decorators.with_vm_resource() +def delete_server(vm_client, ctx, **_): + vm_id = vm_client.name + if not vm_id: + vm_id = ctx.node.properties.get('server', {}).get('name') + if not skip(type(vm_client), + vm_id, + exists=vm_client.exists, + delete_operation=True): + return vapp_tasks._delete_vm( + vm_external=False, + vm_id=vm_id, + vm_client=vm_client.connection, + vm_vdc=vm_client.vdc_name, + vm_config=vm_client.kwargs, + vm_class=VCloudVM, + vm_ctx=ctx) + return vm_client.vm, None + + +@decorators.with_port_resource() +def port_creation_validation(*_, **__): + pass + + +@decorators.with_vcd_client() +@decorators.with_vm_resource() +def preconfigure_nic(vm_client, ctx, server, **kwargs): + for port_ctx in find_rels_by_type(get_ctx_instance(), VM_NIC_REL): + resource, result = vapp_tasks._add_network( + nic_config=port_ctx.target.instance.runtime_properties['port'], + nic_ctx=port_ctx.target, + vm_id=vm_client.name, + vm_client=vm_client.connection, + vm_vdc=vm_client.vdc_name, + vm_config=server, + vm_class=VCloudVM, + vm_ctx=ctx, + **kwargs) + last_task = get_last_task(result) + if not check_if_task_successful(resource, last_task): + raise OperationRetry('Pending for operation completion.') + operation_name = ctx.operation.name.split('.')[-1] + expose_props(operation_name, + resource, + _ctx=port_ctx.target, + legacy=True) + + +@decorators.with_vcd_client() +@decorators.with_vm_resource() +def postconfigure_nic(vm_client, server, ctx, **kwargs): + vm_id = vm_client.name + if not vm_id: + vm_id = ctx.node.properties.get('server', {}).get('name') + ctx.logger.info('Preconfigure vm client name {}'.format(vm_id)) + ctx.logger.info('Preconfigure server {}'.format(server)) + for port_ctx in find_rels_by_type(get_ctx_instance(), VM_NIC_REL): + # port = convert_nic_config( + # port_ctx.target.instance.runtime_properties['port']) + resource, result = vapp_tasks._add_nic( + nic_config=port_ctx.target.instance.runtime_properties['port'], + nic_ctx=port_ctx.target, + vm_id=vm_id, + vm_client=vm_client.connection, + vm_vdc=vm_client.vdc_name, + vm_config=server, + vm_class=VCloudVM, + vm_ctx=ctx, + **kwargs) + last_task = get_last_task(result) + if not check_if_task_successful(resource, last_task): + raise OperationRetry('Pending for add nic operation completion...') + operation_name = ctx.operation.name.split('.')[-1] + expose_props(operation_name, + resource, + _ctx=port_ctx.target, + legacy=True) + + +@decorators.with_vcd_client() +@decorators.with_vm_resource() +def unlink_nic(vm_client, ctx, **kwargs): + for port_ctx in find_rels_by_type(get_ctx_instance(), VM_NIC_REL): + resource, result = vapp_tasks._delete_nic( + nic_config=port_ctx.target.instance.runtime_properties['port'], + nic_ctx=port_ctx.target, + vm_id=vm_client.name, + vm_client=vm_client.connection, + vm_vdc=vm_client.vdc_name, + vm_config=vm_client.kwargs, + vm_class=VCloudVM, + vm_ctx=ctx, + **kwargs) + last_task = get_last_task(result) + if not check_if_task_successful(resource, last_task): + raise OperationRetry('Pending for unlink operation completion...') + operation_name = ctx.operation.name.split('.')[-1] + expose_props(operation_name, + resource, + _ctx=port_ctx.target, + legacy=True) diff --git a/cloudify_vcd/legacy/decorators.py b/cloudify_vcd/legacy/decorators.py new file mode 100644 index 0000000..ce8f4d4 --- /dev/null +++ b/cloudify_vcd/legacy/decorators.py @@ -0,0 +1,127 @@ +# Copyright (c) 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 functools import wraps + +from cloudify.exceptions import OperationRetry +from cloudify_common_sdk.utils import get_ctx_node, get_ctx_instance + +from . import utils +from ..utils import (get_last_task, check_if_task_successful) + + +def with_vcd_client(): + def wrapper_outer(func): + @wraps(func) + def wrapper_inner(*args, **kwargs): + """ + Initializes the connection object from vcloud_config. + :param args: + :param kwargs: From vcloud_config property. + :return: + """ + ctx = kwargs.get('ctx') + _ctx_node = get_ctx_node() + if 'vcloud_config' not in kwargs: + kwargs['vcloud_config'] = _ctx_node.properties['vcloud_config'] + kwargs['vcloud_cx'] = utils.get_vcloud_cx( + kwargs['vcloud_config'], ctx.logger) + resource, result = utils.get_function_return(func(*args, **kwargs)) + last_task = get_last_task(result) + ctx_instance = get_ctx_instance(ctx) + if not check_if_task_successful(resource, last_task): + ctx_instance.runtime_properties['__RETRY_BAD_REQUEST'] = True + raise OperationRetry('Pending for operation completion.') + return wrapper_inner + return wrapper_outer + + +def with_network_resource(): + def wrapper_outer(func): + @wraps(func) + def wrapper_inner(*args, **kwargs): + """ + Initializes the network object with connection and the translated + configuration from network property. + :param args: + :param kwargs: + :return: + """ + _ctx_node = get_ctx_node() + if 'network' not in kwargs: + kwargs['network'] = _ctx_node.properties['network'] + client = utils.get_network_client(**kwargs) + kwargs['network_client'] = client + return func(*args, **kwargs) + return wrapper_inner + return wrapper_outer + + +def with_gateway_resource(): + def wrapper_outer(func): + @wraps(func) + def wrapper_inner(*args, **kwargs): + """ + Initializes the gateway object with connection and the translated + configuration from network property. + :param args: + :param kwargs: + :return: + """ + client = utils.get_gateway_client(**kwargs) + kwargs['gateway_client'] = client + return func(*args, **kwargs) + return wrapper_inner + return wrapper_outer + + +def with_vm_resource(): + def wrapper_outer(func): + @wraps(func) + def wrapper_inner(*args, **kwargs): + """ + Initializes the gateway object with connection and the translated + configuration from vm property. + :param args: + :param kwargs: + :return: + """ + _ctx_node = get_ctx_node() + if 'server' not in kwargs: + kwargs['server'] = _ctx_node.properties['server'] + client = utils.get_vm_client(**kwargs) + kwargs['vm_client'] = client + return func(*args, **kwargs) + return wrapper_inner + return wrapper_outer + + +def with_port_resource(): + def wrapper_outer(func): + @wraps(func) + def wrapper_inner(*args, **kwargs): + """ + Initializes the gateway object with connection and the translated + configuration from vm property. + :param args: + :param kwargs: + :return: + """ + _ctx_node = get_ctx_node() + if 'port' not in kwargs: + kwargs['port'] = _ctx_node.properties['port'] + utils.get_port_config(**kwargs) + return func(*args, **kwargs) + return wrapper_inner + return wrapper_outer diff --git a/cloudify_vcd/legacy/network/__init__.py b/cloudify_vcd/legacy/network/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cloudify_vcd/legacy/network/tasks.py b/cloudify_vcd/legacy/network/tasks.py new file mode 100644 index 0000000..a207a11 --- /dev/null +++ b/cloudify_vcd/legacy/network/tasks.py @@ -0,0 +1,82 @@ +# Copyright (c) 2014-21 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.exceptions import NonRecoverableError + +from vcd_plugin_sdk.resources.network import VCloudNetwork +from cloudify_common_sdk.utils import \ + skip_creative_or_destructive_operation as skip + +from .. import decorators +from ... import network_tasks +from ...utils import expose_props + + +class MissingGateway(NonRecoverableError): + pass + + +@decorators.with_vcd_client() +@decorators.with_network_resource() +@decorators.with_gateway_resource() +def create_network(network_client, gateway_client, ctx, **_): + if network_client.network_type == 'routed_vdc_network' and \ + not gateway_client.gateway: + raise MissingGateway( + 'The provided gateway {} does not exist.'.format( + gateway_client.name)) + if not skip(network_client.network_type, + network_client.name, + ctx, + exists=network_client.exists, + create_operation=True): + resource, result = network_tasks._create_network( + external_network=False, + network_id=network_client.name, + network_client=network_client.connection, + network_vdc=network_client.vdc_name, + network_config=network_client.kwargs, + network_class=VCloudNetwork, + ctx=ctx) + else: + resource = network_client + operation_name = ctx.operation.name.split('.')[-1] + expose_props(operation_name, + resource, + _ctx=ctx) + + +@decorators.with_vcd_client() +@decorators.with_network_resource() +def delete_network(network_client, ctx, **_): + if not skip(network_client.network_type, + network_client.name, + ctx, + exists=network_client.exists, + delete_operation=True): + resource, result = network_tasks._delete_network( + external_network=False, + network_id=network_client.name, + network_client=network_client.connection, + network_vdc=network_client.vdc_name, + network_config=network_client.kwargs, + network_class=VCloudNetwork, + ctx=ctx) + else: + resource = network_client + operation_name = ctx.operation.name.split('.')[-1] + expose_props(operation_name, + resource, + _ctx=ctx) diff --git a/cloudify_vcd/legacy/tests/__init__.py b/cloudify_vcd/legacy/tests/__init__.py new file mode 100644 index 0000000..30b4f24 --- /dev/null +++ b/cloudify_vcd/legacy/tests/__init__.py @@ -0,0 +1,99 @@ +# Copyright (c) 2014-21 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.manager import DirtyTrackingDict +from cloudify.mocks import MockNodeContext, MockCloudifyContext + + +class CorrectedMockNodeContext(MockNodeContext): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.type_hierarchy = self.type + + +class CorrectedMockCloudifyContext(MockCloudifyContext): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + node_name = kwargs.get('node_name') + properties = kwargs.get('properties') + node_type = kwargs.get('node_type') + self._node = CorrectedMockNodeContext( + node_name, properties, node_type) + self.type_hierarchy = self.type + + +DEFAULT_NODE_PROPS = { + 'use_external_resource': False, + 'resource_id': '', + 'vcloud_config': { + 'username': 'taco', + 'password': 'secret', + 'token': None, + 'url': 'protocol://subdomain.domain.com/endpoint/version/resource', + 'instance': None, + 'vdc': 'vdc', + 'org': 'org', + 'service_type': None, + 'service': None, + 'api_version': '1.0', + 'org_url': None, + 'edge_gateway': None, + 'ssl_verify': True, + } +} + + +def create_ctx(node_id, + node_type, + node_properties, + runtime_props=None, + operation_name=None, + relationships=None): + """ + Create a Mock Context. + + :param node_id: + :param node_type: + :param node_properties: + :param runtime_props: + :param operation_name: + :param relationships: + :return: + """ + + runtime_props = runtime_props or {} + type_hierarchy = ['cloudify.nodes.Root'] + type_hierarchy.extend(node_type) + operation_name = operation_name or 'cloudify.interfaces.lifecycle.create' + operation = { + 'name': operation_name, + 'retry': 0, + } + tenant = { + 'name': 'footen', + } + mock_ctx = CorrectedMockCloudifyContext( + node_id=node_id, + node_name=node_id, + node_type=node_type, + blueprint_id='fooblu', + deployment_id='foodep', + properties=node_properties, + runtime_properties=DirtyTrackingDict(runtime_props), + relationships=relationships, + operation=operation, + tenant=tenant, + ) + return mock_ctx diff --git a/cloudify_vcd/legacy/utils.py b/cloudify_vcd/legacy/utils.py new file mode 100644 index 0000000..0d22969 --- /dev/null +++ b/cloudify_vcd/legacy/utils.py @@ -0,0 +1,421 @@ +# Copyright (c) 2014-21 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 ipaddress +from copy import deepcopy +from tempfile import NamedTemporaryFile + +from ..utils import ( + find_rels_by_type, + find_resource_id_from_relationship_by_type) + +from vcd_plugin_sdk.resources.vapp import VCloudVM +from vcd_plugin_sdk.connection import VCloudConnect +from vcd_plugin_sdk.resources.network import VCloudNetwork, VCloudGateway + +from cloudify import ctx as ctx_from_import +from cloudify.exceptions import NonRecoverableError +from cloudify_common_sdk.utils import ( + get_ctx_node, + get_ctx_instance, + get_deployment_dir +) + + +OLD_NETWORK_KEYS = [ + 'dns', + 'name', + 'dhcp', + 'netmask', + 'gateway_ip', + 'dns_suffix', + 'static_range', + 'edge_gateway' +] +OLD_PORT_KEYS = [ + 'network', + 'mac_address', + 'primary_interface', + 'ip_allocation_mode' +] +OLD_VM_KEYS = [ + 'name', + 'hardware', + 'guest_customization', +] +PORT_NET_REL = 'cloudify.vcloud.port_connected_to_network' +VM_NIC_REL = 'cloudify.vcloud.server_connected_to_port' + + +class RequiredClientKeyMissing(NonRecoverableError): + def __init__(self, key, *args, **kwargs): + msg = 'Required vcloud config key "{}" not provided.'.format(key) + super().__init__(msg, *args, **kwargs) + + +def get_function_return(func_ret): + if isinstance(func_ret, tuple) and len(func_ret) == 2: + return func_ret + return None, None + + +def get_vcloud_cx(client_config, logger): + client_config = deepcopy(client_config) + for bad, good in [('username', 'user'), + ('ssl_verify', 'verify_ssl_certs')]: + if bad in client_config: + logger.warning( + 'The vcloud_config contains the key "{}". ' + 'This is an invalid key. The correct key is "{}".'.format( + good, bad)) + client_config[good] = client_config.pop(bad) + + for key in ['user', 'password', 'org']: + if key not in client_config: + raise RequiredClientKeyMissing(key) + + credentials = { + 'org': client_config.pop('org'), + 'user': client_config.pop('user'), + 'password': client_config.pop('password') + } + new_client_config = {'uri': client_config.pop('url')} + + if 'api_version' in client_config: + new_client_config['api_version'] = client_config.pop('api_version') + + if 'verify_ssl_certs' in client_config: + new_client_config['verify_ssl_certs'] = client_config.pop( + 'verify_ssl_certs') + + if 'log_file' not in new_client_config: + new_temp = NamedTemporaryFile( + dir=get_deployment_dir(ctx_from_import.deployment.id)) + new_client_config['log_file'] = new_temp.name + + # TODO: Figure out what to do with the rest of the stuff in client_config. + return VCloudConnect(logger, new_client_config, credentials) + + +def get_network_client(network, vcloud_cx, vcloud_config, ctx, **_): + """ + Take the network configuration from legacy node type and convert it to + something that the vcd_plugin_sdk objects can understand. + + :param network: from ctx.node.properties['network'] or + operation inputs['network'] + :param vcloud_cx: VCloudConnect from with_vcloud_client decorator. + :param vcloud_config: from ctx.node.properties['vcloud_config'] or + operation inputs['vcloud_config'] + :param ctx: + :param kwargs: + :return: + """ + + _ctx_node = get_ctx_node(ctx) + _ctx_instance = get_ctx_instance(ctx) + + network_type = 'directly_connected_vdc_network' + + # if 'edge_gateway' in vcloud_config: + # network['gateway_name'] = vcloud_config['edge_gateway'] + # network_type = 'routed_vdc_network' + # else: + # network_type = 'isolated_vdc_network' + + tasks = _ctx_instance.runtime_properties.get('__TASKS', []) + if 'name' in network: + network_name = network.pop('name') + elif 'resource_id' in _ctx_instance.runtime_properties: + network_name = _ctx_instance.runtime_properties['resource_id'] + else: + network_name = _ctx_node.properties.get( + 'resource_id', _ctx_instance.id) + if network_type == 'directly_connected_vdc_network': + network = { + 'parent_network_name': network.get('parent_network_name', None) + } + network = convert_routed_network_config(network) + + _ctx_instance.runtime_properties['resource_id'] = network_name + _ctx_instance.runtime_properties['network'] = network + + new_network_config = { + 'network_name': network_name, + 'network_type': network_type, + 'connection': vcloud_cx, + 'vdc_name': vcloud_config.get('vdc'), + 'kwargs': network, + 'tasks': tasks + } + return VCloudNetwork(**new_network_config) + + +def get_port_config(port, ctx, **kwargs): + """ + :param port: + :param vcloud_cx: + :param vcloud_config: + :param ctx: + :param kwargs: + :return: + """ + + _node_instance = get_ctx_instance(ctx) + + if 'network' in port: + network = port.pop('network') + else: + network = find_resource_id_from_relationship_by_type( + _node_instance, PORT_NET_REL) + + port = convert_port_config(port) + if 'network_name' not in port: + port['network_name'] = network + if 'is_connected' not in port: + port['is_connected'] = True + _node_instance.runtime_properties['network'] = network + _node_instance.runtime_properties['port'] = port + + +def get_gateway_client(vcloud_cx, vcloud_config, ctx, **_): + _ctx_instance = get_ctx_instance(ctx) + tasks = _ctx_instance.runtime_properties.get('__TASKS', []) + + if 'edge_gateway' in vcloud_config: + return VCloudGateway( + vcloud_config['edge_gateway'], + connection=vcloud_cx, + vdc_name=vcloud_config.get('vdc'), + tasks=tasks + ) + + +def get_vm_client(server, vcloud_cx, vcloud_config, ctx): + ctx.logger.info('We are getting this server: {}'.format(server)) + _ctx_node = get_ctx_node(ctx) + _ctx_instance = get_ctx_instance(ctx) + name = None + if 'name' in server: + name = server.get('name') + if not name and 'name' in _ctx_instance.runtime_properties: + name = _ctx_instance.runtime_properties['name'] + if not name: + server_from_props = _ctx_node.properties.get('server') + name = server_from_props.get('name') + if not name: + name = _ctx_node.properties.get('resource_id', _ctx_instance.id) + if not name and 'resource_id' in _ctx_instance.runtime_properties: + name = _ctx_instance.runtime_properties['resource_id'] + _ctx_instance.runtime_properties['name'] = name + tasks = _ctx_instance.runtime_properties.get('__TASKS', []) + convert_vm_config(server) + get_server_network(server, _ctx_node, _ctx_instance) + if name: + _ctx_instance.runtime_properties['resource_id'] = name + _ctx_instance.runtime_properties['server'] = server + ctx.logger.info('We are getting this name: {}'.format(name)) + ctx.logger.info('We are getting this server: {}'.format(server)) + # TODO: Change vcloud VM name to host name guest customization pizazz. + return VCloudVM(name, + name, + connection=vcloud_cx, + vdc_name=vcloud_config.get('vdc'), + kwargs={}, + vapp_kwargs=server, + tasks=tasks) + + +def get_server_network(server, _ctx_node, _ctx_instance): + rel = None + server_network_adapter = 'VMXNET3' + + if 'network' not in server and \ + 'management_network_name' in _ctx_node.properties: + server['network'] = _ctx_node.properties['management_network_name'] + elif 'network' not in server: + for rel in find_rels_by_type(_ctx_instance, VM_NIC_REL): + if rel.target.node.properties['port'].get('primary_interface'): + break + if rel: + server['network'] = rel.target.instance.runtime_properties.get( + 'network_name') + + if 'network_adapter_type' not in server and rel: + server['network_adapter_type'] = \ + rel.target.instance.runtime_properties['port'].get( + 'adapter_type', server_network_adapter) + elif 'network_adapter_type' not in server: + server['network_adapter_type'] = server_network_adapter + + if 'ip_address' not in server and rel: + ip_address = rel.target.instance.runtime_properties['port'].get( + 'ip_address') + if ip_address: + server['ip_address'] = ip_address + + +def convert_routed_network_config(config): + + if 'network_cidr' not in config: + cidr = get_network_cidr(config) + if cidr: + config['network_cidr'] = cidr + + if 'ip_range_start' not in config or 'ip_range_end' not in config: + ip_range_start, ip_range_end = get_ip_range(config) + if ip_range_start: + config['ip_range_start'] = ip_range_start.compressed + if ip_range_end: + config['ip_range_end'] = ip_range_end.compressed + + if 'dns' in config: + primary_ip, secondary_ip = get_dns_ips(config['dns']) + config['primary_dns_ip'] = primary_ip + config['secondary_dns_ip'] = secondary_ip + + for key in OLD_NETWORK_KEYS: + config.pop(key, None) + + return config + + +def get_start_end_ip_config(config=None): + start = None + end = None + if config: + start, end = config.split('-') + start = ipaddress.IPv4Address(start) + end = ipaddress.IPv4Address(end) + return start, end + + +def get_gateway_ip(config): + gateway_ip = config.get('gateway_ip') + if gateway_ip: + return ipaddress.IPv4Address(gateway_ip) + + +def get_ip_range(config): + start_static, end_static = get_start_end_ip_config( + config.get('static_range')) + start_dhcp, end_dhcp = get_start_end_ip_config(config.get('dhcp_range')) + start_list = sorted( + [n for n in [start_static, start_dhcp] if n]) + if start_list: + start = start_list[-1] + else: + return None, None + end_list = sorted([n for n in [end_static, end_dhcp] if n]) + if end_list: + end = end_list[-1] + else: + return None, None + return start, end + + +def get_network_cidr(config): + """ + A naive way to generate a CIDR from old style network configuration. + :param config: + :return: + """ + start, end = get_ip_range(config) + gateway_ip = get_gateway_ip(config) + netmask = config.get('netmask') + if netmask: + netmask = ipaddress.IPv4Address('0.0.0.0/{}'.format(netmask)) + if gateway_ip: + start = gateway_ip + ctx_from_import.logger.info( + 'Using these IPs for CIDR: {} {}'.format(start, end)) + if not start or not end: + return None + ip_range = [addr for addr in ipaddress.summarize_address_range(start, end)] + if len(ip_range) >= 1: + if netmask: + return '{}/{}'.format( + ip_range[0].network_address, netmask.prefixlen) + return ip_range[0].compressed + + +def get_dns_ips(ips): + if len(ips) > 1: + return ips[0], ips[1] + return ips[0], None + + +def convert_port_config(config): + """Convert something like this: + port: + network: { get_input: RSP_VRS2_APP_EXT_PROXY_network_name } + ip_allocation_mode: manual + ip_address: { get_input: RSP_VRS2_APP_EXT_PROXY } + primary_interface: false + To this: + adapter_type: 'VMXNET3' + is_primary: false + is_connected: false + ip_address_mode: 'MANUAL' + ip_address: '192.179.2.2' + :param config: + :return: + """ + + if 'ip_allocation_mode' in config: + config['ip_address_mode'] = config.pop('ip_allocation_mode').upper() + + if 'primary_interface' in config: + config['is_primary'] = config.pop('primary_interface') + + if 'adapter_type' not in config: + config['adapter_type'] = 'VMXNET3' + + for key in OLD_PORT_KEYS: + config.pop(key, None) + + return config + + +def convert_vapp_config(config): + return { + 'fence_mode': config.get('fence_mode', 'direct'), + 'accept_all_eulas': config.get('accept_all_eulas', True) + } + + +def convert_vm_config(config): + if 'name' in config: + del config['name'] + if 'power_on' not in config: + config['power_on'] = False + if 'deploy' not in config: + config['deploy'] = False + if 'hardware' in config: + if 'memory' in config['hardware']: + config['memory'] = config['hardware']['memory'] + if 'cpus' in config['hardware']: + config['cpus'] = config['hardware']['cpu'] + if 'guest_customization' in config: + if 'admin_password' in config['guest_customization']: + config['password'] = \ + config['guest_customization']['admin_password'] + if 'computer_name' in config: + config['hostname'] = \ + config['guest_customization']['computer_name'] + + config.update(convert_vapp_config(config)) + + for key in OLD_VM_KEYS: + config.pop(key, None) diff --git a/cloudify_vcd/network_tasks.py b/cloudify_vcd/network_tasks.py index a7da8a6..2f71229 100644 --- a/cloudify_vcd/network_tasks.py +++ b/cloudify_vcd/network_tasks.py @@ -5,6 +5,7 @@ REL_NETWORK_GW = 'cloudify.relationships.vcloud.network_connected_to_gateway' NETWORK_TYPES = { + 'cloudify.vcloud.nodes.Network': 'directly_connected_vdc_network', 'cloudify.nodes.vcloud.RoutedVDCNetwork': 'routed_vdc_network', 'cloudify.nodes.vcloud.DirectlyConnectedVDCNetwork': 'directly_connected_vdc_network', @@ -12,31 +13,39 @@ } -def get_network_type(types): +def get_network_type(types, config=None): for node_type in types: network_type = NETWORK_TYPES.get(node_type) if network_type: return network_type + config = config or {} + if 'gateway_name' in config: + return 'routed_vdc_network' @resource_operation -def create_network(external_network, - network_id, - network_client, - network_vdc, - network_config, - network_class, - ctx, - **__): +def create_network(*args, **kwargs): + return _create_network(*args, **kwargs) + + +def _create_network(external_network, + network_id, + network_client, + network_vdc, + network_config, + network_class, + ctx, + **__): network = find_resource_id_from_relationship_by_type( ctx.instance, REL_NETWORK_GW) if network and 'network_name' not in network_config: network_config['network_name'] = network + network_type = get_network_type(ctx.node.type_hierarchy, network_config) network = network_class( network_id, - get_network_type(ctx.node.type_hierarchy), + network_type, network_client, network_vdc, kwargs=network_config) @@ -47,17 +56,24 @@ def create_network(external_network, @resource_operation -def delete_network(external_network, - network_id, - network_client, - network_vdc, - network_config, - network_class, - ctx, - **___): +def delete_network(*args, **kwargs): + return _delete_network(*args, **kwargs) + + +def _delete_network(external_network, + network_id, + network_client, + network_vdc, + network_config, + network_class, + ctx, + **___): + + network_type = get_network_type(ctx.node.type_hierarchy, network_config) + network = network_class( network_id, - get_network_type(ctx.node.type_hierarchy), + network_type, network_client, network_vdc, kwargs=network_config) diff --git a/cloudify_vcd/tests/test_decorator.py b/cloudify_vcd/tests/test_decorator.py index c5e0e74..bf1c932 100644 --- a/cloudify_vcd/tests/test_decorator.py +++ b/cloudify_vcd/tests/test_decorator.py @@ -24,12 +24,13 @@ def test_external_resource_exists(*_, **__): :return: """ operation = {'name': 'create', 'retry_number': 0} - _ctx = get_mock_node_instance_context(properties={ + _ctx = get_mock_node_instance_context( + properties={ 'use_external_resource': True, 'resource_id': 'foo', 'resource_config': {'foo': 'bar'}, 'client_config': {'foo': 'bar', 'vdc': 'vdc'}}, - operation=operation) + operation=operation) _task = E.Task( status='foo', serviceNamespace='bar', @@ -55,7 +56,8 @@ def test_external_resource_not_exists(*_, **__): :return: """ operation = {'name': 'foo', 'retry_number': 1} - _ctx = get_mock_node_instance_context(properties={ + _ctx = get_mock_node_instance_context( + properties={ 'use_external_resource': True, 'resource_id': 'foo', 'resource_config': {'foo': 'bar'}, @@ -82,7 +84,8 @@ def test_external_resource_not_exists_create_op(*_, **__): :return: """ operation = {'name': 'create', 'retry_number': 1} - _ctx = get_mock_node_instance_context(properties={ + _ctx = get_mock_node_instance_context( + properties={ 'use_external_resource': True, 'resource_id': 'foo', 'resource_config': {'foo': 'bar'}, @@ -111,7 +114,8 @@ def test_implicit_external_resource_bad_request(*_, **__): :return: """ operation = {'name': 'foo', 'retry_number': 1} - _ctx = get_mock_node_instance_context(properties={ + _ctx = get_mock_node_instance_context( + properties={ 'use_external_resource': False, 'resource_id': 'foo', 'resource_config': {'foo': 'bar'}, @@ -148,12 +152,13 @@ def test_new_resource(*_, **__): :return: """ operation = {'name': 'create', 'retry_number': 0} - _ctx = get_mock_node_instance_context(properties={ + _ctx = get_mock_node_instance_context( + properties={ 'use_external_resource': False, 'resource_id': 'foo', 'resource_config': {'foo': 'bar'}, 'client_config': {'foo': 'bar', 'vdc': 'vdc'}}, - operation=operation) + operation=operation) _task = E.Task( status='foo', serviceNamespace='bar', @@ -180,11 +185,13 @@ def test_new_resource_access_forbidden(*_, **__): :return: """ operation = {'name': 'foo', 'retry_number': 1} - _ctx = get_mock_node_instance_context(properties={ + _ctx = get_mock_node_instance_context( + properties={ 'use_external_resource': False, 'resource_id': 'foo', 'resource_config': {'foo': 'bar'}, - 'client_config': {'foo': 'bar', 'vdc': 'vdc'}}, + 'client_config': {'foo': 'bar', 'vdc': 'vdc'} + }, operation=operation ) @@ -207,11 +214,13 @@ def test_new_resource_bad_request_handled(*_, **__): :param __: :return: """ - _ctx = get_mock_node_instance_context(properties={ + _ctx = get_mock_node_instance_context( + properties={ 'use_external_resource': False, 'resource_id': 'foo', 'resource_config': {'foo': 'bar'}, - 'client_config': {'foo': 'bar', 'vdc': 'vdc'}}, + 'client_config': {'foo': 'bar', 'vdc': 'vdc'} + }, ) @resource_operation @@ -241,11 +250,13 @@ def test_new_resource_not_found(*_, **__): :param __: :return: """ - _ctx = get_mock_node_instance_context(properties={ + _ctx = get_mock_node_instance_context( + properties={ 'use_external_resource': False, 'resource_id': 'foo', 'resource_config': {'foo': 'bar'}, - 'client_config': {'foo': 'bar', 'vdc': 'vdc'}}, + 'client_config': {'foo': 'bar', 'vdc': 'vdc'} + }, ) @resource_operation diff --git a/cloudify_vcd/tests/test_tasks.py b/cloudify_vcd/tests/test_tasks.py index 03490d0..2dc5b12 100644 --- a/cloudify_vcd/tests/test_tasks.py +++ b/cloudify_vcd/tests/test_tasks.py @@ -67,12 +67,13 @@ return_value=True) def test_configure_gateway(*_, **__): operation = {'name': 'create', 'retry_number': 0} - _ctx = get_mock_node_instance_context(properties={ + _ctx = get_mock_node_instance_context( + properties={ 'use_external_resource': False, 'resource_id': 'foo', 'resource_config': {'foo': 'bar'}, 'client_config': {'foo': 'bar', 'vdc': 'vdc'}}, - operation=operation) + operation=operation) _ctx.node.type_hierarchy = ['cloudify.nodes.Root', 'cloudify.nodes.vcloud.Gateway'] configure_gateway(ctx=_ctx) @@ -86,12 +87,13 @@ def test_configure_gateway(*_, **__): return_value=True) def test_delete_gateway(*_, **__): operation = {'name': 'delete', 'retry_number': 0} - _ctx = get_mock_node_instance_context(properties={ + _ctx = get_mock_node_instance_context( + properties={ 'use_external_resource': False, 'resource_id': 'foo', 'resource_config': {'foo': 'bar'}, 'client_config': {'foo': 'bar', 'vdc': 'vdc'}}, - operation=operation) + operation=operation) _ctx.node.type_hierarchy = ['cloudify.nodes.Root', 'cloudify.nodes.vcloud.Gateway'] delete_gateway(ctx=_ctx) @@ -516,12 +518,13 @@ def test_delete_nat_rules(*_, **__): return_value=True) def test_create_disk(*_, **__): operation = {'name': 'create', 'retry_number': 0} - _ctx = get_mock_node_instance_context(properties={ + _ctx = get_mock_node_instance_context( + properties={ 'use_external_resource': False, 'resource_id': 'foo', 'resource_config': {'size': 1, 'description': 'foo'}, 'client_config': {'size': 'bar', 'vdc': 'vdc'}}, - operation=operation) + operation=operation) _ctx.node.type_hierarchy = ['cloudify.nodes.Root', 'cloudify.nodes.vcloud.Disk'] with mock.patch('vcd_plugin_sdk.resources.base.VDC') as vdc: @@ -538,12 +541,13 @@ def test_create_disk(*_, **__): return_value=True) def test_delete_disk(*_, **__): operation = {'name': 'delete', 'retry_number': 0} - _ctx = get_mock_node_instance_context(properties={ + _ctx = get_mock_node_instance_context( + properties={ 'use_external_resource': False, 'resource_id': 'foo', 'resource_config': {'size': 1, 'description': 'foo'}, 'client_config': {'size': 'bar', 'vdc': 'vdc'}}, - operation=operation) + operation=operation) _ctx.node.type_hierarchy = ['cloudify.nodes.Root', 'cloudify.nodes.vcloud.Disk'] delete_disk(ctx=_ctx) @@ -669,14 +673,15 @@ def test_detach_disk(*_, **__): return_value=True) def test_create_media(*_, **__): operation = {'name': 'create', 'retry_number': 0} - _ctx = get_mock_node_instance_context(properties={ + _ctx = get_mock_node_instance_context( + properties={ 'use_external_resource': False, 'resource_id': 'foo', 'resource_config': {'catalog_name': 'foo'}, 'iso': {'vol_ident': 'foo', 'sys_ident': '', 'files': {'ISO/FOLDER/content.json': 'baz'}}, 'client_config': {'size': 'bar', 'vdc': 'vdc'}}, - operation=operation) + operation=operation) _ctx.node.type_hierarchy = ['cloudify.nodes.Root', 'cloudify.nodes.vcloud.Media'] create_media(ctx=_ctx) @@ -692,14 +697,15 @@ def test_create_media(*_, **__): return_value=True) def test_delete_media(*_, **__): operation = {'name': 'delete', 'retry_number': 0} - _ctx = get_mock_node_instance_context(properties={ + _ctx = get_mock_node_instance_context( + properties={ 'use_external_resource': False, 'resource_id': 'foo', 'resource_config': {'catalog_name': 'foo'}, 'iso': {'vol_ident': 'foo', 'sys_ident': '', 'files': {'ISO/FOLDER/content.json': 'baz'}}, 'client_config': {'size': 'bar', 'vdc': 'vdc'}}, - operation=operation) + operation=operation) _ctx.node.type_hierarchy = ['cloudify.nodes.Root', 'cloudify.nodes.vcloud.Media'] delete_media(ctx=_ctx) @@ -833,14 +839,15 @@ def test_detach_media(*_, **__): 'by_type', return_value='foo') def test_create_network(*_, **__): operation = {'name': 'create', 'retry_number': 0} - _ctx = get_mock_node_instance_context(properties={ + _ctx = get_mock_node_instance_context( + properties={ 'use_external_resource': False, 'resource_id': 'foo', 'resource_config': { 'gateway_name': 'bar', 'network_cidr': '1.1.1.1/1'}, 'client_config': {'foo': 'bar', 'vdc': 'vdc'}}, - operation=operation) + operation=operation) _ctx.node.type_hierarchy = ['cloudify.nodes.Root', 'cloudify.nodes.vcloud.RoutedVDCNetwork'] create_network(ctx=_ctx) @@ -854,12 +861,13 @@ def test_create_network(*_, **__): return_value=True) def test_delete_network(*_, **__): operation = {'name': 'delete', 'retry_number': 0} - _ctx = get_mock_node_instance_context(properties={ + _ctx = get_mock_node_instance_context( + properties={ 'use_external_resource': False, 'resource_id': 'foo', 'resource_config': {'foo': 'bar'}, 'client_config': {'foo': 'bar', 'vdc': 'vdc'}}, - operation=operation) + operation=operation) _ctx.node.type_hierarchy = ['cloudify.nodes.Root', 'cloudify.nodes.vcloud.RoutedVDCNetwork'] delete_network(ctx=_ctx) @@ -875,13 +883,14 @@ def test_delete_network(*_, **__): 'by_type', return_value='foo') def test_create_vapp(*_, **__): operation = {'name': 'create', 'retry_number': 0} - _ctx = get_mock_node_instance_context(properties={ + _ctx = get_mock_node_instance_context( + properties={ 'use_external_resource': False, 'resource_id': 'foo', 'resource_config': {'description': 'bar', 'fence_mode': 'isolated'}, 'client_config': {'foo': 'bar', 'vdc': 'vdc'}}, - operation=operation) + operation=operation) _ctx.node.type_hierarchy = ['cloudify.nodes.Root', 'cloudify.nodes.vcloud.VApp'] create_vapp(ctx=_ctx) @@ -896,12 +905,13 @@ def test_create_vapp(*_, **__): return_value=True) def test_stop_vapp(*_, **__): operation = {'name': 'stop', 'retry_number': 0} - _ctx = get_mock_node_instance_context(properties={ + _ctx = get_mock_node_instance_context( + properties={ 'use_external_resource': False, 'resource_id': 'foo', 'resource_config': {'description': 'bar', 'fence_mode': 'baz'}, 'client_config': {'foo': 'bar', 'vdc': 'vdc'}}, - operation=operation) + operation=operation) _ctx.node.type_hierarchy = ['cloudify.nodes.Root', 'cloudify.nodes.vcloud.VApp'] stop_vapp(ctx=_ctx) @@ -916,12 +926,13 @@ def test_stop_vapp(*_, **__): return_value=True) def test_delete_vapp(*_, **__): operation = {'name': 'delete', 'retry_number': 0} - _ctx = get_mock_node_instance_context(properties={ + _ctx = get_mock_node_instance_context( + properties={ 'use_external_resource': False, 'resource_id': 'foo', 'resource_config': {'description': 'bar', 'fence_mode': 'baz'}, 'client_config': {'foo': 'bar', 'vdc': 'vdc'}}, - operation=operation) + operation=operation) _ctx.node.type_hierarchy = ['cloudify.nodes.Root', 'cloudify.nodes.vcloud.VApp'] delete_vapp(ctx=_ctx) @@ -938,14 +949,17 @@ def test_delete_vapp(*_, **__): 'by_type', return_value='foo') def test_create_vm(*_, **__): operation = {'name': 'create', 'retry_number': 0} - _ctx = get_mock_node_instance_context(properties={ + _ctx = get_mock_node_instance_context( + properties={ 'use_external_resource': False, 'resource_id': 'foo', - 'resource_config': {'catalog': 'bar', - 'template': 'baz', - 'fence_mode': 'isolated'}, + 'resource_config': { + 'catalog': 'bar', + 'template': 'baz', + 'fence_mode': 'isolated' + }, 'client_config': {'foo': 'bar', 'vdc': 'vdc'}}, - operation=operation) + operation=operation) _ctx.node.type_hierarchy = ['cloudify.nodes.Root', 'cloudify.nodes.vcloud.VM'] with mock.patch('vcd_plugin_sdk.resources.base.VDC') as vdc: @@ -964,14 +978,16 @@ def test_create_vm(*_, **__): 'by_type', return_value='foo') def test_create_vm_external(*_, **__): operation = {'name': 'create', 'retry_number': 0} - _ctx = get_mock_node_instance_context(properties={ + _ctx = get_mock_node_instance_context( + properties={ 'use_external_resource': True, 'resource_id': 'foo', 'resource_config': {'catalog': 'bar', 'template': 'baz', 'fence_mode': 'isolated'}, 'client_config': {'foo': 'bar', 'vdc': 'vdc'}}, - operation=operation) + operation=operation + ) _ctx.node.type_hierarchy = ['cloudify.nodes.Root', 'cloudify.nodes.vcloud.VM'] with mock.patch('vcd_plugin_sdk.resources.base.VDC') as vdc: @@ -991,24 +1007,30 @@ def test_create_vm_external(*_, **__): 'by_type', return_value='foo') def test_create_vm_handles_bad_request(*_, **__): operation = {'name': 'create', 'retry_number': 0} - _ctx = get_mock_node_instance_context(properties={ + _ctx = get_mock_node_instance_context( + properties={ 'use_external_resource': False, 'resource_id': 'foo', - 'resource_config': {'catalog': 'bar', - 'template': 'baz', - 'fence_mode': 'isolated'}, - 'client_config': {'foo': 'bar', 'vdc': 'vdc'}}, - operation=operation) + 'resource_config': { + 'catalog': 'bar', + 'template': 'baz', + 'fence_mode': 'isolated'}, + 'client_config': {'foo': 'bar', 'vdc': 'vdc'} + }, + operation=operation) _ctx.node.type_hierarchy = ['cloudify.nodes.Root', 'cloudify.nodes.vcloud.VM'] - with mock.patch('cloudify_vcd.constants.VCloudVM.instantiate_vapp', - side_effect=BadRequestException( - 400, - 'foo', - { - 'message': 'DUPLICATE_NAME', - 'minorCode': 400 - })): + with mock.patch( + 'cloudify_vcd.constants.VCloudVM.instantiate_vapp', + side_effect=BadRequestException( + 400, + 'foo', + { + 'message': 'DUPLICATE_NAME', + 'minorCode': 400 + } + ) + ): create_vm(ctx=_ctx) assert _ctx.instance.runtime_properties['resource_id'] == 'foo' assert '__created' in _ctx.instance.runtime_properties @@ -1024,14 +1046,15 @@ def test_create_vm_handles_bad_request(*_, **__): 'by_type', return_value='foo') def test_create_vm_raises_bad_request(*_, **__): operation = {'name': 'create', 'retry_number': 0} - _ctx = get_mock_node_instance_context(properties={ + _ctx = get_mock_node_instance_context( + properties={ 'use_external_resource': False, 'resource_id': 'foo', 'resource_config': {'catalog': 'bar', 'template': 'baz', 'fence_mode': 'isolated'}, 'client_config': {'foo': 'bar', 'vdc': 'vdc'}}, - operation=operation) + operation=operation) _ctx.node.type_hierarchy = ['cloudify.nodes.Root', 'cloudify.nodes.vcloud.VM'] with mock.patch('cloudify_vcd.constants.VCloudVM.instantiate_vapp', @@ -1050,14 +1073,15 @@ def test_create_vm_raises_bad_request(*_, **__): 'by_type', return_value='foo') def test_create_vm_raises_retry(*_, **__): operation = {'name': 'create', 'retry_number': 0} - _ctx = get_mock_node_instance_context(properties={ + _ctx = get_mock_node_instance_context( + properties={ 'use_external_resource': False, 'resource_id': 'foo', 'resource_config': {'catalog': 'bar', 'template': 'baz', 'fence_mode': 'isolated'}, 'client_config': {'foo': 'bar', 'vdc': 'vdc'}}, - operation=operation) + operation=operation) _ctx.node.type_hierarchy = ['cloudify.nodes.Root', 'cloudify.nodes.vcloud.VM'] with mock.patch('cloudify_vcd.constants.VCloudVM.instantiate_vapp', @@ -1082,12 +1106,13 @@ def test_create_vm_raises_retry(*_, **__): 'by_type', return_value='foo') def test_configure_vm(*_, **__): operation = {'name': 'configure', 'retry_number': 0} - _ctx = get_mock_node_instance_context(properties={ + _ctx = get_mock_node_instance_context( + properties={ 'use_external_resource': False, 'resource_id': 'foo', 'resource_config': {'catalog': 'bar', 'template': 'baz'}, 'client_config': {'foo': 'bar', 'vdc': 'vdc'}}, - operation=operation) + operation=operation) _ctx.node.type_hierarchy = ['cloudify.nodes.Root', 'cloudify.nodes.vcloud.VM'] with mock.patch('vcd_plugin_sdk.resources.base.VDC') as vdc: @@ -1106,12 +1131,14 @@ def test_configure_vm(*_, **__): 'by_type', return_value='foo') def test_start_vm(*_, **__): operation = {'name': 'start', 'retry_number': 0} - _ctx = get_mock_node_instance_context(properties={ + _ctx = get_mock_node_instance_context( + properties={ 'use_external_resource': False, 'resource_id': 'foo', 'resource_config': {'catalog': 'bar', 'template': 'baz'}, 'client_config': {'foo': 'bar', 'vdc': 'vdc'}}, - operation=operation) + operation=operation + ) _ctx.node.type_hierarchy = ['cloudify.nodes.Root', 'cloudify.nodes.vcloud.VM'] with mock.patch('vcd_plugin_sdk.resources.base.VDC') as vdc: @@ -1130,12 +1157,14 @@ def test_start_vm(*_, **__): 'by_type', return_value='foo') def test_stop_vm(*_, **__): operation = {'name': 'stop', 'retry_number': 0} - _ctx = get_mock_node_instance_context(properties={ + _ctx = get_mock_node_instance_context( + properties={ 'use_external_resource': False, 'resource_id': 'foo', 'resource_config': {'catalog': 'bar', 'template': 'baz'}, 'client_config': {'foo': 'bar', 'vdc': 'vdc'}}, - operation=operation) + operation=operation + ) _ctx.node.type_hierarchy = ['cloudify.nodes.Root', 'cloudify.nodes.vcloud.VM'] with mock.patch('vcd_plugin_sdk.resources.base.VDC') as vdc: @@ -1154,12 +1183,14 @@ def test_stop_vm(*_, **__): 'by_type', return_value='foo') def test_delete_vm(*_, **__): operation = {'name': 'delete', 'retry_number': 0} - _ctx = get_mock_node_instance_context(properties={ + _ctx = get_mock_node_instance_context( + properties={ 'use_external_resource': False, 'resource_id': 'foo', 'resource_config': {'catalog': 'bar', 'template': 'baz'}, 'client_config': {'foo': 'bar', 'vdc': 'vdc'}}, - operation=operation) + operation=operation + ) _ctx.node.type_hierarchy = ['cloudify.nodes.Root', 'cloudify.nodes.vcloud.VM'] with mock.patch('vcd_plugin_sdk.resources.base.VDC') as vdc: @@ -1178,7 +1209,8 @@ def test_configure_nic(*_, **__): ) ) )] - _ctx = get_mock_node_instance_context(properties={ + _ctx = get_mock_node_instance_context( + properties={ 'use_external_resource': False, 'resource_id': 'foo', 'resource_config': { @@ -1189,8 +1221,9 @@ def test_configure_nic(*_, **__): 'ip_address': '192.169.2.2' }, 'client_config': {'foo': 'bar', 'vdc': 'vdc'}}, - operation=operation, - relationships=relationships) + operation=operation, + relationships=relationships + ) _ctx.node.type_hierarchy = ['cloudify.nodes.Root', 'cloudify.nodes.vcloud.NIC'] configure_nic(ctx=_ctx) diff --git a/cloudify_vcd/utils.py b/cloudify_vcd/utils.py index 84a9f2e..bb3ce3b 100644 --- a/cloudify_vcd/utils.py +++ b/cloudify_vcd/utils.py @@ -118,7 +118,10 @@ def _return_resource_args(self, index): def is_relationship(_ctx=None): _ctx = _ctx or ctx - return _ctx.type == RELATIONSHIP_INSTANCE + try: + return _ctx.type == RELATIONSHIP_INSTANCE + except AttributeError: + return False def is_node_instance(_ctx=None): @@ -261,12 +264,16 @@ def get_resource_data(__ctx): return base_properties -def update_runtime_properties(current_ctx, props): +def update_runtime_properties(current_ctx, props, legacy=False): props = cleanup_objectify(props) ctx.logger.debug('Updating instance with properties {props}.'.format( props=props)) - if is_relationship(): + if legacy: + is_rel = is_relationship(current_ctx) + else: + is_rel = is_relationship() + if is_rel: if current_ctx.instance.id == ctx.source.instance.id: ctx.source.instance.runtime_properties.update(props) ctx.source.instance.runtime_properties.dirty = True @@ -389,7 +396,8 @@ def find_rel_by_type(node_instance, rel_type): def find_resource_id_from_relationship_by_type(node_instance, rel_type): rel = find_rel_by_type(node_instance, rel_type) - return rel.target.instance.runtime_properties.get('resource_id') + if rel: + return rel.target.instance.runtime_properties.get('resource_id') def use_external_resource(external, @@ -426,7 +434,11 @@ def use_external_resource(external, t=resource_type, r=resource_name)) -def expose_props(operation_name, resource=None, new_props=None, _ctx=None): +def expose_props(operation_name, + resource=None, + new_props=None, + _ctx=None, + legacy=False): _ctx = _ctx or ctx new_props = new_props or {} @@ -450,7 +462,7 @@ def expose_props(operation_name, resource=None, new_props=None, _ctx=None): # expose props is called after a successful operation, # so we should override this if we reach this point. new_props.update({'__RETRY_BAD_REQUEST': False}) - update_runtime_properties(_ctx, new_props) + update_runtime_properties(_ctx, new_props, legacy) def get_last_task(task): diff --git a/cloudify_vcd/vapp_tasks.py b/cloudify_vcd/vapp_tasks.py index 69c8153..9af5194 100644 --- a/cloudify_vcd/vapp_tasks.py +++ b/cloudify_vcd/vapp_tasks.py @@ -3,6 +3,7 @@ BadRequestException, MissingLinkException, InvalidStateException, + EntityNotFoundException, OperationNotSupportedException) from cloudify import ctx @@ -29,14 +30,18 @@ @resource_operation -def create_vapp(_, - vapp_id, - vapp_client, - vapp_vdc, - vapp_config, - vapp_class, - vapp_ctx, - **___): +def create_vapp(*args, **kwargs): + return _create_vapp(*args, **kwargs) + + +def _create_vapp(_=None, + vapp_id=None, + vapp_client=None, + vapp_vdc=None, + vapp_config=None, + vapp_class=None, + vapp_ctx=None, + **___): """ At the moment this function does nothing substantial. Creating vApps happens during VM create. @@ -73,14 +78,18 @@ def create_vapp(_, @resource_operation -def stop_vapp(vapp_ext, - vapp_id, - vapp_client, - vapp_vdc, - vapp_config, - vapp_class, - __, - **___): +def stop_vapp(*args, **kwargs): + return _stop_vapp(*args, *kwargs) + + +def _stop_vapp(vapp_ext=None, + vapp_id=None, + vapp_client=None, + vapp_vdc=None, + vapp_config=None, + vapp_class=None, + __=None, + **___): """ Perform undeploy operation on a vApp. @@ -107,14 +116,18 @@ def stop_vapp(vapp_ext, @resource_operation -def power_off_vapp(vapp_ext, - vapp_id, - vapp_client, - vapp_vdc, - vapp_config, - vapp_class, - __, - **___): +def power_off_vapp(*args, **kwargs): + return _power_off_vapp(*args, **kwargs) + + +def _power_off_vapp(vapp_ext=None, + vapp_id=None, + vapp_client=None, + vapp_vdc=None, + vapp_config=None, + vapp_class=None, + __=None, + **___): """ Execute power off on the vApp before deletion. :param vapp_ext: @@ -147,14 +160,18 @@ def power_off_vapp(vapp_ext, @resource_operation -def delete_vapp(vapp_ext, - vapp_id, - vapp_client, - vapp_vdc, - vapp_config, - vapp_class, - __, - **___): +def delete_vapp(*args, **kwargs): + return _delete_vapp(*args, **kwargs) + + +def _delete_vapp(vapp_ext=None, + vapp_id=None, + vapp_client=None, + vapp_vdc=None, + vapp_config=None, + vapp_class=None, + __=None, + **___): """ Delete a vApp. @@ -186,14 +203,19 @@ def delete_vapp(vapp_ext, @resource_operation -def create_vm(vm_external, - vm_id, - vm_client, - vm_vdc, - vm_config, - vm_class, - vm_ctx, - **_): +def create_vm(*args, **kwargs): + return _create_vm(*args, **kwargs) + + +def _create_vm(vm_external=None, + vm_id=None, + vm_client=None, + vm_vdc=None, + vm_config=None, + vm_class=None, + vm_ctx=None, + **_): + """ Instiatiate a vApp and create a virtual machine. @@ -208,8 +230,7 @@ def create_vm(vm_external, :return: """ - vapp_name = find_resource_id_from_relationship_by_type( - vm_ctx.instance, REL_VM_VAPP) # or vapp_id + vapp_name = get_vapp_name_from_vm_ctx(vm_ctx, vm_id) network = find_rel_by_type( vm_ctx.instance, REL_VM_NETWORK) @@ -220,7 +241,7 @@ def create_vm(vm_external, vm_name = vm_config.get('vm_name') if vm_name != vm_id: - ctx.logger.warn( + ctx.logger.debug( 'The parameter vm_name {v} in resource_config does not match ' 'the resource ID provided {i}. ' 'Using resource_id instead.'.format(v=vm_name, i=vm_id)) @@ -233,9 +254,11 @@ def create_vm(vm_external, 'is invalid. Valid values are {v}.'.format( fm=fence_mode, v=FENCE_MODE)) + ctx.logger.info(vm_config) + vm = vm_class( vm_id, - vapp_name, + vapp_name or vm_id, vm_client, vdc_name=vm_vdc, kwargs={}, @@ -260,7 +283,7 @@ def create_vm(vm_external, if not (vcd_already_exists(e) and not vm_external) or bad_vm_name(e): raise else: - vm.logger.warn('The vm {name} unexpectedly exists.'.format( + vm.logger.debug('The vm {name} unexpectedly exists.'.format( name=vm.name)) last_task = None vm_ctx.instance.runtime_properties['__VM_CREATE_VAPP'] = True @@ -268,17 +291,28 @@ def create_vm(vm_external, @resource_operation -def configure_vm(_, - vm_id, - vm_client, - vm_vdc, - vm_config, - vm_class, - vm_ctx, - **__): +def configure_vm(*args, **kwargs): + return _configure_vm(*args, **kwargs) + +def get_vapp_name_from_vm_ctx(vm_ctx, vm_id): vapp_name = find_resource_id_from_relationship_by_type( vm_ctx.instance, REL_VM_VAPP) + if not vapp_name: + return vm_id + return vm_id + + +def _configure_vm(_=None, + vm_id=None, + vm_client=None, + vm_vdc=None, + vm_config=None, + vm_class=None, + vm_ctx=None, + **__): + + vapp_name = get_vapp_name_from_vm_ctx(vm_ctx, vm_id) return vm_class( vm_id, vapp_name, @@ -290,14 +324,18 @@ def configure_vm(_, @resource_operation -def start_vm(vm_external, - vm_id, - vm_client, - vm_vdc, - vm_config, - vm_class, - vm_ctx, - **__): +def start_vm(*args, **kwargs): + return _start_vm(*args, **kwargs) + + +def _start_vm(vm_external=None, + vm_id=None, + vm_client=None, + vm_vdc=None, + vm_config=None, + vm_class=None, + vm_ctx=None, + **__): """ Power on both existing and new VMs. :param vm_external: @@ -311,8 +349,7 @@ def start_vm(vm_external, :return: """ - vapp_name = find_resource_id_from_relationship_by_type( - vm_ctx.instance, REL_VM_VAPP) + vapp_name = get_vapp_name_from_vm_ctx(vm_ctx, vm_id) vm = vm_class( vm_id, vapp_name, @@ -331,9 +368,9 @@ def start_vm(vm_external, last_task = \ vm_ctx.instance.runtime_properties['tasks']['update'][-1] except (KeyError, IndexError): - vm.logger.warn('The vm {name} is powered on, ' - 'but has no previous start task ' - 'and is not external.'.format(name=vm.name)) + vm.logger.debug('The vm {name} is powered on, ' + 'but has no previous start task ' + 'and is not external.'.format(name=vm.name)) return vm, None else: return vm, last_task @@ -344,14 +381,18 @@ def start_vm(vm_external, @resource_operation -def stop_vm(vm_external, - vm_id, - vm_client, - vm_vdc, - vm_config, - vm_class, - vm_ctx, - **__): +def stop_vm(*args, **kwargs): + return _stop_vm(*args, **kwargs) + + +def _stop_vm(vm_external=None, + vm_id=None, + vm_client=None, + vm_vdc=None, + vm_config=None, + vm_class=None, + vm_ctx=None, + **__): """ :param vm_external: @@ -365,8 +406,7 @@ def stop_vm(vm_external, :return: """ - vapp_name = find_resource_id_from_relationship_by_type( - vm_ctx.instance, REL_VM_VAPP) + vapp_name = get_vapp_name_from_vm_ctx(vm_ctx, vm_id) vm = vm_class( vm_id, vapp_name, @@ -393,14 +433,18 @@ def stop_vm(vm_external, @resource_operation -def delete_vm(vm_external, - vm_id, - vm_client, - vm_vdc, - vm_config, - vm_class, - vm_ctx, - **__): +def delete_vm(*args, **kwargs): + return _delete_vm(*args, **kwargs) + + +def _delete_vm(vm_external=None, + vm_id=None, + vm_client=None, + vm_vdc=None, + vm_config=None, + vm_class=None, + vm_ctx=None, + **__): """ :param vm_external: @@ -414,8 +458,7 @@ def delete_vm(vm_external, :return: """ - vapp_name = find_resource_id_from_relationship_by_type( - vm_ctx.instance, REL_VM_VAPP) + vapp_name = get_vapp_name_from_vm_ctx(vm_ctx, vm_id) vm = vm_class( vm_id, vapp_name, @@ -425,7 +468,7 @@ def delete_vm(vm_external, vapp_kwargs=vm_config ) - if vm_external: + if vm_external or not vm.vapp_object.exists: return vm, None try: last_task = vm.undeploy() @@ -435,23 +478,37 @@ def delete_vm(vm_external, (not vcd_unresolved_vm(e) and not cannot_power_off(e)): raise last_task = None + except EntityNotFoundException: + ctx.logger.info('VM is deleted. Now to delete Vapp.') if vm_ctx.instance.runtime_properties.get('__VM_CREATE_VAPP'): - vm.delete() - last_task = vm.vapp_object.delete() - + if vm.exists: + try: + vm.delete() + except Exception as e: + raise OperationRetry( + 'Waiting for VM to be deleted. {}'.format(str(e))) + if vm.vapp_object.exists: + try: + last_task = vm.vapp_object.delete() + except BadRequestException: + raise OperationRetry('Waiting for vapp to be deleted.') return vm, last_task @resource_operation -def configure_nic(_, - __, - ___, - ____, - nic_config, - _____, - nic_ctx, - **______): +def configure_nic(*args, **kwargs): + return _configure_nic(*args, **kwargs) + + +def _configure_nic(_=None, + __=None, + ___=None, + ____=None, + nic_config=None, + _____=None, + nic_ctx=None, + **______): """ :param _: Unused external @@ -477,21 +534,25 @@ def configure_nic(_, @resource_operation -def add_network(_, - __, - ___, - ____, - nic_config, - _____, - nic_ctx, - ______, - vm_id, - vm_client, - vm_vdc, - vm_config, - vm_class, - vm_ctx, - **_______): +def add_network(*args, **kwargs): + return _add_network(*args, **kwargs) + + +def _add_network(_=None, + __=None, + ___=None, + ____=None, + nic_config=None, + _____=None, + nic_ctx=None, + ______=None, + vm_id=None, + vm_client=None, + vm_vdc=None, + vm_config=None, + vm_class=None, + vm_ctx=None, + **_______): """Add a network to a VM. @@ -513,56 +574,77 @@ def add_network(_, :return: """ - vapp_name = find_resource_id_from_relationship_by_type( - vm_ctx.instance, REL_VM_VAPP) + vapp_name = get_vapp_name_from_vm_ctx(vm_ctx, vm_id) nic_network = find_resource_id_from_relationship_by_type( nic_ctx.instance, REL_NIC_NETWORK) vapp_node = find_rel_by_type(vm_ctx.instance, REL_VM_VAPP) - fence_mode = vapp_node.target.node.properties['resource_config'].get( - 'fence_mode') + fence_mode = 'bridged' + if vapp_node: + fence_mode = vapp_node.target.node.properties['resource_config'].get( + 'fence_mode') + elif 'fence_mode' in vm_ctx.instance.runtime_properties.get('server', {}): + fence_mode = vm_ctx.instance.runtime_properties['server'].get( + 'fence_mode') if nic_network: nic_config['network_name'] = nic_network + ctx.logger.info('Initializing vm with vm config {}'.format(vm_config)) + vm = vm_class( vm_id, vapp_name, vm_client, vdc_name=vm_vdc, - kwargs={}, + kwargs=vm_config, vapp_kwargs=vm_config ) if nic_network not in vm.vapp_networks: + vapp_network_config = { + 'orgvdc_network_name': nic_network, + 'fence_mode': fence_mode + } + ctx.logger.info( + 'Initializing vapp network with vapp network config {}'.format( + vapp_network_config)) try: - last_task = vm.add_vapp_network( - orgvdc_network_name=nic_config['network_name'], - fence_mode=fence_mode) + last_task = vm.add_vapp_network(**vapp_network_config) return vm, last_task except InvalidStateException as e: - raise OperationRetry( - 'Failed to add network {n} to vm {vm} for {e}.'.format( - n=nic_config['network_name'], vm=vm.name, e=e)) + if 'is already connected to vApp' not in str(e): + raise OperationRetry( + 'Failed to add network {n} to vm {vm} for {e}.'.format( + n=nic_network, vm=vm.name, e=e)) + + if nic_network not in vm.vapp_networks: + raise OperationRetry( + 'Waiting to add network {} to vapp {}.'.format( + nic_network, vapp_name)) return vm, None @resource_operation -def add_nic(_, - __, - ___, - ____, - nic_config, - _____, - nic_ctx, - ______, - vm_id, - vm_client, - vm_vdc, - vm_config, - vm_class, - vm_ctx, - **_______): +def add_nic(*args, **kwargs): + return _add_nic(*args, **kwargs) + + +def _add_nic(_=None, + __=None, + ___=None, + ____=None, + nic_config=None, + _____=None, + nic_ctx=None, + ______=None, + vm_id=None, + vm_client=None, + vm_vdc=None, + vm_config=None, + vm_class=None, + vm_ctx=None, + **_______): """ Add Nic to VM. :param _: Unused external nic @@ -583,54 +665,73 @@ def add_nic(_, :return: """ - vapp_name = find_resource_id_from_relationship_by_type( - vm_ctx.instance, REL_VM_VAPP) + if not vm_id: + vm_id = vm_ctx.node.properties.get('server', {}).get('name') + + vapp_name = get_vapp_name_from_vm_ctx(vm_ctx, vm_id) nic_network = find_resource_id_from_relationship_by_type( nic_ctx.instance, REL_NIC_NETWORK) if nic_network: nic_config['network_name'] = nic_network + ctx.logger.info('We are using this VM ID : {}'.format(vm_id)) + ctx.logger.info('We are using this VM App : {}'.format(vapp_name)) + vm = vm_class( vm_id, vapp_name, vm_client, vdc_name=vm_vdc, - kwargs={}, + kwargs=vm_config, vapp_kwargs=vm_config ) last_task = None - if not vm.get_nic_from_config(nic_config): + try: + has_nic_in_config = vm.get_nic_from_config(nic_config) + except AttributeError: + raise OperationRetry('Waiting for nics to be assigned...') + if not has_nic_in_config: last_task = vm.add_nic(**nic_config) - nic_ctx.instance.runtime_properties['ip_address'] = None - nic_ctx.instance.runtime_properties['mac_address'] = None - for nic in vm.nics: - _nic_network = nic.get('network') - if _nic_network == nic_network: - nic_ctx.instance.runtime_properties['ip_address'] = \ - nic.get('ip_address') - nic_ctx.instance.runtime_properties['mac_address'] = \ - nic.get('mac_address') - break + try: + nic_ctx.instance.runtime_properties['ip_address'] = None + nic_ctx.instance.runtime_properties['mac_address'] = None + except NonRecoverableError: + ctx.logger.debug( + 'Skipping IP assignment in legacy plugin will do it later.') + + else: + for nic in vm.nics: + _nic_network = nic.get('network') + if _nic_network == nic_network: + nic_ctx.instance.runtime_properties['ip_address'] = \ + nic.get('ip_address') + nic_ctx.instance.runtime_properties['mac_address'] = \ + nic.get('mac_address') + break return vm, last_task @resource_operation -def delete_nic(_, - __, - ___, - ____, - nic_config, - _____, - nic_ctx, - ______, - vm_id, - vm_client, - vm_vdc, - vm_config, - vm_class, - vm_ctx, - **_______): +def delete_nic(*args, **kwargs): + return _delete_nic(*args, **kwargs) + + +def _delete_nic(_=None, + __=None, + ___=None, + ____=None, + nic_config=None, + _____=None, + nic_ctx=None, + ______=None, + vm_id=None, + vm_client=None, + vm_vdc=None, + vm_config=None, + vm_class=None, + vm_ctx=None, + **_______): """ Delete NIC and remove network from vapp. :param _: Unused external nic @@ -651,8 +752,7 @@ def delete_nic(_, :return: """ - vapp_name = find_resource_id_from_relationship_by_type( - vm_ctx.instance, REL_VM_VAPP) + vapp_name = get_vapp_name_from_vm_ctx(vm_ctx, vm_id) nic_network = find_resource_id_from_relationship_by_type( nic_ctx.instance, REL_NIC_NETWORK) if nic_network: @@ -681,7 +781,7 @@ def delete_nic(_, last_task = vm.remove_vapp_network(nic_config['network_name']) return vm, last_task - ctx.logger.warn( + ctx.logger.debug( 'The NIC {config} was not found, ' 'so we cannot remove it from the VM.'.format(config=nic_config)) diff --git a/constraints.txt b/constraints.txt new file mode 100644 index 0000000..a5911a1 --- /dev/null +++ b/constraints.txt @@ -0,0 +1 @@ +https://github.com/cloudify-incubator/pyvcloud/archive/refs/heads/23.0.0-build.zip diff --git a/examples/SPARX_SERVICE.yaml b/examples/SPARX_SERVICE.yaml new file mode 100644 index 0000000..98eba59 --- /dev/null +++ b/examples/SPARX_SERVICE.yaml @@ -0,0 +1,213 @@ +tosca_definitions_version: cloudify_dsl_1_3 + +description: + This vcd test1 VRS project. + +imports: + - http://www.getcloudify.org/spec/cloudify/4.6/types.yaml + - plugin:cloudify-vcloud-plugin + +inputs: + + RSP_VRS1_APP_EXT_PROXY: + type: string + default: x + + RSP_VRS1_APP_OAM: + type: string + default: x + + +#server_name is the vm name + server_name: + type: string +# vCloud section + default: x + + host_name: + type: string + default: x + + vcloud_username: + type: string + default: { get_secret: vcd_username } + + vcloud_password: + type: string + default: { get_secret: vcd_password } + + vcloud_token: + type: string + default: '' + + vcloud_url: + type: string + default: { get_secret: vcd_fqdn } + + vcloud_service: + type: string + default: 'RSP' + + vcloud_service_type: + type: string + default: 'vcd' + + vcloud_instance: + type: string + default: 'RSP' + + vcloud_api_version: + type: string + default: '5.5' + + vcloud_org_url: + type: string + default: { get_secret: vcd_url } + + vcloud_org: + type: string + default: 'RSP' + + vcloud_vdc: + type: string + default: 'RSP' + + catalog: + type: string + default: 'TOMIA_Catalog' + + ssl_verify: + type: boolean + default: false + description: > + ssl check for connections to private services + (disable self-signed certificates) + + edge_gateway: + type: string + default: ESMAD0VEGRSP + +# Server section + + template: + type: string + description: rhel7 + default: { get_secret: rhel7_template } + + server_cpu: + type: string + default: 6 + + server_memory: + type: string + default: 32768 + +# Agent section: + user: + type: string + default: shuser + + login_pass: + type: string + default: "Sxx-Pxx00" + +# Network section + + RSP_VRS1_APP_EXT_PROXY_network_name: + type: string + default: "RSP_VRS1_APP_EXT_PROXY" + + RSP_VRS1_APP_OAM_network_name: + type: string + default: "RSP_VRS1_APP_OAM" + + + +dsl_definitions: + +######################################################## +# CREDENTIALS +######################################################## + + vcloud_config: &vcloud_config + username: { get_input: vcloud_username } + password: { get_input: vcloud_password } + # token: { get_input: vcloud_token } + url: { get_input: vcloud_url } + # instance: { get_input: vcloud_instance } + vdc: { get_input: vcloud_vdc } + org: { get_input: vcloud_org } + service_type: { get_input: vcloud_service_type } + #service: { get_input: vcloud_service } + #api_version: { get_input: vcloud_api_version } + #org_url: { get_input: vcloud_org_url } + #edge_gateway: { get_input: edge_gateway } + ssl_verify: { get_input: ssl_verify } + +node_templates: +#network + RSP_VRS1_APP_EXT_PROXY_network: + type: cloudify.vcloud.nodes.Network + properties: + use_external_resource: true + resource_id: { get_input: RSP_VRS1_APP_EXT_PROXY_network_name } + vcloud_config: *vcloud_config + + RSP_VRS1_APP_OAM_network: + type: cloudify.vcloud.nodes.Network + properties: + use_external_resource: true + resource_id: { get_input: RSP_VRS1_APP_OAM_network_name } + vcloud_config: *vcloud_config + + +#ports + RSP_VRS1_APP_EXT_PROXY_port: + type: cloudify.vcloud.nodes.Port + properties: + port: + network: { get_input: RSP_VRS1_APP_EXT_PROXY_network_name } + ip_allocation_mode: manual + ip_address: { get_input: RSP_VRS1_APP_EXT_PROXY } + primary_interface: false + vcloud_config: *vcloud_config + relationships: + - target: RSP_VRS1_APP_EXT_PROXY_network + type: cloudify.vcloud.port_connected_to_network + + RSP_VRS1_APP_OAM_port: + type: cloudify.vcloud.nodes.Port + properties: + port: + network: { get_input: RSP_VRS1_APP_OAM_network_name } + ip_allocation_mode: manual + ip_address: { get_input: RSP_VRS1_APP_OAM } + primary_interface: true + vcloud_config: *vcloud_config + relationships: + - target: RSP_VRS1_APP_OAM_network + type: cloudify.vcloud.port_connected_to_network + + sparx_service_server: + type: cloudify.vcloud.nodes.Server + properties: + agent_config: + user: shuser + install_method: remote + password: { get_secret: login_pass } + 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: host_name } + management_network: { get_input: RSP_VRS1_APP_OAM_network_name } + vcloud_config: *vcloud_config + relationships: + - target: RSP_VRS1_APP_EXT_PROXY_port + type: cloudify.vcloud.server_connected_to_port + - target: RSP_VRS1_APP_OAM_port + type: cloudify.vcloud.server_connected_to_port diff --git a/plugin.yaml b/plugin.yaml index c969370..7e89bc0 100644 --- a/plugin.yaml +++ b/plugin.yaml @@ -1,9 +1,11 @@ plugins: - vcd: + vcd: &plugin_mapping executor: central_deployment_agent package_name: cloudify-vcloud-plugin - package_version: '2.0.1' + package_version: '2.0.3' + # legacy + vcloud: *plugin_mapping data_types: @@ -84,6 +86,18 @@ data_types: description: Configuration options for pyvcloud.vcd.client.Client and pyvcloud.vcd.client.BasicLoginCredentials required: false + cloudify.datatypes.vcloud.LegacyBaseProperties: + properties: &LegacyBaseProperties + use_external_resource: + type: boolean + default: false + resource_id: + type: string + default: '' + vcloud_config: + type: dict + default: {} + node_types: cloudify.nodes.vcloud.Media: @@ -115,7 +129,7 @@ node_types: implementation: vcd.cloudify_vcd.disk_tasks.delete_disk cloudify.nodes.vcloud.NIC: - derived_from: cloudify.nodes.Volume + derived_from: cloudify.nodes.Port properties: <<: *BaseProperties resource_config: @@ -223,6 +237,78 @@ node_types: delete: implementation: vcd.cloudify_vcd.gateway_tasks.delete_gateway +## legacy types + + cloudify.vcloud.nodes.Server: + derived_from: cloudify.nodes.Compute + properties: + <<: *LegacyBaseProperties + server: + default: {} + management_network: + type: string + management_network_name: + type: string + default: { get_property: [SELF, management_network]} + interfaces: + cloudify.interfaces.lifecycle: + create: + implementation: vcloud.vcloud_server_plugin.server.create + inputs: {} + configure: + implementation: vcloud.vcloud_server_plugin.server.configure + inputs: {} + start: + implementation: vcloud.vcloud_server_plugin.server.start + inputs: {} + stop: + implementation: vcloud.vcloud_server_plugin.server.stop + inputs: {} + delete: + implementation: vcloud.vcloud_server_plugin.server.delete + inputs: {} + + cloudify.vcloud.nodes.Port: + derived_from: cloudify.nodes.Port + properties: + vcloud_config: + default: {} + port: + default: {} + interfaces: + cloudify.interfaces.lifecycle: + create: + implementation: vcloud.vcloud_network_plugin.port.creation_validation + inputs: {} + delete: + implementation: vcloud.vcloud_network_plugin.port.delete + inputs: {} + + cloudify.vcloud.nodes.Network: + derived_from: cloudify.nodes.Network + properties: + <<: *LegacyBaseProperties + network: + default: {} + interfaces: + cloudify.interfaces.lifecycle: + create: + implementation: vcloud.vcloud_network_plugin.network.create + inputs: + network: + default: { get_property: [SELF, network ] } + vcloud_config: + default: { get_property: [SELF, vcloud_config] } + delete: + implementation: vcloud.vcloud_network_plugin.network.delete + inputs: + vcloud_config: + default: { get_property: [SELF, vcloud_config] } + cloudify.interfaces.validation: + creation: + implementation: vcloud.vcloud_network_plugin.network.creation_validation + inputs: {} + relationships: cloudify.relationships.vcloud.network_connected_to_gateway: @@ -304,3 +390,16 @@ relationships: cloudify.relationships.vcloud.nic_connected_to_network: derived_from: cloudify.relationships.connected_to + +### Legacy Relationships + + cloudify.vcloud.port_connected_to_network: + derived_from: cloudify.relationships.vcloud.nic_connected_to_network + + cloudify.vcloud.server_connected_to_port: + derived_from: cloudify.relationships.vcloud.vm_connected_to_nic + target_interfaces: + cloudify.interfaces.relationship_lifecycle: + preconfigure: {} + postconfigure: {} + unlink: {} diff --git a/setup.py b/setup.py index c9476bf..7b953f2 100644 --- a/setup.py +++ b/setup.py @@ -40,7 +40,12 @@ def get_version(rel_file='plugin.yaml'): name='cloudify-vcloud-plugin', version=get_version(), packages=[ + 'vcloud_network_plugin', + 'vcloud_server_plugin', 'cloudify_vcd', + 'cloudify_vcd.legacy', + 'cloudify_vcd.legacy.compute', + 'cloudify_vcd.legacy.network', 'vcd_plugin_sdk', 'vcd_plugin_sdk.resources', ], @@ -49,7 +54,7 @@ def get_version(rel_file='plugin.yaml'): install_requires=[ 'cloudify-common>=5.1.0', 'pyvcloud==23.0.0', - 'cloudify-utilities-plugins-sdk', + 'cloudify-utilities-plugins-sdk-without-paramiko==0.0.45b', 'lxml' ] ) diff --git a/tox.ini b/tox.ini index a6bbdea..62f0e39 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = nosetest,pep8 +envlist = pytest,flake8 minversion = 1.6 skipsdist = True @@ -13,12 +13,22 @@ usedevelop = True install_command = pip install -U {opts} {packages} deps = -r{toxinidir}/dev-requirements.txt -r{toxinidir}/test-requirements.txt + -e . whitelist_externals = bash -[testenv:code_quality] +[testenv:flake8] commands = - flake8 vcd_plugin_sdk cloudify_vcd + flake8 cloudify_vcd vcd_plugin_sdk vcloud_network_plugin vcloud_server_plugin -[testenv:unit_tests] +[testenv:pytest] commands = - pytest vcd_plugin_sdk cloudify_vcd + pytest -s -v cloudify_vcd vcd_plugin_sdk vcloud_network_plugin vcloud_server_plugin + +[testenv:venv] +commands = {posargs} + +[flake8] +show-source = True +ignore = +exclude=.venv,.tox,dist,*egg,etc,build,bin,lib,local,share +filename=*.py diff --git a/vcd_plugin_sdk/connection.py b/vcd_plugin_sdk/connection.py index a43c0c7..bea2547 100644 --- a/vcd_plugin_sdk/connection.py +++ b/vcd_plugin_sdk/connection.py @@ -26,9 +26,9 @@ def default_logger(stream=sys.stdout): logging.basicConfig( # stream=stream, - filename=os.path.join( - os.path.expanduser('~'), - 'Desktop', 'vcloudclient.log'), + # filename=os.path.join( + # os.path.expanduser('~'), + # 'Desktop', 'vcloudclient.log'), level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%y.%m.%d-%H:%M:%S') diff --git a/vcd_plugin_sdk/resources/base.py b/vcd_plugin_sdk/resources/base.py index cdb172c..e912a41 100644 --- a/vcd_plugin_sdk/resources/base.py +++ b/vcd_plugin_sdk/resources/base.py @@ -30,6 +30,7 @@ def __init__(self, connection, vdc_name, vapp_name=None, tasks=None): self._connection = connection or VCloudConnect() self.logger = self.connection.logger + self.vdc_name = vdc_name try: vdc_resource = self._connection.org.get_vdc(vdc_name) diff --git a/vcd_plugin_sdk/resources/network.py b/vcd_plugin_sdk/resources/network.py index 9c394c2..7606c36 100644 --- a/vcd_plugin_sdk/resources/network.py +++ b/vcd_plugin_sdk/resources/network.py @@ -76,6 +76,13 @@ def network(self): sleep(5) return self._network + @property + def exists(self): + try: + return self.network + except VCloudSDKException: + return False + @property def allocated_addresses(self): # In busy environments, this can be pretty testy. diff --git a/vcd_plugin_sdk/resources/tests/test_resources.py b/vcd_plugin_sdk/resources/tests/test_resources.py index 4e71c14..b54368f 100644 --- a/vcd_plugin_sdk/resources/tests/test_resources.py +++ b/vcd_plugin_sdk/resources/tests/test_resources.py @@ -506,7 +506,7 @@ def test_vcloud_vm(*_, **__): vcloud_vm.add_vapp_network(**{ 'orgvdc_network_name': 'bar', }) - assert vcloud_vm.vapp_object.vapp.connect_org_vdc_network.call_count == 1 + assert vcloud_vm.vapp_object.vapp.connect_org_vdc_network.call_count == 2 vcloud_vm.remove_vapp_network('bar') assert \ vcloud_vm.vapp_object.vapp.disconnect_org_vdc_network.call_count == 1 diff --git a/vcd_plugin_sdk/resources/vapp.py b/vcd_plugin_sdk/resources/vapp.py index d50a503..0a59b02 100644 --- a/vcd_plugin_sdk/resources/vapp.py +++ b/vcd_plugin_sdk/resources/vapp.py @@ -19,7 +19,9 @@ from pyvcloud.vcd.client import TaskStatus from pyvcloud.vcd.exceptions import ( VcdTaskException, - EntityNotFoundException) + BadRequestException, + EntityNotFoundException, + OperationNotSupportedException) from .base import VCloudResource from .network import VCloudNetwork @@ -60,6 +62,14 @@ def vapp(self): self._vapp = self.get_vapp(self.vapp_name) return self._vapp + @property + def exists(self): + try: + return self.vapp + except EntityNotFoundException: + pass + return False + @property def networks(self): try: @@ -93,6 +103,8 @@ def get_catalog_items(self): return items def get_vapp(self, vapp_name=None): + vapp_name = vapp_name or self.name + self.logger.info('Looking for vapp {}'.format(vapp_name)) vapp_resource = self.vdc.get_vapp(vapp_name) return VApp(self.client, resource=vapp_resource) @@ -145,8 +157,73 @@ def undeploy(self, vapp_name=None, action='default'): # def delete_vms(self, vm_names): # return self.vapp.delete_vms(vm_names) # + def add_network(self, **kwargs): - task = self.vapp.connect_org_vdc_network(**kwargs) + task = None + self.logger.info('We have these direct networks: {}'.format( + self.vdc.list_orgvdc_direct_networks())) + self.logger.info('We have these routed networks: {}'.format( + self.vdc.list_orgvdc_routed_networks())) + self.logger.info('We have these isolated networks: {}'.format( + self.vdc.list_orgvdc_isolated_networks())) + bad_networks_exc = (BadRequestException, + OperationNotSupportedException) + if 'network_name' in kwargs: + kwargs['orgvdc_network_name'] = kwargs['network_name'] + try: + task = self.vapp.connect_org_vdc_network( + kwargs['orgvdc_network_name']) + except bad_networks_exc as e: + self.logger.info('Using just name did not work. {}'.format(str(e))) + self.logger.info('1We have these networks in vapp: {}'.format( + self.vapp.get_all_networks())) + + fence_mode = [kwargs.get('fence_mode')] + fence_mode.extend(['bridged', 'isolated', 'natRouted']) + for mode in fence_mode: + kwargs['fence_mode'] = mode + try: + self.logger.info('Trying these parameters {}'.format(kwargs)) + task = self.vapp.connect_org_vdc_network(**kwargs) + except bad_networks_exc as e: + self.logger.error(str(e)) + self.logger.info('These parameters failed: {}'.format( + kwargs)) + sleep(2) + try: + self.logger.info( + 'Trying these parameters {}'.format(kwargs)) + task = self.vapp.connect_org_vdc_network( + is_deployed=True, **kwargs) + except bad_networks_exc as e: + self.logger.error(str(e)) + self.logger.info( + 'These parameters failed: {}'.format(kwargs)) + # sleep(2) + else: + self.logger.info( + 'These parameters did not fail: {}'.format( + kwargs)) + break + continue + else: + self.logger.info('These parameters did not fail: {}'.format( + kwargs)) + break + self.logger.info( + '3We have these networks in vapp: {}'.format( + self.vapp.get_all_networks())) + + self.logger.info('Pausing to let vcloud think....') + # sleep(10) + + if 'orgvdc_network_name' in kwargs: + if kwargs['orgvdc_network_name'] in self.vapp.get_all_networks(): + return task + + if not task: + return + if 'add_network' in self.tasks: self.tasks['add_network'].append(task) else: @@ -204,6 +281,14 @@ def vm(self): self._vm.reload() return self._vm + @property + def exists(self): + try: + return self.vm + except EntityNotFoundException: + pass + return False + @property def nics(self): return self.vm.list_nics() @@ -231,7 +316,10 @@ def exposed_data(self): sleep(1) return data - def get_vm(self, vm_name): + def get_vm(self, vm_name=None): + if not vm_name: + vm_name = self.name + self.logger.info('Looking for vm_name {}'.format(vm_name)) vm_resource = self.vapp_object.vapp.get_vm(vm_name) vm = VM(self.client, resource=vm_resource) return vm diff --git a/vcloud_network_plugin/README.md b/vcloud_network_plugin/README.md new file mode 100644 index 0000000..8e0c690 --- /dev/null +++ b/vcloud_network_plugin/README.md @@ -0,0 +1,3 @@ +# Tosca VCloud Plugin Compatibility Project + +The Cloudify VCloud Plugin is a replacement for the Tosca VCloud Plugin. However, it is not backward compatible. The new plugin uses modern Cloudify topology and design. In order for users olf the old Tosca VCloud Plugin to continue to use their old blueprints on Python 3 managers, we have limited compatibility modules. diff --git a/vcloud_network_plugin/__init__.py b/vcloud_network_plugin/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/vcloud_network_plugin/network.py b/vcloud_network_plugin/network.py new file mode 100644 index 0000000..6eded98 --- /dev/null +++ b/vcloud_network_plugin/network.py @@ -0,0 +1,32 @@ +# Copyright (c) 2014-21 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_vcd.legacy.network.tasks import create_network, delete_network + + +@operation(resumable=True) +def create(*args, **kwargs): + create_network(*args, **kwargs) + + +@operation(resumable=True) +def delete(*args, **kwargs): + delete_network(*args, **kwargs) + + +@operation(resumable=True) +def creation_validation(*args, **kwargs): + pass diff --git a/vcloud_network_plugin/port.py b/vcloud_network_plugin/port.py new file mode 100644 index 0000000..ac8e12b --- /dev/null +++ b/vcloud_network_plugin/port.py @@ -0,0 +1,30 @@ +# Copyright (c) 2014-21 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_vcd.legacy.compute.tasks import port_creation_validation + +# TODO: We need to add add_network, add_nic, and remove_nic, after we do VM. + + +@operation(resumable=True) +def creation_validation(*args, **kwargs): + port_creation_validation(*args, **kwargs) + + +@operation(resumable=True) +def delete(*_, **kwargs): + _ctx = kwargs.get('ctx') + for key in list(_ctx.instance.runtime_properties.keys()): + del _ctx.instance.runtime_properties[key] diff --git a/vcloud_network_plugin/tests/__init__.py b/vcloud_network_plugin/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/vcloud_network_plugin/tests/test_network.py b/vcloud_network_plugin/tests/test_network.py new file mode 100644 index 0000000..85bcd6a --- /dev/null +++ b/vcloud_network_plugin/tests/test_network.py @@ -0,0 +1,138 @@ +# Copyright (c) 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 copy import deepcopy +from cloudify.state import current_ctx +from mock import patch, PropertyMock + +from .. network import create, delete +from cloudify_vcd.legacy.tests import create_ctx, DEFAULT_NODE_PROPS + + +def get_network_ctx(existing=False, + resource_id=None, + network=None, + gateway=None): + """ + Generate the node props for use in the test. + :param existing: whether to create or not + :param resource_id: name of the resource + :param network: the network resource config + :param gateway: name of the edge gateway + :return: + """ + + network = network or { + 'static_range': '10.10.0.2-10.10.0.128', + 'gateway_ip': '10.10.0.1' + } + + network_node_props = {'network': network} + network_node_props.update( + deepcopy(DEFAULT_NODE_PROPS)) + network_node_props['use_external_resource'] = existing + network_node_props['resource_id'] = resource_id + if gateway: + network_node_props['vcloud_config']['edge_gateway'] = gateway + return network_node_props + + +@patch('cloudify_vcd.legacy.utils.NamedTemporaryFile') +@patch('cloudify_vcd.legacy.utils.get_deployment_dir') +@patch('vcd_plugin_sdk.connection.Org', autospec=True) +@patch('pyvcloud.vcd.vdc.VDC.get_direct_orgvdc_network') +@patch('vcd_plugin_sdk.connection.Client', autospec=True) +@patch('pyvcloud.vcd.vdc.VDC.get_gateway', return_value={'href': 'foo'}) +def test_create_external_network_with_gateway(*_, **__): + network_node_props = get_network_ctx(True, 'foo', gateway='baz') + _ctx = create_ctx( + node_id='external_proxy', + node_type=[ + 'cloudify.nodes.Network', + 'cloudify.vcloud.nodes.Network' + ], + node_properties=network_node_props) + current_ctx.set(_ctx) + create(ctx=_ctx) + assert _ctx.instance.runtime_properties['resource_id'] == 'foo' + + +@patch('cloudify_vcd.legacy.utils.NamedTemporaryFile') +@patch('cloudify_vcd.legacy.utils.get_deployment_dir') +@patch('cloudify_vcd.legacy.decorators.get_last_task') +@patch('vcd_plugin_sdk.connection.Org', autospec=True) +@patch('pyvcloud.vcd.vdc.VDC.get_direct_orgvdc_network') +@patch('vcd_plugin_sdk.connection.Client', autospec=True) +@patch('pyvcloud.vcd.vdc.VDC.get_gateway', return_value={'href': 'foo'}) +def test_delete_external_network_with_gateway(*_, **__): + network_node_props = get_network_ctx(True, 'foo', gateway='baz') + _ctx = create_ctx( + node_id='external_proxy', + node_type=[ + 'cloudify.nodes.Network', + 'cloudify.vcloud.nodes.Network' + ], + node_properties=network_node_props) + current_ctx.set(_ctx) + delete(ctx=_ctx) + assert _ctx.instance.runtime_properties['resource_id'] == 'foo' + + +@patch('cloudify_vcd.legacy.utils.NamedTemporaryFile') +@patch('cloudify_vcd.legacy.utils.get_deployment_dir') +@patch('cloudify_vcd.legacy.decorators.get_last_task') +@patch('vcd_plugin_sdk.connection.Org', autospec=True) +@patch('vcd_plugin_sdk.connection.Client', autospec=True) +@patch('pyvcloud.vcd.vdc.VDC.get_gateway', return_value={'href': 'foo'}) +@patch('cloudify_vcd.legacy.decorators.check_if_task_successful', + return_value=True) +@patch('pyvcloud.vcd.vdc.Platform.get_external_network', + return_value={'href': 'foo'}) +def test_create_network_with_gateway(*_, **__): + network_node_props = get_network_ctx(resource_id='foo', gateway='baz') + _ctx = create_ctx( + node_id='external_proxy', + node_type=[ + 'cloudify.nodes.Network', + 'cloudify.vcloud.nodes.Network' + ], + node_properties=network_node_props) + current_ctx.set(_ctx) + with patch('cloudify_vcd.legacy.utils.VCloudNetwork.exists', + new_callable=PropertyMock) as net_exists: + net_exists.return_value = False + + +@patch('cloudify_vcd.legacy.utils.NamedTemporaryFile') +@patch('cloudify_vcd.legacy.utils.get_deployment_dir') +@patch('cloudify_vcd.legacy.decorators.get_last_task') +@patch('vcd_plugin_sdk.connection.Org', autospec=True) +@patch('pyvcloud.vcd.vdc.VDC.get_direct_orgvdc_network') +@patch('vcd_plugin_sdk.connection.Client', autospec=True) +@patch('pyvcloud.vcd.vdc.VDC.get_gateway', return_value={'href': 'foo'}) +@patch('cloudify_vcd.legacy.decorators.check_if_task_successful', + return_value=True) +def test_delete_network_with_gateway(*_, **__): + network_node_props = get_network_ctx(resource_id='foo', gateway='baz') + _ctx = create_ctx( + node_id='external_proxy', + node_type=[ + 'cloudify.nodes.Network', + 'cloudify.vcloud.nodes.Network' + ], + node_properties=network_node_props, + operation_name='cloudify.interfaces.lifecycle.delete') + current_ctx.set(_ctx) + delete(ctx=_ctx) + assert '__deleted' in _ctx.instance.runtime_properties diff --git a/vcloud_network_plugin/tests/test_port.py b/vcloud_network_plugin/tests/test_port.py new file mode 100644 index 0000000..ee284a6 --- /dev/null +++ b/vcloud_network_plugin/tests/test_port.py @@ -0,0 +1,62 @@ +from copy import deepcopy + +from mock import MagicMock +from cloudify.state import current_ctx + +from .. port import creation_validation +from .test_network import get_network_ctx +from cloudify_vcd.legacy.tests import create_ctx, DEFAULT_NODE_PROPS + + +def get_port_ctx(num=None, primary=True): + num = num or '1' + port = { + 'ip_allocation_mode': 'manual', + 'ip_address': '10.10.10.{}'.format(num), + 'primary_interface': primary, + } + port_props = {'port': port} + port_props.update( + deepcopy(DEFAULT_NODE_PROPS)) + return port_props + + +def test_create_external_network_with_gateway(*_, **__): + port_props = get_port_ctx() + net_props = get_network_ctx(resource_id='foo-bar') + _net_ctx = create_ctx( + node_id='external_proxy', + node_type=[ + 'cloudify.nodes.Network', + 'cloudify.vcloud.nodes.Network' + ], + node_properties=net_props, + runtime_props={ + 'resource_id': net_props['resource_id'], + 'network': net_props['network'] + } + ) + rels = [ + MagicMock( + name='network1', + target=_net_ctx, + type_hierarchy=[ + 'cloudify.vcloud.port_connected_to_network', + 'cloudify.relationships.connected_to' + ] + ), + ] + _ctx = create_ctx( + node_id='external_proxy', + node_type=[ + 'cloudify.nodes.Port', + 'cloudify.vcloud.nodes.Port' + ], + node_properties=port_props, + relationships=rels + ) + current_ctx.set(_ctx) + creation_validation(ctx=_ctx) + assert _ctx.instance.runtime_properties['network'] == 'foo-bar' + assert _ctx.instance.runtime_properties['port']['ip_address'] == \ + '10.10.10.1' diff --git a/vcloud_server_plugin/README.md b/vcloud_server_plugin/README.md new file mode 100644 index 0000000..37f4a7e --- /dev/null +++ b/vcloud_server_plugin/README.md @@ -0,0 +1,4 @@ +# Tosca VCloud Plugin Compatibility Project + +The Cloudify VCloud Plugin is a replacement for the Tosca VCloud Plugin. However, it is not backward compatible. The new plugin uses modern Cloudify topology and design. In order for users olf the old Tosca VCloud Plugin to continue to use their old blueprints on Python 3 managers, we have limited compatibility modules. + diff --git a/vcloud_server_plugin/__init__.py b/vcloud_server_plugin/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/vcloud_server_plugin/server.py b/vcloud_server_plugin/server.py new file mode 100644 index 0000000..43477f0 --- /dev/null +++ b/vcloud_server_plugin/server.py @@ -0,0 +1,83 @@ +# Copyright (c) 2014-21 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_vcd.legacy.compute.tasks import ( + # configure_server, + postconfigure_nic, + preconfigure_nic, + power_off_vapp, + delete_server, + create_server, + start_server, + stop_server, + delete_vapp, + unlink_nic, + stop_vapp, +) + + +@operation(resumable=True) +def create(*args, **kwargs): + create_server(*args, **kwargs) + + +@operation(resumable=True) +def configure(*args, **kwargs): + preconfigure_nic(*args, **kwargs) + # configure_server(*args, **kwargs) + postconfigure_nic(*args, **kwargs) + ctx = kwargs['ctx'] + nic = None + data = ctx.instance.runtime_properties.get('data', {}) + for nic in data.get('nics', []): + if nic['primary'] == 'true': + break + if nic: + ctx.logger.info('Found primary IP address.') + ctx.instance.runtime_properties['ip'] = nic['ip_address'] + ctx.instance.runtime_properties['ip_address'] = nic['ip_address'] + ctx.instance.runtime_properties['private_ip_address'] = \ + nic['ip_address'] + ctx.logger.info('Assigned ip properties to ip address {}'.format( + nic['ip_address'])) + + +@operation(resumable=True) +def postconfigure(*args, **kwargs): + pass + + +@operation(resumable=True) +def start(*args, **kwargs): + start_server(*args, **kwargs) + + +@operation(resumable=True) +def stop(*args, **kwargs): + stop_server(*args, **kwargs) + unlink_nic(*args, **kwargs) + stop_vapp(*args, **kwargs) + power_off_vapp(*args, **kwargs) + + +@operation(resumable=True) +def delete(*args, **kwargs): + delete_server(*args, **kwargs) + delete_vapp(*args, **kwargs) + + +@operation(resumable=True) +def creation_validation(*args, **kwargs): + pass diff --git a/vcloud_server_plugin/tests/__init__.py b/vcloud_server_plugin/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/vcloud_server_plugin/tests/test_server.py b/vcloud_server_plugin/tests/test_server.py new file mode 100644 index 0000000..78d8317 --- /dev/null +++ b/vcloud_server_plugin/tests/test_server.py @@ -0,0 +1,784 @@ +# Copyright (c) 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 copy import deepcopy +from mock import patch, MagicMock, PropertyMock +from cloudify.state import current_ctx + +from .. server import create, delete, configure, start, stop +from vcloud_network_plugin.tests.test_port import get_port_ctx +from cloudify_vcd.legacy.tests import create_ctx, DEFAULT_NODE_PROPS + + +def get_vm_ctx(existing=False, + resource_id=None, + server=None): + """ + Generate the node props for use in the test. + :param existing: whether to create or not + :param resource_id: name of the resource + :param server: the server resource config + :param gateway: name of the edge gateway + :return: + """ + server = server or { + 'name': 'foo', + 'catalog': 'acme', + 'template': 'taco', + 'hardware': { + 'memory': 1024, + 'cpu': 1, + }, + 'guest_customization': { + 'computer_name': 'bar' + } + + } + server_node_props = {'server': server} + server_node_props.update( + deepcopy(DEFAULT_NODE_PROPS)) + server_node_props['use_external_resource'] = existing + server_node_props['resource_id'] = resource_id + return server_node_props + + +@patch('cloudify_vcd.legacy.utils.NamedTemporaryFile') +@patch('cloudify_vcd.legacy.utils.get_deployment_dir') +@patch('cloudify_vcd.legacy.decorators.get_last_task') +@patch('vcd_plugin_sdk.connection.Org', autospec=True) +@patch('vcd_plugin_sdk.resources.vapp.VCloudVM.get_vm') +@patch('vcd_plugin_sdk.connection.Client', autospec=True) +@patch('vcd_plugin_sdk.resources.vapp.VCloudVM.exists', return_value=True) +@patch('cloudify_vcd.legacy.decorators.check_if_task_successful', + return_value=True) +def test_create_external_vm(*_, **__): + server_node_props = get_vm_ctx(True, 'foo') + _ctx = create_ctx( + node_id='server', + node_type=[ + 'cloudify.nodes.Compute', + 'cloudify.vcloud.nodes.Server' + ], + node_properties=server_node_props, + ) + current_ctx.set(_ctx) + with patch('vcd_plugin_sdk.resources.base.VDC') as vdc: + vdc.client.get_api_version = (lambda: '33') + create(ctx=_ctx) + assert _ctx.instance.runtime_properties['resource_id'] == 'foo' + + +@patch('cloudify_vcd.legacy.utils.NamedTemporaryFile') +@patch('cloudify_vcd.legacy.utils.get_deployment_dir') +@patch('cloudify_vcd.legacy.decorators.get_last_task') +@patch('vcd_plugin_sdk.connection.Org', autospec=True) +@patch('vcd_plugin_sdk.resources.vapp.VCloudVM.get_vm') +@patch('vcd_plugin_sdk.connection.Client', autospec=True) +@patch('vcd_plugin_sdk.resources.vapp.VCloudVM.exists', return_value=True) +@patch('cloudify_vcd.legacy.decorators.check_if_task_successful', + return_value=True) +def test_delete_external_vm(*_, **__): + server_node_props = get_vm_ctx(True, 'foo') + _ctx = create_ctx( + node_id='external_proxy', + node_type=[ + 'cloudify.nodes.Compute', + 'cloudify.vcloud.nodes.Server' + ], + node_properties=server_node_props) + current_ctx.set(_ctx) + with patch('vcd_plugin_sdk.resources.base.VDC') as vdc: + vdc.client.get_api_version = (lambda: '33') + delete(ctx=_ctx) + assert _ctx.instance.runtime_properties['resource_id'] == 'foo' + + +@patch('pyvcloud.vcd.vapp.VApp.get_vm') +@patch('cloudify_vcd.legacy.utils.NamedTemporaryFile') +@patch('cloudify_vcd.legacy.utils.get_deployment_dir') +@patch('vcd_plugin_sdk.connection.Org', autospec=True) +@patch('vcd_plugin_sdk.connection.Client', autospec=True) +@patch('vcd_plugin_sdk.resources.vapp.VCloudVM.exists', return_value=True) +def test_configure_external_vm(*_, **__): + server_node_props = get_vm_ctx(True, 'foo') + _ctx = create_ctx( + node_id='external_proxy', + node_type=[ + 'cloudify.nodes.Compute', + 'cloudify.vcloud.nodes.Server' + ], + node_properties=server_node_props) + current_ctx.set(_ctx) + with patch('vcd_plugin_sdk.resources.base.VDC') as vdc: + vdc.client.get_api_version = (lambda: '33') + configure(ctx=_ctx) + assert _ctx.instance.runtime_properties['resource_id'] == 'foo' + + +@patch('pyvcloud.vcd.vapp.VApp.get_vm') +@patch('cloudify_vcd.legacy.utils.NamedTemporaryFile') +@patch('cloudify_vcd.legacy.utils.get_deployment_dir') +@patch('cloudify_vcd.legacy.decorators.get_last_task') +@patch('vcd_plugin_sdk.connection.Org', autospec=True) +@patch('vcd_plugin_sdk.connection.Client', autospec=True) +@patch('cloudify_vcd.legacy.decorators.check_if_task_successful', + return_value=True) +@patch('vcd_plugin_sdk.resources.vapp.VCloudVM.exists', return_value=True) +def test_start_external_vm(*_, **__): + server_node_props = get_vm_ctx(True, 'foo') + _ctx = create_ctx( + node_id='external_proxy', + node_type=[ + 'cloudify.nodes.Compute', + 'cloudify.vcloud.nodes.Server' + ], + node_properties=server_node_props) + current_ctx.set(_ctx) + with patch('vcd_plugin_sdk.resources.base.VDC') as vdc: + vdc.client.get_api_version = (lambda: '33') + start(ctx=_ctx) + assert _ctx.instance.runtime_properties['resource_id'] == 'foo' + + +@patch('pyvcloud.vcd.vapp.VApp.get_vm') +@patch('cloudify_vcd.legacy.utils.NamedTemporaryFile') +@patch('cloudify_vcd.legacy.utils.get_deployment_dir') +@patch('cloudify_vcd.legacy.decorators.get_last_task') +@patch('vcd_plugin_sdk.connection.Org', autospec=True) +@patch('vcd_plugin_sdk.connection.Client', autospec=True) +@patch('cloudify_vcd.legacy.decorators.check_if_task_successful', + return_value=True) +@patch('vcd_plugin_sdk.resources.vapp.VCloudVM.exists', return_value=True) +def test_stop_external_vm(*_, **__): + server_node_props = get_vm_ctx(True, 'foo') + _ctx = create_ctx( + node_id='external_proxy', + node_type=[ + 'cloudify.nodes.Compute', + 'cloudify.vcloud.nodes.Server' + ], + node_properties=server_node_props) + current_ctx.set(_ctx) + with patch('vcd_plugin_sdk.resources.base.VDC') as vdc: + vdc.client.get_api_version = (lambda: '33') + stop(ctx=_ctx) + assert _ctx.instance.runtime_properties['resource_id'] == 'foo' + + +@patch('pyvcloud.vcd.vapp.VApp.get_vm') +@patch('cloudify_vcd.legacy.utils.NamedTemporaryFile') +@patch('cloudify_vcd.legacy.utils.get_deployment_dir') +@patch('cloudify_vcd.legacy.decorators.get_last_task') +@patch('vcd_plugin_sdk.connection.Org', autospec=True) +@patch('vcd_plugin_sdk.connection.Client', autospec=True) +@patch('cloudify_vcd.legacy.decorators.check_if_task_successful', + return_value=True) +def test_create_vm(*_, **__): + server_node_props = get_vm_ctx(resource_id='foo') + _port1_ctx = create_ctx( + node_id='port1', + node_type=[ + 'cloudify.nodes.Port', + 'cloudify.vcloud.nodes.Port' + ], + node_properties=get_port_ctx(), + runtime_props={ + 'network_name': 'port1_network', + 'port': { + 'ip_address': '10.10.10.1' + } + } + ) + _port2_ctx = create_ctx( + node_id='port2', + node_type=[ + 'cloudify.nodes.Port', + 'cloudify.vcloud.nodes.Port' + ], + node_properties=get_port_ctx('2', False), + runtime_props={ + 'network_name': 'port2_network', + 'port': { + 'ip_address': '10.10.10.2' + } + } + ) + rels = [ + MagicMock( + name='port1', + target=_port1_ctx, + type_hierarchy=[ + 'cloudify.vcloud.server_connected_to_port', + 'cloudify.relationships.connected_to' + ] + ), + MagicMock( + name='port2', + target=_port2_ctx, + type_hierarchy=[ + 'cloudify.vcloud.server_connected_to_port', + 'cloudify.relationships.connected_to' + ] + ), + ] + _ctx = create_ctx( + node_id='server', + node_type=[ + 'cloudify.nodes.Compute', + 'cloudify.vcloud.nodes.Server' + ], + node_properties=server_node_props, + relationships=rels + ) + current_ctx.set(_ctx) + with patch('vcd_plugin_sdk.resources.base.VDC') as vdc: + vdc.client.get_api_version = (lambda: '33') + with patch('cloudify_vcd.legacy.utils.VCloudVM.exists', + new_callable=PropertyMock) as vm_exists: + vm_exists.return_value = False + create(ctx=_ctx) + assert '__VM_CREATE_VAPP' in _ctx.instance.runtime_properties + assert _ctx.instance.runtime_properties['resource_id'] == 'foo' + assert _ctx.instance.runtime_properties['server']['network'] == \ + 'port1_network' + assert _ctx.instance.runtime_properties['server']['ip_address'] == \ + '10.10.10.1' + + +@patch('pyvcloud.vcd.vapp.VApp.get_vm') +@patch('cloudify_vcd.legacy.utils.NamedTemporaryFile') +@patch('cloudify_vcd.legacy.utils.get_deployment_dir') +@patch('cloudify_vcd.legacy.decorators.get_last_task') +@patch('vcd_plugin_sdk.connection.Org', autospec=True) +@patch('vcd_plugin_sdk.connection.Client', autospec=True) +@patch('cloudify_vcd.legacy.decorators.check_if_task_successful', + return_value=True) +def test_create_vm_no_primary_port(*_, **__): + server_node_props = get_vm_ctx(resource_id='foo') + _port1_ctx = create_ctx( + node_id='port1', + node_type=[ + 'cloudify.nodes.Port', + 'cloudify.vcloud.nodes.Port' + ], + node_properties=get_port_ctx(primary=False), + runtime_props={ + 'network_name': 'port1_network', + 'port': { + 'ip_address': '10.10.10.1' + } + } + ) + _port2_ctx = create_ctx( + node_id='port2', + node_type=[ + 'cloudify.nodes.Port', + 'cloudify.vcloud.nodes.Port' + ], + node_properties=get_port_ctx('2', False), + runtime_props={ + 'network_name': 'port2_network', + 'port': { + 'ip_address': '10.10.10.2' + } + } + ) + rels = [ + MagicMock( + name='port1', + target=_port1_ctx, + type_hierarchy=[ + 'cloudify.vcloud.server_connected_to_port', + 'cloudify.relationships.connected_to' + ] + ), + MagicMock( + name='port2', + target=_port2_ctx, + type_hierarchy=[ + 'cloudify.vcloud.server_connected_to_port', + 'cloudify.relationships.connected_to' + ] + ), + ] + _ctx = create_ctx( + node_id='server', + node_type=[ + 'cloudify.nodes.Compute', + 'cloudify.vcloud.nodes.Server' + ], + node_properties=server_node_props, + relationships=rels + ) + current_ctx.set(_ctx) + with patch('vcd_plugin_sdk.resources.base.VDC') as vdc: + vdc.client.get_api_version = (lambda: '33') + with patch('cloudify_vcd.legacy.utils.VCloudVM.exists', + new_callable=PropertyMock) as vm_exists: + vm_exists.return_value = False + create(ctx=_ctx) + assert '__VM_CREATE_VAPP' in _ctx.instance.runtime_properties + assert _ctx.instance.runtime_properties['resource_id'] == 'foo' + assert _ctx.instance.runtime_properties['server']['network'] == \ + 'port2_network' + + +@patch('pyvcloud.vcd.vapp.VApp.get_vm') +@patch('cloudify_vcd.legacy.utils.NamedTemporaryFile') +@patch('cloudify_vcd.legacy.utils.get_deployment_dir') +@patch('cloudify_vcd.legacy.decorators.get_last_task') +@patch('vcd_plugin_sdk.connection.Org', autospec=True) +@patch('vcd_plugin_sdk.connection.Client', autospec=True) +@patch('cloudify_vcd.legacy.decorators.check_if_task_successful', + return_value=True) +def test_create_vm_port_network(*_, **__): + server_node_props = get_vm_ctx(resource_id='foo') + _port1_props = get_port_ctx() + _port1_props['port']['network'] = 'port1_port_network' + _port1_ctx = create_ctx( + node_id='port1', + node_type=[ + 'cloudify.nodes.Port', + 'cloudify.vcloud.nodes.Port' + ], + node_properties=_port1_props, + runtime_props={ + 'network_name': 'port1_network', + 'port': { + 'ip_address': '10.10.10.5' + } + } + ) + _port2_ctx = create_ctx( + node_id='port2', + node_type=[ + 'cloudify.nodes.Port', + 'cloudify.vcloud.nodes.Port' + ], + node_properties=get_port_ctx('2', False), + runtime_props={ + 'network_name': 'port2_network', + 'port': { + 'ip_address': '10.10.10.2' + } + } + ) + rels = [ + MagicMock( + name='port1', + target=_port1_ctx, + type_hierarchy=[ + 'cloudify.vcloud.server_connected_to_port', + 'cloudify.relationships.connected_to' + ] + ), + MagicMock( + name='port2', + target=_port2_ctx, + type_hierarchy=[ + 'cloudify.vcloud.server_connected_to_port', + 'cloudify.relationships.connected_to' + ] + ), + ] + _ctx = create_ctx( + node_id='server', + node_type=[ + 'cloudify.nodes.Compute', + 'cloudify.vcloud.nodes.Server' + ], + node_properties=server_node_props, + relationships=rels + ) + current_ctx.set(_ctx) + with patch('vcd_plugin_sdk.resources.base.VDC') as vdc: + vdc.client.get_api_version = (lambda: '33') + with patch('cloudify_vcd.legacy.utils.VCloudVM.exists', + new_callable=PropertyMock) as vm_exists: + vm_exists.return_value = False + create(ctx=_ctx) + assert '__VM_CREATE_VAPP' in _ctx.instance.runtime_properties + assert _ctx.instance.runtime_properties['resource_id'] == 'foo' + assert _ctx.instance.runtime_properties['server']['network'] == \ + 'port1_network' + assert _ctx.instance.runtime_properties['server']['ip_address'] == \ + '10.10.10.5' + + +@patch('pyvcloud.vcd.vapp.VApp.get_vm') +@patch('cloudify_vcd.legacy.utils.NamedTemporaryFile') +@patch('cloudify_vcd.legacy.utils.get_deployment_dir') +@patch('cloudify_vcd.legacy.decorators.get_last_task') +@patch('vcd_plugin_sdk.connection.Org', autospec=True) +@patch('vcd_plugin_sdk.connection.Client', autospec=True) +@patch('cloudify_vcd.legacy.decorators.check_if_task_successful', + return_value=True) +def test_configure_vm(*_, **__): + server_node_props = get_vm_ctx(resource_id='foo') + _ctx = create_ctx( + node_id='external_proxy', + node_type=[ + 'cloudify.nodes.Compute', + 'cloudify.vcloud.nodes.Server' + ], + node_properties=server_node_props) + current_ctx.set(_ctx) + with patch('vcd_plugin_sdk.resources.base.VDC') as vdc: + vdc.client.get_api_version = (lambda: '33') + configure(ctx=_ctx) + assert _ctx.instance.runtime_properties['resource_id'] == 'foo' + + +@patch('pyvcloud.vcd.vapp.VApp') +@patch('cloudify_vcd.legacy.compute.tasks.VCloudVM') +@patch('cloudify_vcd.legacy.utils.NamedTemporaryFile') +@patch('cloudify_vcd.legacy.utils.get_deployment_dir') +@patch('cloudify_vcd.legacy.decorators.get_last_task') +@patch('vcd_plugin_sdk.connection.Org', autospec=True) +@patch('vcd_plugin_sdk.connection.Client', autospec=True) +@patch('cloudify_vcd.legacy.compute.tasks.get_last_task') +@patch('cloudify_vcd.legacy.decorators.check_if_task_successful', + return_value=True) +@patch('cloudify_vcd.legacy.compute.tasks.check_if_task_successful', + return_value=True) +def test_configure_vm_with_two_ports(*_, **__): + server_node_props = get_vm_ctx(resource_id='foo') + _port1_ctx = create_ctx( + node_id='port1', + node_type=[ + 'cloudify.nodes.Port', + 'cloudify.vcloud.nodes.Port' + ], + node_properties=get_port_ctx(), + runtime_props={ + 'network_name': 'port1_network', + 'port': { + 'is_primary': False, + 'is_connected': True, + 'ip_address_mode': 'manual', + 'adapter_type': 'VXNET3', + 'network_name': 'port1_network', + 'ip_address': '10.10.10.1' + } + } + ) + _port2_ctx = create_ctx( + node_id='port2', + node_type=[ + 'cloudify.nodes.Port', + 'cloudify.vcloud.nodes.Port' + ], + node_properties=get_port_ctx('2', False), + runtime_props={ + 'network_name': 'port2_network', + 'port': { + 'is_primary': False, + 'is_connected': True, + 'ip_address_mode': 'manual', + 'adapter_type': 'VXNET3', + 'network_name': 'port2_network', + 'ip_address': '10.10.10.2' + } + } + ) + rels = [ + MagicMock( + name='port1', + target=_port1_ctx, + type_hierarchy=[ + 'cloudify.vcloud.server_connected_to_port', + 'cloudify.relationships.connected_to' + ] + ), + MagicMock( + name='port2', + target=_port2_ctx, + type_hierarchy=[ + 'cloudify.vcloud.server_connected_to_port', + 'cloudify.relationships.connected_to' + ] + ), + ] + _ctx = create_ctx( + node_id='server', + node_type=[ + 'cloudify.nodes.Compute', + 'cloudify.vcloud.nodes.Server' + ], + node_properties=server_node_props, + relationships=rels, + operation_name='cloudify.interfaces.lifecycle.configure' + ) + current_ctx.set(_ctx) + with patch('vcd_plugin_sdk.resources.base.VDC') as vdc: + vdc.client.get_api_version = (lambda: '33') + configure(ctx=_ctx) + # assert _ctx.instance.runtime_properties['resource_id'] == 'foo' + assert _ctx.instance.runtime_properties['server']['network'] == \ + 'port1_network' + assert _ctx.instance.runtime_properties['server']['ip_address'] == \ + '10.10.10.1' + + +@patch('pyvcloud.vcd.vapp.VApp') +@patch('cloudify_vcd.legacy.compute.tasks.VCloudVM') +@patch('cloudify_vcd.legacy.utils.NamedTemporaryFile') +@patch('cloudify_vcd.legacy.utils.get_deployment_dir') +@patch('cloudify_vcd.legacy.decorators.get_last_task') +@patch('vcd_plugin_sdk.connection.Org', autospec=True) +@patch('vcd_plugin_sdk.connection.Client', autospec=True) +@patch('cloudify_vcd.legacy.compute.tasks.get_last_task') +@patch('cloudify_vcd.legacy.decorators.check_if_task_successful', + return_value=True) +@patch('cloudify_vcd.legacy.compute.tasks.check_if_task_successful', + return_value=True) +def test_configure_vm_with_two_ports_and_network_name(*_, **__): + server_node_props = get_vm_ctx(resource_id='foo') + _port1_props = get_port_ctx() + _port1_props['port']['network'] = 'port1_port_network' + _port1_ctx = create_ctx( + node_id='port1', + node_type=[ + 'cloudify.nodes.Port', + 'cloudify.vcloud.nodes.Port' + ], + node_properties=_port1_props, + runtime_props={ + 'network_name': 'port1_network', + 'port': { + 'is_primary': False, + 'is_connected': True, + 'ip_address_mode': 'manual', + 'adapter_type': 'VXNET3', + 'network_name': 'port1_network', + 'ip_address': '10.10.10.5' + } + } + ) + _port2_ctx = create_ctx( + node_id='port2', + node_type=[ + 'cloudify.nodes.Port', + 'cloudify.vcloud.nodes.Port' + ], + node_properties=get_port_ctx('2', False), + runtime_props={ + 'network_name': 'port2_network', + 'port': { + 'is_primary': False, + 'is_connected': True, + 'ip_address_mode': 'manual', + 'adapter_type': 'VXNET3', + 'network_name': 'port1_network', + 'ip_address': '10.10.10.2' + } + } + ) + rels = [ + MagicMock( + name='port1', + target=_port1_ctx, + type_hierarchy=[ + 'cloudify.vcloud.server_connected_to_port', + 'cloudify.relationships.connected_to' + ] + ), + MagicMock( + name='port2', + target=_port2_ctx, + type_hierarchy=[ + 'cloudify.vcloud.server_connected_to_port', + 'cloudify.relationships.connected_to' + ] + ), + ] + _ctx = create_ctx( + node_id='server', + node_type=[ + 'cloudify.nodes.Compute', + 'cloudify.vcloud.nodes.Server' + ], + node_properties=server_node_props, + relationships=rels + ) + current_ctx.set(_ctx) + with patch('vcd_plugin_sdk.resources.base.VDC') as vdc: + vdc.client.get_api_version = (lambda: '33') + configure(ctx=_ctx) + # assert _ctx.instance.runtime_properties['resource_id'] == 'foo' + assert _ctx.instance.runtime_properties['server']['network'] == \ + 'port1_network' + assert _ctx.instance.runtime_properties['server']['ip_address'] == \ + '10.10.10.5' + + +@patch('pyvcloud.vcd.vapp.VApp.get_vm') +@patch('cloudify_vcd.legacy.utils.NamedTemporaryFile') +@patch('cloudify_vcd.legacy.utils.get_deployment_dir') +@patch('cloudify_vcd.legacy.decorators.get_last_task') +@patch('vcd_plugin_sdk.connection.Org', autospec=True) +@patch('pyvcloud.vcd.vapp.VApp.connect_org_vdc_network') +@patch('cloudify_vcd.legacy.compute.tasks.get_last_task') +@patch('vcd_plugin_sdk.connection.Client', autospec=True) +@patch('cloudify_vcd.legacy.decorators.check_if_task_successful', + return_value=True) +def test_configure_vm_port_no_primary_port(*_, **__): + server_node_props = get_vm_ctx(resource_id='foo') + _port1_ctx = create_ctx( + node_id='port1', + node_type=[ + 'cloudify.nodes.Port', + 'cloudify.vcloud.nodes.Port' + ], + node_properties=get_port_ctx(primary=False), + runtime_props={ + 'network_name': 'port1_network', + 'port': { + 'is_primary': False, + 'is_connected': True, + 'ip_address_mode': 'manual', + 'adapter_type': 'VXNET3', + 'network_name': 'port1_network', + 'ip_address': '10.10.10.1' + } + } + ) + _port2_ctx = create_ctx( + node_id='port2', + node_type=[ + 'cloudify.nodes.Port', + 'cloudify.vcloud.nodes.Port' + ], + node_properties=get_port_ctx('2', False), + runtime_props={ + 'network_name': 'port2_network', + 'port': { + 'is_primary': False, + 'is_connected': True, + 'ip_address_mode': 'manual', + 'adapter_type': 'VXNET3', + 'network_name': 'port2_network', + 'ip_address': '10.10.10.2' + } + } + ) + rels = [ + MagicMock( + name='port1', + target=_port1_ctx, + type_hierarchy=[ + 'cloudify.vcloud.server_connected_to_port', + 'cloudify.relationships.connected_to' + ] + ), + MagicMock( + name='port2', + target=_port2_ctx, + type_hierarchy=[ + 'cloudify.vcloud.server_connected_to_port', + 'cloudify.relationships.connected_to' + ] + ), + ] + _ctx = create_ctx( + node_id='server', + node_type=[ + 'cloudify.nodes.Compute', + 'cloudify.vcloud.nodes.Server' + ], + node_properties=server_node_props, + relationships=rels + ) + current_ctx.set(_ctx) + with patch('vcd_plugin_sdk.resources.base.VDC') as vdc: + vdc.client.get_api_version = (lambda: '33') + configure(ctx=_ctx) + assert _ctx.instance.runtime_properties['resource_id'] == 'foo' + assert _ctx.instance.runtime_properties['server']['network'] == \ + 'port2_network' + assert _ctx.instance.runtime_properties['server']['ip_address'] == \ + '10.10.10.2' + + +@patch('pyvcloud.vcd.vapp.VApp.get_vm') +@patch('cloudify_vcd.legacy.utils.NamedTemporaryFile') +@patch('cloudify_vcd.legacy.utils.get_deployment_dir') +@patch('cloudify_vcd.legacy.decorators.get_last_task') +@patch('vcd_plugin_sdk.connection.Org', autospec=True) +@patch('vcd_plugin_sdk.connection.Client', autospec=True) +@patch('cloudify_vcd.legacy.decorators.check_if_task_successful', + return_value=True) +def test_start_vm(*_, **__): + server_node_props = get_vm_ctx(resource_id='foo') + _ctx = create_ctx( + node_id='external_proxy', + node_type=[ + 'cloudify.nodes.Compute', + 'cloudify.vcloud.nodes.Server' + ], + node_properties=server_node_props) + current_ctx.set(_ctx) + with patch('vcd_plugin_sdk.resources.base.VDC') as vdc: + vdc.client.get_api_version = (lambda: '33') + start(ctx=_ctx) + assert _ctx.instance.runtime_properties['resource_id'] == 'foo' + + +@patch('pyvcloud.vcd.vapp.VApp.get_vm') +@patch('cloudify_vcd.legacy.utils.NamedTemporaryFile') +@patch('cloudify_vcd.legacy.utils.get_deployment_dir') +@patch('cloudify_vcd.legacy.decorators.get_last_task') +@patch('vcd_plugin_sdk.connection.Org', autospec=True) +@patch('vcd_plugin_sdk.connection.Client', autospec=True) +@patch('cloudify_vcd.legacy.decorators.check_if_task_successful', + return_value=True) +def test_stop_vm(*_, **__): + server_node_props = get_vm_ctx(resource_id='foo') + _ctx = create_ctx( + node_id='external_proxy', + node_type=[ + 'cloudify.nodes.Compute', + 'cloudify.vcloud.nodes.Server' + ], + node_properties=server_node_props) + current_ctx.set(_ctx) + with patch('vcd_plugin_sdk.resources.base.VDC') as vdc: + vdc.client.get_api_version = (lambda: '33') + stop(ctx=_ctx) + assert _ctx.instance.runtime_properties['resource_id'] == 'foo' + + +@patch('cloudify_vcd.legacy.utils.NamedTemporaryFile') +@patch('cloudify_vcd.legacy.utils.get_deployment_dir') +@patch('cloudify_vcd.legacy.decorators.get_last_task') +@patch('vcd_plugin_sdk.connection.Org', autospec=True) +@patch('vcd_plugin_sdk.resources.vapp.VCloudVM.get_vm') +@patch('vcd_plugin_sdk.connection.Client', autospec=True) +@patch('vcd_plugin_sdk.resources.vapp.VCloudVM.exists', return_value=True) +@patch('cloudify_vcd.legacy.decorators.check_if_task_successful', + return_value=True) +def test_delete_vm(*_, **__): + server_node_props = get_vm_ctx(resource_id='foo') + _ctx = create_ctx( + node_id='external_proxy', + node_type=[ + 'cloudify.nodes.Compute', + 'cloudify.vcloud.nodes.Server' + ], + node_properties=server_node_props) + current_ctx.set(_ctx) + with patch('vcd_plugin_sdk.resources.base.VDC') as vdc: + vdc.client.get_api_version = (lambda: '33') + delete(ctx=_ctx) + assert _ctx.instance.runtime_properties['resource_id'] == 'foo'