From 825a98213a442bc36c803d5b0f1a73b76034e070 Mon Sep 17 00:00:00 2001 From: Me Date: Thu, 24 Oct 2019 13:50:36 +0300 Subject: [PATCH 1/9] Update all operation to support resume workflow --- CHANGELOG.txt | 2 ++ cinder_plugin/volume.py | 8 +++---- glance_plugin/image.py | 8 +++---- keystone_plugin/project.py | 8 +++---- keystone_plugin/user.py | 6 +++--- neutron_plugin/floatingip.py | 10 ++++----- neutron_plugin/network.py | 10 ++++----- neutron_plugin/port.py | 14 ++++++------- neutron_plugin/rbac_policy.py | 8 +++---- neutron_plugin/router.py | 18 ++++++++-------- neutron_plugin/security_group.py | 6 +++--- neutron_plugin/subnet.py | 6 +++--- nova_plugin/flavor.py | 4 ++-- nova_plugin/floatingip.py | 6 +++--- nova_plugin/host_aggregate.py | 12 +++++------ nova_plugin/keypair.py | 6 +++--- nova_plugin/security_group.py | 6 +++--- nova_plugin/server.py | 36 ++++++++++++++++---------------- nova_plugin/server_group.py | 6 +++--- plugin.yaml | 2 +- setup.py | 2 +- 21 files changed, 93 insertions(+), 91 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index fc3dadca..4bfa228f 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,5 @@ +2.14.13: + - add resumable on operations. 2.14.12: - expose ipv4 and ipv6 addresses as runtime properties for port. 2.14.11: diff --git a/cinder_plugin/volume.py b/cinder_plugin/volume.py index 674a82f3..a096b310 100644 --- a/cinder_plugin/volume.py +++ b/cinder_plugin/volume.py @@ -65,7 +65,7 @@ def _set_volume_runtime_properties(volume): ctx.node.properties.get('boot', False) -@operation +@operation(resumable=True) @with_cinder_client def create(cinder_client, args={}, @@ -158,7 +158,7 @@ def _delete_backup(cinder_client, search_opts): retry_after=30) -@operation +@operation(resumable=True) @with_cinder_client def delete(cinder_client, **kwargs): # seach snapshots for volume @@ -279,14 +279,14 @@ def snapshot_delete(cinder_client, **kwargs): _delete_snapshot(cinder_client, search_opts) -@operation +@operation(resumable=True) @with_cinder_client def creation_validation(cinder_client, **kwargs): validate_resource(ctx, cinder_client, VOLUME_OPENSTACK_TYPE, VOLUME_OPENSTACK_ID_KEY) -@operation +@operation(resumable=True) @with_cinder_client def list_volumes(cinder_client, args, **kwargs): volume_list = cinder_client.volumes.list(**args) diff --git a/glance_plugin/image.py b/glance_plugin/image.py index 303f8782..7d4e9116 100644 --- a/glance_plugin/image.py +++ b/glance_plugin/image.py @@ -39,7 +39,7 @@ REQUIRED_PROPERTIES = ['container_format', 'disk_format'] -@operation +@operation(resumable=True) @with_glance_client def create(glance_client, args, **kwargs): if use_external_resource(ctx, glance_client, IMAGE_OPENSTACK_TYPE): @@ -72,7 +72,7 @@ def _get_image_by_ctx(glance_client, ctx): image_id=get_openstack_id(ctx)) -@operation +@operation(resumable=True) @with_glance_client def start(glance_client, start_retry_interval, **kwargs): img = _get_image_by_ctx(glance_client, ctx) @@ -82,7 +82,7 @@ def start(glance_client, start_retry_interval, **kwargs): retry_after=start_retry_interval) -@operation +@operation(resumable=True) @with_glance_client def delete(glance_client, **kwargs): _remove_protected(glance_client) @@ -107,7 +107,7 @@ def update(glance_client, args, **kwargs): set_openstack_runtime_properties(ctx, image, IMAGE_OPENSTACK_TYPE) -@operation +@operation(resumable=True) @with_glance_client def creation_validation(glance_client, **kwargs): validate_resource(ctx, glance_client, IMAGE_OPENSTACK_TYPE) diff --git a/keystone_plugin/project.py b/keystone_plugin/project.py index 1d0ffc08..1fc25d2d 100644 --- a/keystone_plugin/project.py +++ b/keystone_plugin/project.py @@ -46,7 +46,7 @@ ROLES = 'roles' -@operation +@operation(resumable=True) @with_keystone_client def create(keystone_client, args, **kwargs): if use_external_resource(ctx, keystone_client, PROJECT_OPENSTACK_TYPE): @@ -61,7 +61,7 @@ def create(keystone_client, args, **kwargs): set_openstack_runtime_properties(ctx, project, PROJECT_OPENSTACK_TYPE) -@operation +@operation(resumable=True) def start(quota_dict, **kwargs): users = ctx.node.properties[USERS] validate_users(users, **kwargs) @@ -71,7 +71,7 @@ def start(quota_dict, **kwargs): update_project_quota(quota=quota, **kwargs) -@operation +@operation(resumable=True) @with_keystone_client @with_nova_client @with_cinder_client @@ -87,7 +87,7 @@ def delete(keystone_client, nova_client, cinder_client, RUNTIME_PROPERTIES_KEYS) -@operation +@operation(resumable=True) @with_keystone_client def creation_validation(keystone_client, **kwargs): validate_resource(ctx, keystone_client, PROJECT_OPENSTACK_TYPE) diff --git a/keystone_plugin/user.py b/keystone_plugin/user.py index 0ac3ccd0..a8002902 100644 --- a/keystone_plugin/user.py +++ b/keystone_plugin/user.py @@ -30,7 +30,7 @@ RUNTIME_PROPERTIES_KEYS = COMMON_RUNTIME_PROPERTIES_KEYS -@operation +@operation(resumable=True) @with_keystone_client def create(keystone_client, args, **kwargs): if use_external_resource(ctx, keystone_client, USER_OPENSTACK_TYPE): @@ -42,14 +42,14 @@ def create(keystone_client, args, **kwargs): set_openstack_runtime_properties(ctx, user, USER_OPENSTACK_TYPE) -@operation +@operation(resumable=True) @with_keystone_client def delete(keystone_client, **kwargs): delete_resource_and_runtime_properties(ctx, keystone_client, RUNTIME_PROPERTIES_KEYS) -@operation +@operation(resumable=True) @with_keystone_client def update(keystone_client, args, **kwargs): user_dict = create_object_dict(ctx, USER_OPENSTACK_TYPE, args, {}) diff --git a/neutron_plugin/floatingip.py b/neutron_plugin/floatingip.py index d69ea13d..bf5cc67e 100644 --- a/neutron_plugin/floatingip.py +++ b/neutron_plugin/floatingip.py @@ -43,7 +43,7 @@ FLOATING_NETWORK_ERROR_SUFFIX -@operation +@operation(resumable=True) @with_neutron_client def create(neutron_client, args, **kwargs): @@ -96,7 +96,7 @@ def create(neutron_client, args, **kwargs): ctx.logger.info('Floating IP creation response: {0}'.format(fip)) -@operation +@operation(resumable=True) @with_neutron_client def delete(neutron_client, **kwargs): delete_floatingip(neutron_client) @@ -110,13 +110,13 @@ def list_floatingips(neutron_client, args, **kwargs): fip_list.get('floatingips', [])) -@operation +@operation(resumable=True) @with_neutron_client def creation_validation(neutron_client, **kwargs): floatingip_creation_validation(neutron_client, 'floating_ip_address') -@operation +@operation(resumable=True) @with_neutron_client def connect_port(neutron_client, **kwargs): if is_external_relationship_not_conditionally_created(ctx): @@ -129,7 +129,7 @@ def connect_port(neutron_client, **kwargs): floating_ip_id, {FLOATINGIP_OPENSTACK_TYPE: fip}) -@operation +@operation(resumable=True) @with_neutron_client def disconnect_port(neutron_client, **kwargs): if is_external_relationship(ctx): diff --git a/neutron_plugin/network.py b/neutron_plugin/network.py index ad7544d8..1661f6e3 100644 --- a/neutron_plugin/network.py +++ b/neutron_plugin/network.py @@ -37,7 +37,7 @@ RUNTIME_PROPERTIES_KEYS = COMMON_RUNTIME_PROPERTIES_KEYS -@operation +@operation(resumable=True) @with_neutron_client def create(neutron_client, args, **kwargs): @@ -53,7 +53,7 @@ def create(neutron_client, args, **kwargs): set_neutron_runtime_properties(ctx, net, NETWORK_OPENSTACK_TYPE) -@operation +@operation(resumable=True) @with_neutron_client def start(neutron_client, **kwargs): network_id = get_openstack_id(ctx) @@ -75,7 +75,7 @@ def start(neutron_client, **kwargs): }) -@operation +@operation(resumable=True) @with_neutron_client def stop(neutron_client, **kwargs): if is_external_resource(ctx): @@ -90,7 +90,7 @@ def stop(neutron_client, **kwargs): }) -@operation +@operation(resumable=True) @with_neutron_client def delete(neutron_client, **kwargs): delete_resource_and_runtime_properties(ctx, neutron_client, @@ -105,7 +105,7 @@ def list_networks(neutron_client, args, **kwargs): net_list.get('networks', [])) -@operation +@operation(resumable=True) @with_neutron_client def creation_validation(neutron_client, **kwargs): validate_resource(ctx, neutron_client, NETWORK_OPENSTACK_TYPE) diff --git a/neutron_plugin/port.py b/neutron_plugin/port.py index 25735fdf..26a7bb7f 100644 --- a/neutron_plugin/port.py +++ b/neutron_plugin/port.py @@ -149,7 +149,7 @@ def _export_ips_to_port_instance(port): ctx.instance.runtime_properties['ipv6_address'] = '' -@operation +@operation(resumable=True) @with_neutron_client def create(neutron_client, args, **kwargs): @@ -188,7 +188,7 @@ def create(neutron_client, args, **kwargs): _export_ips_to_port_instance(p) -@operation +@operation(resumable=True) @with_nova_client @with_neutron_client def attach(nova_client, neutron_client, **kwargs): @@ -249,7 +249,7 @@ def _port_delete(neutron_client, port_id, ext_port): ctx.logger.info("Applied on remove: {}".format(repr(change))) -@operation +@operation(resumable=True) @with_neutron_client def delete(neutron_client, **kwargs): try: @@ -270,7 +270,7 @@ def delete(neutron_client, **kwargs): raise -@operation +@operation(resumable=True) @with_nova_client @with_neutron_client def detach(nova_client, neutron_client, **kwargs): @@ -311,7 +311,7 @@ def detach(nova_client, neutron_client, **kwargs): ctx.logger.info('Successfully detached port {0}'.format(port_id)) -@operation +@operation(resumable=True) @with_neutron_client def connect_security_group(neutron_client, **kwargs): port_id = get_openstack_id(ctx.source) @@ -355,7 +355,7 @@ def connect_security_group(neutron_client, **kwargs): ) -@operation +@operation(resumable=True) @with_neutron_client def disconnect_security_group(neutron_client, **kwargs): port_id = get_openstack_id(ctx.source) @@ -393,7 +393,7 @@ def list_ports(neutron_client, args, **kwargs): port_list.get('ports', [])) -@operation +@operation(resumable=True) @with_neutron_client def creation_validation(neutron_client, **kwargs): validate_resource(ctx, neutron_client, PORT_OPENSTACK_TYPE) diff --git a/neutron_plugin/rbac_policy.py b/neutron_plugin/rbac_policy.py index 9bc07467..1d26e39f 100644 --- a/neutron_plugin/rbac_policy.py +++ b/neutron_plugin/rbac_policy.py @@ -121,7 +121,7 @@ def create_rbac_policy_object_dict(ctx, args): return rbac_policy -@operation +@operation(resumable=True) @with_neutron_client def create(neutron_client, args, **kwargs): if use_external_resource(ctx, neutron_client, RBAC_POLICY_OPENSTACK_TYPE): @@ -141,7 +141,7 @@ def create(neutron_client, args, **kwargs): set_neutron_runtime_properties(ctx, rp, RBAC_POLICY_OPENSTACK_TYPE) -@operation +@operation(resumable=True) @with_neutron_client def delete(neutron_client, **kwargs): delete_resource_and_runtime_properties( @@ -151,7 +151,7 @@ def delete(neutron_client, **kwargs): ) -@operation +@operation(resumable=True) @with_neutron_client def list_rbac_policies(neutron_client, args, **kwargs): rbac_policies = neutron_client.list_rbac_policies(**args) @@ -162,7 +162,7 @@ def list_rbac_policies(neutron_client, args, **kwargs): ) -@operation +@operation(resumable=True) @with_neutron_client def find_and_delete(neutron_client, args, **kwargs): reference_rbac_policy = create_rbac_policy_object_dict(ctx, args) diff --git a/neutron_plugin/router.py b/neutron_plugin/router.py index 3e0307f6..584286f4 100644 --- a/neutron_plugin/router.py +++ b/neutron_plugin/router.py @@ -97,7 +97,7 @@ def dict_merge(a, b): return neutron_client.update_router(router_id, new_router) -@operation +@operation(resumable=True) @with_neutron_client def create(neutron_client, args, **kwargs): @@ -133,7 +133,7 @@ def create(neutron_client, args, **kwargs): set_neutron_runtime_properties(ctx, r, ROUTER_OPENSTACK_TYPE) -@operation +@operation(resumable=True) @with_neutron_client def update(neutron_client, args, **kwargs): if not args: @@ -151,7 +151,7 @@ def update(neutron_client, args, **kwargs): return neutron_client.update_router(router_id, args) -@operation +@operation(resumable=True) @with_neutron_client def update_routes(neutron_client, args, **kwargs): routes = args.get(ROUTES_OPENSTACK_TYPE) @@ -179,7 +179,7 @@ def update_routes(neutron_client, args, **kwargs): 'Failed while trying to retrieve router instance') -@operation +@operation(resumable=True) @with_neutron_client def add_routes(neutron_client, args, **kwargs): @@ -216,7 +216,7 @@ def add_routes(neutron_client, args, **kwargs): 'Failed while trying to retrieve router instance') -@operation +@operation(resumable=True) @with_neutron_client def connect_subnet(neutron_client, **kwargs): router_id = get_openstack_id(ctx.target) @@ -236,7 +236,7 @@ def connect_subnet(neutron_client, **kwargs): neutron_client.add_interface_router(router_id, {'subnet_id': subnet_id}) -@operation +@operation(resumable=True) @with_neutron_client def disconnect_subnet(neutron_client, **kwargs): if is_external_relationship(ctx): @@ -256,14 +256,14 @@ def disconnect_subnet(neutron_client, **kwargs): ) -@operation +@operation(resumable=True) @with_neutron_client def delete(neutron_client, **kwargs): delete_resource_and_runtime_properties(ctx, neutron_client, RUNTIME_PROPERTIES_KEYS) -@operation +@operation(resumable=True) @with_neutron_client def delete_routes(neutron_client, **kwargs): @@ -279,7 +279,7 @@ def list_routers(neutron_client, args, **kwargs): router_list.get('routers', [])) -@operation +@operation(resumable=True) @with_neutron_client def creation_validation(neutron_client, **kwargs): validate_resource(ctx, neutron_client, ROUTER_OPENSTACK_TYPE) diff --git a/neutron_plugin/security_group.py b/neutron_plugin/security_group.py index 51849c87..f68cb49f 100644 --- a/neutron_plugin/security_group.py +++ b/neutron_plugin/security_group.py @@ -49,7 +49,7 @@ SG_OPENSTACK_TYPE = 'security_group' -@operation +@operation(resumable=True) @with_neutron_client def create( neutron_client, args, @@ -111,7 +111,7 @@ def create( raise -@operation +@operation(resumable=True) @with_neutron_client def delete(neutron_client, **kwargs): delete_sg(neutron_client) @@ -125,7 +125,7 @@ def list_security_groups(neutron_client, args, **kwargs): sg_list.get('security_groups', [])) -@operation +@operation(resumable=True) @with_neutron_client def creation_validation(neutron_client, **kwargs): sg_creation_validation(neutron_client, 'remote_ip_prefix') diff --git a/neutron_plugin/subnet.py b/neutron_plugin/subnet.py index 356916d4..24e11ed1 100644 --- a/neutron_plugin/subnet.py +++ b/neutron_plugin/subnet.py @@ -41,7 +41,7 @@ RUNTIME_PROPERTIES_KEYS = COMMON_RUNTIME_PROPERTIES_KEYS -@operation +@operation(resumable=True) @with_neutron_client def create(neutron_client, args, **kwargs): @@ -77,7 +77,7 @@ def create(neutron_client, args, **kwargs): set_neutron_runtime_properties(ctx, s, SUBNET_OPENSTACK_TYPE) -@operation +@operation(resumable=True) @with_neutron_client def delete(neutron_client, **kwargs): delete_resource_and_runtime_properties(ctx, neutron_client, @@ -92,7 +92,7 @@ def list_subnets(neutron_client, args, **kwargs): subnet_list.get('subnets', [])) -@operation +@operation(resumable=True) @with_neutron_client def creation_validation(neutron_client, args, **kwargs): validate_resource(ctx, neutron_client, SUBNET_OPENSTACK_TYPE) diff --git a/nova_plugin/flavor.py b/nova_plugin/flavor.py index 1c08f803..1bcaa4db 100644 --- a/nova_plugin/flavor.py +++ b/nova_plugin/flavor.py @@ -63,7 +63,7 @@ def _set_tenants_access(ctx, nova_client, flavor): ctx.instance.runtime_properties[TENANTS_PROPERTY] = tenants -@operation +@operation(resumable=True) @with_nova_client def create(nova_client, args, **kwargs): if use_external_resource(ctx, nova_client, FLAVOR_OPENSTACK_TYPE): @@ -79,7 +79,7 @@ def create(nova_client, args, **kwargs): _set_tenants_access(ctx, nova_client, flavor) -@operation +@operation(resumable=True) @with_nova_client def delete(nova_client, **kwargs): delete_resource_and_runtime_properties( diff --git a/nova_plugin/floatingip.py b/nova_plugin/floatingip.py index e770c540..dd4cd3d5 100644 --- a/nova_plugin/floatingip.py +++ b/nova_plugin/floatingip.py @@ -31,7 +31,7 @@ # nova..findall) and lists, which won't return such unallocated # resources. -@operation +@operation(resumable=True) @with_nova_client def create(nova_client, args, **kwargs): @@ -48,13 +48,13 @@ def create(nova_client, args, **kwargs): set_floatingip_runtime_properties(fip.id, fip.ip) -@operation +@operation(resumable=True) @with_nova_client def delete(nova_client, **kwargs): delete_floatingip(nova_client) -@operation +@operation(resumable=True) @with_nova_client def creation_validation(nova_client, **kwargs): floatingip_creation_validation(nova_client, 'ip') diff --git a/nova_plugin/host_aggregate.py b/nova_plugin/host_aggregate.py index 5222603e..c8baa951 100644 --- a/nova_plugin/host_aggregate.py +++ b/nova_plugin/host_aggregate.py @@ -83,7 +83,7 @@ def _remove_hosts(ctx, nova_client, host_aggregate_id, hosts): delete_runtime_properties(ctx, HOSTS_PROPERTY) -@operation +@operation(resumable=True) @with_nova_client def create(nova_client, args, **kwargs): if use_external_resource(ctx, nova_client, HOST_AGGREGATE_OPENSTACK_TYPE): @@ -107,7 +107,7 @@ def create(nova_client, args, **kwargs): ) -@operation +@operation(resumable=True) @with_nova_client def delete(nova_client, **kwargs): if not is_external_resource(ctx): @@ -129,7 +129,7 @@ def delete(nova_client, **kwargs): ) -@operation +@operation(resumable=True) @with_nova_client def update(nova_client, args, **kwargs): if HOST_AGGREGATE_OPENSTACK_TYPE in args: @@ -147,7 +147,7 @@ def update(nova_client, args, **kwargs): _set_metadata(ctx, nova_client, get_openstack_id(ctx), args) -@operation +@operation(resumable=True) @with_nova_client def list_host_aggregates(nova_client, **kwargs): host_aggregates_list = nova_client.aggregates.list() @@ -159,13 +159,13 @@ def list_host_aggregates(nova_client, **kwargs): ) -@operation +@operation(resumable=True) @with_nova_client def add_hosts(nova_client, hosts, **kwargs): _add_hosts(ctx, nova_client, get_openstack_id(ctx), hosts) -@operation +@operation(resumable=True) @with_nova_client def remove_hosts(nova_client, hosts, **kwargs): _remove_hosts(ctx, nova_client, get_openstack_id(ctx), hosts) diff --git a/nova_plugin/keypair.py b/nova_plugin/keypair.py index 4c0298bb..47b111c3 100644 --- a/nova_plugin/keypair.py +++ b/nova_plugin/keypair.py @@ -41,7 +41,7 @@ PRIVATE_KEY_PATH_PROP = 'private_key_path' -@operation +@operation(resumable=True) @with_nova_client def create(nova_client, args, **kwargs): @@ -86,7 +86,7 @@ def create(nova_client, args, **kwargs): raise -@operation +@operation(resumable=True) @with_nova_client def delete(nova_client, **kwargs): if not is_external_resource(ctx): @@ -108,7 +108,7 @@ def list_keypairs(nova_client, args, **kwargs): add_list_to_runtime_properties(ctx, KEYPAIR_OPENSTACK_TYPE, keypair_list) -@operation +@operation(resumable=True) @with_nova_client def creation_validation(nova_client, **kwargs): diff --git a/nova_plugin/security_group.py b/nova_plugin/security_group.py index 283eae85..6d6ef335 100644 --- a/nova_plugin/security_group.py +++ b/nova_plugin/security_group.py @@ -31,7 +31,7 @@ ) -@operation +@operation(resumable=True) @with_nova_client def create(nova_client, args, **kwargs): @@ -69,13 +69,13 @@ def create(nova_client, args, **kwargs): raise -@operation +@operation(resumable=True) @with_nova_client def delete(nova_client, **kwargs): delete_sg(nova_client) -@operation +@operation(resumable=True) @with_nova_client def creation_validation(nova_client, **kwargs): sg_creation_validation(nova_client, 'cidr') diff --git a/nova_plugin/server.py b/nova_plugin/server.py index b707fe3f..17f3c999 100644 --- a/nova_plugin/server.py +++ b/nova_plugin/server.py @@ -257,7 +257,7 @@ def _handle_boot_volume(server, ctx): boot_volume.runtime_properties[OPENSTACK_AZ_PROPERTY] -@operation +@operation(resumable=True) @with_nova_client @with_neutron_client def create(nova_client, neutron_client, args, **kwargs): @@ -412,7 +412,7 @@ def get_network(port_id): return map(get_network, port_ids) -@operation +@operation(resumable=True) @with_nova_client def start(nova_client, start_retry_interval=30, @@ -479,7 +479,7 @@ def start(nova_client, server_state_fault)) -@operation +@operation(resumable=True) @with_nova_client def stop(nova_client, **kwargs): """ @@ -536,7 +536,7 @@ def _server_start(nova_client, server): ctx.logger.info('Server is already started?') -@operation +@operation(resumable=True) @with_nova_client def reboot(nova_client, reboot_type='soft', **kwargs): @@ -612,7 +612,7 @@ def _check_finished_upload(nova_client, server, waiting_list): retry_after=30) -@operation +@operation(resumable=True) @with_nova_client def freeze_suspend(nova_client, **kwargs): """ @@ -623,7 +623,7 @@ def freeze_suspend(nova_client, **kwargs): _server_suspend(nova_client, server) -@operation +@operation(resumable=True) @with_nova_client def freeze_resume(nova_client, **kwargs): """ @@ -634,7 +634,7 @@ def freeze_resume(nova_client, **kwargs): _server_resume(nova_client, server) -@operation +@operation(resumable=True) @with_nova_client @with_glance_client def snapshot_create(nova_client, glance_client, **kwargs): @@ -692,7 +692,7 @@ def _get_image(glance_client, snapshot_name, snapshot_incremental): return None, None -@operation +@operation(resumable=True) @with_nova_client @with_glance_client def snapshot_apply(nova_client, glance_client, **kwargs): @@ -759,7 +759,7 @@ def _image_delete(glance_client, snapshot_name, snapshot_incremental): retry_after=30) -@operation +@operation(resumable=True) @with_nova_client @with_glance_client def snapshot_delete(nova_client, glance_client, **kwargs): @@ -781,7 +781,7 @@ def snapshot_delete(nova_client, glance_client, **kwargs): return _image_delete(glance_client, snapshot_name, snapshot_incremental) -@operation +@operation(resumable=True) @with_nova_client def delete(nova_client, **kwargs): if not is_external_resource(ctx): @@ -796,7 +796,7 @@ def delete(nova_client, **kwargs): delete_runtime_properties(ctx, RUNTIME_PROPERTIES_KEYS) -@operation +@operation(resumable=True) @with_nova_client def list_servers(nova_client, args, **kwargs): server_list = nova_client.servers.list(**args) @@ -896,7 +896,7 @@ def _get_mgmt_ip(net_ips): ctx.instance.runtime_properties[IPV6_PROPERTY] = '' -@operation +@operation(resumable=True) @with_nova_client def connect_floatingip(nova_client, fixed_ip='', **kwargs): server_id = get_openstack_id(ctx.source) @@ -925,7 +925,7 @@ def connect_floatingip(nova_client, fixed_ip='', **kwargs): .format(floating_ip_address, server_id)) -@operation +@operation(resumable=True) @with_nova_client @with_neutron_client def disconnect_floatingip(nova_client, neutron_client, **kwargs): @@ -945,7 +945,7 @@ def disconnect_floatingip(nova_client, neutron_client, **kwargs): .format(server_floating_ip['floating_ip_address'])) -@operation +@operation(resumable=True) @with_nova_client def connect_security_group(nova_client, **kwargs): server_id = get_openstack_id(ctx.source) @@ -987,7 +987,7 @@ def group_matches(security_group): is_connected=True) -@operation +@operation(resumable=True) @with_nova_client def disconnect_security_group(nova_client, **kwargs): if is_external_relationship(ctx): @@ -1015,7 +1015,7 @@ def disconnect_security_group(nova_client, **kwargs): is_connected=False) -@operation +@operation(resumable=True) @with_nova_client @with_cinder_client def attach_volume(nova_client, @@ -1110,7 +1110,7 @@ def _detach_volume(nova_client, cinder_client, server_id, volume_id, timeout=status_timeout) -@operation +@operation(resumable=True) @with_nova_client @with_cinder_client def detach_volume(nova_client, @@ -1227,7 +1227,7 @@ def _get_properties_by_node_instance_id(node_instance_id): return node.properties -@operation +@operation(resumable=True) @with_nova_client def creation_validation(nova_client, args, **kwargs): diff --git a/nova_plugin/server_group.py b/nova_plugin/server_group.py index 4aefdda8..2fccce18 100644 --- a/nova_plugin/server_group.py +++ b/nova_plugin/server_group.py @@ -32,7 +32,7 @@ SERVER_GROUP_OPENSTACK_TYPE = 'server_group' -@operation +@operation(resumable=True) @with_nova_client def create(nova_client, args, **kwargs): if use_external_resource(ctx, nova_client, SERVER_GROUP_OPENSTACK_TYPE): @@ -50,7 +50,7 @@ def create(nova_client, args, **kwargs): SERVER_GROUP_OPENSTACK_TYPE) -@operation +@operation(resumable=True) @with_nova_client def delete(nova_client, **kwargs): if not is_external_resource(ctx): @@ -72,7 +72,7 @@ def list_servergroups(nova_client, args, **kwargs): server_group_list) -@operation +@operation(resumable=True) @with_nova_client def creation_validation(nova_client, **kwargs): validate_resource(ctx, nova_client, SERVER_GROUP_OPENSTACK_TYPE) diff --git a/plugin.yaml b/plugin.yaml index 7fd972c0..904f6cee 100644 --- a/plugin.yaml +++ b/plugin.yaml @@ -7,7 +7,7 @@ plugins: executor: central_deployment_agent source: https://github.com/cloudify-cosmo/cloudify-openstack-plugin/archive/2.14.12.zip package_name: cloudify-openstack-plugin - package_version: '2.14.12' + package_version: '2.14.13' data_types: cloudify.openstack.types.custom_configuration: diff --git a/setup.py b/setup.py index ccd81af7..54f5b38f 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ setup( zip_safe=True, name='cloudify-openstack-plugin', - version='2.14.12', + version='2.14.13', author='Cloudify', author_email='hello@cloudify.co', packages=[ From 8ba638d6facd96946f512b9af80a51e1524a1702 Mon Sep 17 00:00:00 2001 From: Ahmad Musa Date: Thu, 24 Oct 2019 18:03:53 +0300 Subject: [PATCH 2/9] added missing operation decorater from some endpoints --- cinder_plugin/volume.py | 3 +++ keystone_plugin/project.py | 5 ++++- keystone_plugin/user.py | 2 +- neutron_plugin/floatingip.py | 1 + neutron_plugin/network.py | 1 + neutron_plugin/port.py | 1 + neutron_plugin/router.py | 1 + neutron_plugin/security_group.py | 1 + neutron_plugin/subnet.py | 1 + nova_plugin/flavor.py | 1 + nova_plugin/keypair.py | 1 + nova_plugin/server_group.py | 1 + plugin.yaml | 2 +- 13 files changed, 18 insertions(+), 3 deletions(-) diff --git a/cinder_plugin/volume.py b/cinder_plugin/volume.py index a096b310..6d6030ec 100644 --- a/cinder_plugin/volume.py +++ b/cinder_plugin/volume.py @@ -204,6 +204,7 @@ def _get_snapshot_name(ctx, kwargs): return "vol-{}-{}".format(get_openstack_id(ctx), kwargs["snapshot_name"]) +@operation(resumable=True) @with_cinder_client def snapshot_create(cinder_client, **kwargs): volume_id = get_openstack_id(ctx) @@ -224,6 +225,7 @@ def snapshot_create(cinder_client, **kwargs): metadata=None) +@operation(resumable=True) @with_cinder_client def snapshot_apply(cinder_client, **kwargs): volume_id = get_openstack_id(ctx) @@ -254,6 +256,7 @@ def snapshot_apply(cinder_client, **kwargs): ctx.logger.error("Apply snapshot is unsuported") +@operation(resumable=True) @with_cinder_client def snapshot_delete(cinder_client, **kwargs): volume_id = get_openstack_id(ctx) diff --git a/keystone_plugin/project.py b/keystone_plugin/project.py index 1fc25d2d..ad88f298 100644 --- a/keystone_plugin/project.py +++ b/keystone_plugin/project.py @@ -87,7 +87,6 @@ def delete(keystone_client, nova_client, cinder_client, RUNTIME_PROPERTIES_KEYS) -@operation(resumable=True) @with_keystone_client def creation_validation(keystone_client, **kwargs): validate_resource(ctx, keystone_client, PROJECT_OPENSTACK_TYPE) @@ -163,6 +162,7 @@ def delete_quota(project_id, quota, client, what_quota): 'Deleted {0} quota'.format(what_quota)) +@operation(resumable=True) @with_nova_client @with_neutron_client @with_cinder_client @@ -177,12 +177,14 @@ def update_project_quota(nova_client, update_quota(project_id, quota, cinder_client, CINDER) +@operation(resumable=True) @with_keystone_client def list_projects(keystone_client, args, **kwargs): projects_list = keystone_client.projects.list(**args) add_list_to_runtime_properties(ctx, PROJECT_OPENSTACK_TYPE, projects_list) +@operation(resumable=True) @with_nova_client @with_neutron_client @with_cinder_client @@ -198,6 +200,7 @@ def get_project_quota(nova_client, ctx.instance.runtime_properties[QUOTA] = quota +@operation(resumable=True) @with_keystone_client def update_project(keystone_client, args, **kwargs): diff --git a/keystone_plugin/user.py b/keystone_plugin/user.py index a8002902..b202dc8b 100644 --- a/keystone_plugin/user.py +++ b/keystone_plugin/user.py @@ -57,7 +57,7 @@ def update(keystone_client, args, **kwargs): user = keystone_client.users.update(**user_dict) set_openstack_runtime_properties(ctx, user, USER_OPENSTACK_TYPE) - +@operation(resumable=True) @with_keystone_client def list_users(keystone_client, args, **kwargs): users_list = keystone_client.users.list(**args) diff --git a/neutron_plugin/floatingip.py b/neutron_plugin/floatingip.py index bf5cc67e..5231a12a 100644 --- a/neutron_plugin/floatingip.py +++ b/neutron_plugin/floatingip.py @@ -102,6 +102,7 @@ def delete(neutron_client, **kwargs): delete_floatingip(neutron_client) +@operation(resumable=True) @with_neutron_client def list_floatingips(neutron_client, args, **kwargs): fip_list = neutron_client.list_floatingips(**args) diff --git a/neutron_plugin/network.py b/neutron_plugin/network.py index 1661f6e3..9eda3ad7 100644 --- a/neutron_plugin/network.py +++ b/neutron_plugin/network.py @@ -97,6 +97,7 @@ def delete(neutron_client, **kwargs): RUNTIME_PROPERTIES_KEYS) +@operation(resumable=True) @with_neutron_client def list_networks(neutron_client, args, **kwargs): net_list = neutron_client.list_networks(**args) diff --git a/neutron_plugin/port.py b/neutron_plugin/port.py index 26a7bb7f..4aa555f6 100644 --- a/neutron_plugin/port.py +++ b/neutron_plugin/port.py @@ -385,6 +385,7 @@ def disconnect_security_group(neutron_client, **kwargs): ) +@operation(resumable=True) @with_neutron_client def list_ports(neutron_client, args, **kwargs): port_list = neutron_client.list_ports(**args) diff --git a/neutron_plugin/router.py b/neutron_plugin/router.py index 584286f4..9ed82387 100644 --- a/neutron_plugin/router.py +++ b/neutron_plugin/router.py @@ -271,6 +271,7 @@ def delete_routes(neutron_client, **kwargs): delete_runtime_properties(ctx, RUNTIME_PROPERTIES_KEYS) +@operation(resumable=True) @with_neutron_client def list_routers(neutron_client, args, **kwargs): router_list = neutron_client.list_routers(**args) diff --git a/neutron_plugin/security_group.py b/neutron_plugin/security_group.py index f68cb49f..b42abd06 100644 --- a/neutron_plugin/security_group.py +++ b/neutron_plugin/security_group.py @@ -117,6 +117,7 @@ def delete(neutron_client, **kwargs): delete_sg(neutron_client) +@operation(resumable=True) @with_neutron_client def list_security_groups(neutron_client, args, **kwargs): sg_list = neutron_client.list_security_groups(**args) diff --git a/neutron_plugin/subnet.py b/neutron_plugin/subnet.py index 24e11ed1..ec81feff 100644 --- a/neutron_plugin/subnet.py +++ b/neutron_plugin/subnet.py @@ -84,6 +84,7 @@ def delete(neutron_client, **kwargs): RUNTIME_PROPERTIES_KEYS) +@operation(resumable=True) @with_neutron_client def list_subnets(neutron_client, args, **kwargs): subnet_list = neutron_client.list_subnets(**args) diff --git a/nova_plugin/flavor.py b/nova_plugin/flavor.py index 1bcaa4db..1100ff79 100644 --- a/nova_plugin/flavor.py +++ b/nova_plugin/flavor.py @@ -91,6 +91,7 @@ def delete(nova_client, **kwargs): delete_runtime_properties(ctx, [EXTRA_SPECS_PROPERTY, TENANTS_PROPERTY]) +@operation(resumable=True) @with_nova_client def list_flavors(nova_client, args, **kwargs): flavor_list = nova_client.flavors.list(**args) diff --git a/nova_plugin/keypair.py b/nova_plugin/keypair.py index 47b111c3..94e4ac49 100644 --- a/nova_plugin/keypair.py +++ b/nova_plugin/keypair.py @@ -102,6 +102,7 @@ def delete(nova_client, **kwargs): delete_runtime_properties(ctx, RUNTIME_PROPERTIES_KEYS) +@operation(resumable=True) @with_nova_client def list_keypairs(nova_client, args, **kwargs): keypair_list = nova_client.keypairs.list(**args) diff --git a/nova_plugin/server_group.py b/nova_plugin/server_group.py index 2fccce18..3d3af2ac 100644 --- a/nova_plugin/server_group.py +++ b/nova_plugin/server_group.py @@ -64,6 +64,7 @@ def delete(nova_client, **kwargs): delete_runtime_properties(ctx, RUNTIME_PROPERTIES_KEYS) +@operation(resumable=True) @with_nova_client def list_servergroups(nova_client, args, **kwargs): server_group_list = nova_client.server_groups.list(**args) diff --git a/plugin.yaml b/plugin.yaml index 904f6cee..77bfb1a9 100644 --- a/plugin.yaml +++ b/plugin.yaml @@ -5,7 +5,7 @@ plugins: openstack: executor: central_deployment_agent - source: https://github.com/cloudify-cosmo/cloudify-openstack-plugin/archive/2.14.12.zip + source: https://github.com/cloudify-cosmo/cloudify-openstack-plugin/archive/2.14.13.zip package_name: cloudify-openstack-plugin package_version: '2.14.13' From 899fa94545cede46c6bfd1fb116213a4d5a3ae7f Mon Sep 17 00:00:00 2001 From: Ahmad Musa Date: Mon, 28 Oct 2019 11:54:17 +0200 Subject: [PATCH 3/9] fix flake8 issue --- CHANGELOG.txt | 2 +- keystone_plugin/user.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 4bfa228f..5f29aa0f 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,5 +1,5 @@ 2.14.13: - - add resumable on operations. + - Add support for resumable actions. 2.14.12: - expose ipv4 and ipv6 addresses as runtime properties for port. 2.14.11: diff --git a/keystone_plugin/user.py b/keystone_plugin/user.py index b202dc8b..74cb18ba 100644 --- a/keystone_plugin/user.py +++ b/keystone_plugin/user.py @@ -57,6 +57,7 @@ def update(keystone_client, args, **kwargs): user = keystone_client.users.update(**user_dict) set_openstack_runtime_properties(ctx, user, USER_OPENSTACK_TYPE) + @operation(resumable=True) @with_keystone_client def list_users(keystone_client, args, **kwargs): From f8d6f730b2c7c084746c95004214a1b4e72f9bfb Mon Sep 17 00:00:00 2001 From: Ahmad Musa Date: Tue, 29 Oct 2019 16:46:14 +0200 Subject: [PATCH 4/9] added a new function to check if the operation should be executed or not given the runtime properties --- openstack_plugin_common/__init__.py | 137 ++++++++++++++++++++++------ 1 file changed, 107 insertions(+), 30 deletions(-) diff --git a/openstack_plugin_common/__init__.py b/openstack_plugin_common/__init__.py index d802efba..689bf3da 100644 --- a/openstack_plugin_common/__init__.py +++ b/openstack_plugin_common/__init__.py @@ -52,7 +52,7 @@ OPENSTACK_RESOURCE_PROPERTY = 'external_resource' # resource's parameters CONDITIONALLY_CREATED = 'conditionally_created' # resource was # conditionally created -CONFIG_RUNTIME_PROPERTY = CONFIG_PROPERTY # openstack configuration +CONFIG_RUNTIME_PROPERTY = CONFIG_PROPERTY # openstack configuration # operation inputs CONFIG_INPUT = CONFIG_PROPERTY @@ -99,6 +99,13 @@ 'glanceclient.v2.client'] } +CLOUDIFY_CREATE_OPERATION = 'cloudify.interfaces.lifecycle.create' +CLOUDIFY_CONFIGURE_OPERATION = 'cloudify.interfaces.lifecycle.configure' +CLOUDIFY_START_OPERATION = 'cloudify.interfaces.lifecycle.start' +CLOUDIFY_STOP_OPERATION = 'cloudify.interfaces.lifecycle.stop' +CLOUDIFY_DELETE_OPERATION = 'cloudify.interfaces.lifecycle.delete' +CLOUDIFY_UNLINK_OPERATION = 'cloudify.interfaces.relationship_lifecycle.unlink' + # TODO: Move this to cloudify-plugins-common (code freeze currently # in effect). @@ -108,6 +115,7 @@ class CloudifyCtxLogHandler(logging.Handler): A logger attached to this handler will result in logging being passed through to the Cloudify logger. """ + def __init__(self, ctx): """ Constructor. @@ -242,7 +250,7 @@ def get_attribute_of_connected_nodes_by_relationship_type(ctx, def get_relationships_by_openstack_type(ctx, type_name): return [rel for rel in ctx.instance.relationships if rel.target.instance.runtime_properties.get( - OPENSTACK_TYPE_PROPERTY) == type_name] + OPENSTACK_TYPE_PROPERTY) == type_name] def get_connected_nodes_by_openstack_type(ctx, type_name): @@ -299,7 +307,6 @@ def get_property(ctx, property_name, kwargs={}, default=None): def transform_resource_name(ctx, res): - if isinstance(res, basestring): res = {'name': res} @@ -320,7 +327,7 @@ def transform_resource_name(ctx, res): "already has this prefix".format(name, pfx)) else: ctx.logger.info("Transformed resource name '{0}' to '{1}'".format( - name, res['name'])) + name, res['name'])) return res['name'] @@ -341,7 +348,6 @@ def _get_resource_by_name_or_id_from_ctx(ctx, name_field_name, openstack_type, def get_resource_by_name_or_id( resource_id, openstack_type, sugared_client, raise_if_not_found=True, name_field_name='name'): - # search for resource by name (or name-equivalent field) search_param = {name_field_name: resource_id} resource = sugared_client.cosmo_get_if_exists(openstack_type, @@ -438,8 +444,8 @@ def validate_resource(ctx, sugared_client, openstack_type, ctx.logger.debug( 'OK: {0} (node {1}) can be created. provisioned {2}: {3}, ' 'quota: {4}' - .format(openstack_type, ctx.node.id, openstack_type_plural, - resource_amount, resource_quota)) + .format(openstack_type, ctx.node.id, openstack_type_plural, + resource_amount, resource_quota)) else: err = ('{0} (node {1}) cannot be created due to quota limitations.' ' provisioned {2}: {3}, quota: {4}' @@ -473,15 +479,15 @@ def is_external_resource(ctx): def is_external_resource_not_conditionally_created(ctx): return is_external_resource_by_properties(ctx.node.properties) and \ - not ctx.instance.runtime_properties.get(CONDITIONALLY_CREATED) + not ctx.instance.runtime_properties.get(CONDITIONALLY_CREATED) def is_external_relationship_not_conditionally_created(ctx): return is_external_resource_by_properties(ctx.source.node.properties) and \ - is_external_resource_by_properties(ctx.target.node.properties) and \ - not ctx.source.instance.runtime_properties.get( - CONDITIONALLY_CREATED) and not \ - ctx.target.instance.runtime_properties.get(CONDITIONALLY_CREATED) + is_external_resource_by_properties(ctx.target.node.properties) and \ + not ctx.source.instance.runtime_properties.get( + CONDITIONALLY_CREATED) and not \ + ctx.target.instance.runtime_properties.get(CONDITIONALLY_CREATED) def is_create_if_missing(ctx): @@ -490,17 +496,17 @@ def is_create_if_missing(ctx): def is_external_relationship(ctx): return is_external_resource_by_properties(ctx.source.node.properties) and \ - is_external_resource_by_properties(ctx.target.node.properties) + is_external_resource_by_properties(ctx.target.node.properties) def is_external_resource_by_properties(properties): return USE_EXTERNAL_RESOURCE_PROPERTY in properties and \ - properties[USE_EXTERNAL_RESOURCE_PROPERTY] + properties[USE_EXTERNAL_RESOURCE_PROPERTY] def is_create_if_missing_by_properties(properties): return CREATE_IF_MISSING_PROPERTY in properties and \ - properties[CREATE_IF_MISSING_PROPERTY] + properties[CREATE_IF_MISSING_PROPERTY] def delete_runtime_properties(ctx, runtime_properties_keys): @@ -542,7 +548,6 @@ def create_object_dict(ctx, object_name, args, object_dict=None): def add_list_to_runtime_properties(ctx, openstack_type_name, object_list): - objects = [] for obj in object_list: @@ -574,7 +579,6 @@ def set_neutron_runtime_properties(ctx, openstack_object, openstack_type): class Config(object): - OPENSTACK_CONFIG_PATH_ENV_VAR = 'OPENSTACK_CONFIG_PATH' OPENSTACK_CONFIG_PATH_DEFAULT_PATH = '~/openstack_config.json' OPENSTACK_ENV_VAR_PREFIX = 'OS_' @@ -613,7 +617,6 @@ def update_config(overridden_cfg, overriding_cfg): class OpenStackClient(object): - COMMON = {'username', 'password', 'auth_url'} AUTH_SETS = [ COMMON | {'tenant_name'}, @@ -635,7 +638,7 @@ def __init__(self, client_name, client_class, config=None, *args, **kw): # This check to make sure that blueprint openstack config # contains all the required auth params + any non-auth param - if set(config.keys())\ + if set(config.keys()) \ in self.AUTH_SETS and config.keys() in self.NON_AUTH_ITEMS: # Check if there is any value exists on ``cfg`` @@ -718,11 +721,11 @@ def set2str(s): "path which is set under the environment variable {} or at the " "default location {}), or as nested properties under an " "'{}' property. Valid auth param sets are: {}." - .format(received_params, - Config.OPENSTACK_CONFIG_PATH_ENV_VAR, - Config.OPENSTACK_CONFIG_PATH_DEFAULT_PATH, - CONFIG_PROPERTY, - ', '.join(valid_auth_sets))) + .format(received_params, + Config.OPENSTACK_CONFIG_PATH_ENV_VAR, + Config.OPENSTACK_CONFIG_PATH_DEFAULT_PATH, + CONFIG_PROPERTY, + ', '.join(valid_auth_sets))) @staticmethod def _merge_custom_configuration(cfg, client_name): @@ -837,7 +840,6 @@ def _cosmo_get(self, obj_type_single, if_exists, **kw): class GlanceClient(OpenStackClient): - # Can't glance_url be figured out from keystone REQUIRED_CONFIG_PARAMS = \ ['username', 'password', 'tenant_name', 'auth_url'] @@ -880,6 +882,9 @@ def with_neutron_client(f): @wraps(f) def wrapper(*args, **kw): _handle_kw('neutron_client', NeutronClientWithSugar, kw) + valid = _check_valid_resource_id_with_operation(kw) + if not valid: + return try: return f(*args, **kw) @@ -888,6 +893,7 @@ def wrapper(*args, **kw): _re_raise(e, recoverable=False, status_code=e.status_code) else: raise + return wrapper @@ -895,6 +901,9 @@ def with_nova_client(f): @wraps(f) def wrapper(*args, **kw): _handle_kw('nova_client', NovaClientWithSugar, kw) + valid = _check_valid_resource_id_with_operation(kw) + if not valid: + return try: return f(*args, **kw) @@ -905,6 +914,7 @@ def wrapper(*args, **kw): _re_raise(e, recoverable=False, status_code=e.code) else: raise + return wrapper @@ -912,6 +922,9 @@ def with_cinder_client(f): @wraps(f) def wrapper(*args, **kw): _handle_kw('cinder_client', CinderClientWithSugar, kw) + valid = _check_valid_resource_id_with_operation(kw) + if not valid: + return try: return f(*args, **kw) @@ -920,6 +933,7 @@ def wrapper(*args, **kw): _re_raise(e, recoverable=False, status_code=e.code) else: raise + return wrapper @@ -927,6 +941,9 @@ def with_glance_client(f): @wraps(f) def wrapper(*args, **kw): _handle_kw('glance_client', GlanceClientWithSugar, kw) + valid = _check_valid_resource_id_with_operation(kw) + if not valid: + return try: return f(*args, **kw) @@ -935,6 +952,7 @@ def wrapper(*args, **kw): _re_raise(e, recoverable=False, status_code=e.code) else: raise + return wrapper @@ -942,6 +960,9 @@ def with_keystone_client(f): @wraps(f) def wrapper(*args, **kw): _handle_kw('keystone_client', KeystoneClientWithSugar, kw) + valid = _check_valid_resource_id_with_operation(kw) + if not valid: + return try: return f(*args, **kw) @@ -952,11 +973,67 @@ def wrapper(*args, **kw): raise except keystone_exceptions.ClientException as e: _re_raise(e, recoverable=False) + return wrapper -def _handle_kw(client_name, client_class, kw): +def _check_valid_resource_id_with_operation(kw): + """ + function used to check if we should do the requested operation from workflow or not ;given runtime properties + :param kw: + :return: + """ + + _ctx = _find_context_in_kw(kw) or ctx + resource_id = None + node_instance_created = False + relation_instance_created = False + # get resource id and operation_name + if _ctx.type == context.NODE_INSTANCE: + resource_id = _ctx.instance.runtime_properties.get(OPENSTACK_ID_PROPERTY) + node_instance_created = _ctx.instance.runtime_properties.get('created') + elif _ctx.type == context.RELATIONSHIP_INSTANCE: + resource_id = _ctx.source.instance.runtime_properties.get(OPENSTACK_ID_PROPERTY) + relation_instance_created = _ctx.source.instance.runtime_properties.get('created') + operation_name = _ctx.operation.name + + # check resource_id + if resource_id: + # if create and resource_id provided with external resource return True otherwise False and assign created + if operation_name == CLOUDIFY_CREATE_OPERATION: + if is_external_resource(_ctx): + return True + _ctx.logger.info("resource is already created") + if _ctx.type == context.NODE_INSTANCE: + _ctx.instance.runtime_properties['created'] = True + elif _ctx.type == context.RELATIONSHIP_INSTANCE: + _ctx.source.instance.runtime_properties['created'] = True + return False + # if operation not create remove the created flag from runtime properties and return True to do the operation + elif operation_name in [CLOUDIFY_STOP_OPERATION, CLOUDIFY_DELETE_OPERATION, CLOUDIFY_UNLINK_OPERATION]: + if node_instance_created: + del _ctx.instance.runtime_properties['created'] + elif relation_instance_created: + del _ctx.source.instance.runtime_properties['created'] + return True + # any other operation skip since it is already created + if node_instance_created: + return False + elif relation_instance_created: + return False + + else: + # skip operations since resource_id is not assigned to take action + if operation_name in [CLOUDIFY_CONFIGURE_OPERATION, CLOUDIFY_START_OPERATION, CLOUDIFY_STOP_OPERATION, + CLOUDIFY_DELETE_OPERATION, CLOUDIFY_UNLINK_OPERATION]: + _ctx.logger.info("ignoring action since resource_id is not set") + return False + + return True + + +def _handle_kw(client_name, client_class, kw): _ctx = _find_context_in_kw(kw) or ctx if _ctx.type == context.NODE_INSTANCE: config = _ctx.node.properties.get(CONFIG_PROPERTY) @@ -1100,7 +1177,7 @@ def cosmo_list(self, obj_type_single, **kw): """ Sugar for list_XXXs()['XXXs'] """ obj_type_plural = self.cosmo_plural(obj_type_single) for obj in getattr(self, 'list_' + obj_type_plural)(**kw)[ - obj_type_plural]: + obj_type_plural]: yield obj def cosmo_delete_resource(self, obj_type_single, obj_id): @@ -1125,7 +1202,7 @@ def cosmo_list_prefixed(self, obj_type_single, name_prefix): def cosmo_delete_prefixed(self, name_prefix): # Cleanup all neutron.list_XXX() objects with names starting # with self.name_prefix - for obj_type_single in 'port', 'router', 'network', 'subnet',\ + for obj_type_single in 'port', 'router', 'network', 'subnet', \ 'security_group': for obj in self.cosmo_list_prefixed(obj_type_single, name_prefix): if obj_type_single == 'router': @@ -1185,7 +1262,7 @@ def get_quota(self, obj_type_single): class KeystoneClientWithSugar(OpenStackClient): # keystone does not have resource quota - KEYSTONE_INFINITE_RESOURCE_QUOTA = 10**9 + KEYSTONE_INFINITE_RESOURCE_QUOTA = 10 ** 9 def __init__(self, *args, **kw): super(KeystoneClientWithSugar, self).__init__( @@ -1211,7 +1288,7 @@ def get_quota(self, obj_type_single): class GlanceClientWithSugar(OpenStackClient): - GLANCE_INIFINITE_RESOURCE_QUOTA = 10**9 + GLANCE_INIFINITE_RESOURCE_QUOTA = 10 ** 9 def __init__(self, *args, **kw): super(GlanceClientWithSugar, self).__init__( From e07652616dde15c7a6c4ffc93b68502e8a21400a Mon Sep 17 00:00:00 2001 From: Ahmad Musa Date: Tue, 29 Oct 2019 16:52:37 +0200 Subject: [PATCH 5/9] added a new function to check if the operation should be executed or not given the runtime properties --- openstack_plugin_common/__init__.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/openstack_plugin_common/__init__.py b/openstack_plugin_common/__init__.py index 689bf3da..a4b5509c 100644 --- a/openstack_plugin_common/__init__.py +++ b/openstack_plugin_common/__init__.py @@ -882,8 +882,7 @@ def with_neutron_client(f): @wraps(f) def wrapper(*args, **kw): _handle_kw('neutron_client', NeutronClientWithSugar, kw) - valid = _check_valid_resource_id_with_operation(kw) - if not valid: + if not _check_valid_resource_id_with_operation(kw): return try: @@ -901,8 +900,7 @@ def with_nova_client(f): @wraps(f) def wrapper(*args, **kw): _handle_kw('nova_client', NovaClientWithSugar, kw) - valid = _check_valid_resource_id_with_operation(kw) - if not valid: + if not _check_valid_resource_id_with_operation(kw): return try: @@ -922,8 +920,7 @@ def with_cinder_client(f): @wraps(f) def wrapper(*args, **kw): _handle_kw('cinder_client', CinderClientWithSugar, kw) - valid = _check_valid_resource_id_with_operation(kw) - if not valid: + if not _check_valid_resource_id_with_operation(kw): return try: @@ -941,8 +938,7 @@ def with_glance_client(f): @wraps(f) def wrapper(*args, **kw): _handle_kw('glance_client', GlanceClientWithSugar, kw) - valid = _check_valid_resource_id_with_operation(kw) - if not valid: + if not _check_valid_resource_id_with_operation(kw): return try: @@ -960,8 +956,7 @@ def with_keystone_client(f): @wraps(f) def wrapper(*args, **kw): _handle_kw('keystone_client', KeystoneClientWithSugar, kw) - valid = _check_valid_resource_id_with_operation(kw) - if not valid: + if not _check_valid_resource_id_with_operation(kw): return try: From 0f55d1776ef73d3f3d8f565756fa98ada55b5e9e Mon Sep 17 00:00:00 2001 From: Ahmad Musa Date: Tue, 29 Oct 2019 17:14:39 +0200 Subject: [PATCH 6/9] added a new function to check if the operation should be executed or not given the runtime properties --- openstack_plugin_common/__init__.py | 51 ++++++++++++++++++----------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/openstack_plugin_common/__init__.py b/openstack_plugin_common/__init__.py index a4b5509c..8e730b40 100644 --- a/openstack_plugin_common/__init__.py +++ b/openstack_plugin_common/__init__.py @@ -250,7 +250,7 @@ def get_attribute_of_connected_nodes_by_relationship_type(ctx, def get_relationships_by_openstack_type(ctx, type_name): return [rel for rel in ctx.instance.relationships if rel.target.instance.runtime_properties.get( - OPENSTACK_TYPE_PROPERTY) == type_name] + OPENSTACK_TYPE_PROPERTY) == type_name] def get_connected_nodes_by_openstack_type(ctx, type_name): @@ -443,9 +443,9 @@ def validate_resource(ctx, sugared_client, openstack_type, or resource_quota == INFINITE_RESOURCE_QUOTA: ctx.logger.debug( 'OK: {0} (node {1}) can be created. provisioned {2}: {3}, ' - 'quota: {4}' - .format(openstack_type, ctx.node.id, openstack_type_plural, - resource_amount, resource_quota)) + 'quota: {4}'.format(openstack_type, ctx.node.id, + openstack_type_plural, + resource_amount, resource_quota)) else: err = ('{0} (node {1}) cannot be created due to quota limitations.' ' provisioned {2}: {3}, quota: {4}' @@ -487,7 +487,7 @@ def is_external_relationship_not_conditionally_created(ctx): is_external_resource_by_properties(ctx.target.node.properties) and \ not ctx.source.instance.runtime_properties.get( CONDITIONALLY_CREATED) and not \ - ctx.target.instance.runtime_properties.get(CONDITIONALLY_CREATED) + ctx.target.instance.runtime_properties.get(CONDITIONALLY_CREATED) def is_create_if_missing(ctx): @@ -721,11 +721,11 @@ def set2str(s): "path which is set under the environment variable {} or at the " "default location {}), or as nested properties under an " "'{}' property. Valid auth param sets are: {}." - .format(received_params, - Config.OPENSTACK_CONFIG_PATH_ENV_VAR, - Config.OPENSTACK_CONFIG_PATH_DEFAULT_PATH, - CONFIG_PROPERTY, - ', '.join(valid_auth_sets))) + .format(received_params, + Config.OPENSTACK_CONFIG_PATH_ENV_VAR, + Config.OPENSTACK_CONFIG_PATH_DEFAULT_PATH, + CONFIG_PROPERTY, + ', '.join(valid_auth_sets))) @staticmethod def _merge_custom_configuration(cfg, client_name): @@ -974,7 +974,8 @@ def wrapper(*args, **kw): def _check_valid_resource_id_with_operation(kw): """ - function used to check if we should do the requested operation from workflow or not ;given runtime properties + function used to check if we should do the requested operation from + workflow or not ;given runtime properties :param kw: :return: """ @@ -986,16 +987,20 @@ def _check_valid_resource_id_with_operation(kw): # get resource id and operation_name if _ctx.type == context.NODE_INSTANCE: - resource_id = _ctx.instance.runtime_properties.get(OPENSTACK_ID_PROPERTY) + resource_id = _ctx.instance.runtime_properties.get( + OPENSTACK_ID_PROPERTY) node_instance_created = _ctx.instance.runtime_properties.get('created') elif _ctx.type == context.RELATIONSHIP_INSTANCE: - resource_id = _ctx.source.instance.runtime_properties.get(OPENSTACK_ID_PROPERTY) - relation_instance_created = _ctx.source.instance.runtime_properties.get('created') + resource_id = _ctx.source.instance.runtime_properties.get( + OPENSTACK_ID_PROPERTY) + relation_instance_created = _ctx.source.instance.runtime_properties. \ + get('created') operation_name = _ctx.operation.name # check resource_id if resource_id: - # if create and resource_id provided with external resource return True otherwise False and assign created + # if create and resource_id provided with external resource + # return True otherwise False and assign created if operation_name == CLOUDIFY_CREATE_OPERATION: if is_external_resource(_ctx): return True @@ -1005,8 +1010,11 @@ def _check_valid_resource_id_with_operation(kw): elif _ctx.type == context.RELATIONSHIP_INSTANCE: _ctx.source.instance.runtime_properties['created'] = True return False - # if operation not create remove the created flag from runtime properties and return True to do the operation - elif operation_name in [CLOUDIFY_STOP_OPERATION, CLOUDIFY_DELETE_OPERATION, CLOUDIFY_UNLINK_OPERATION]: + # if operation not create remove the created flag from runtime + # properties and return True to do the operation + elif operation_name in [CLOUDIFY_STOP_OPERATION, + CLOUDIFY_DELETE_OPERATION, + CLOUDIFY_UNLINK_OPERATION]: if node_instance_created: del _ctx.instance.runtime_properties['created'] elif relation_instance_created: @@ -1020,8 +1028,11 @@ def _check_valid_resource_id_with_operation(kw): else: # skip operations since resource_id is not assigned to take action - if operation_name in [CLOUDIFY_CONFIGURE_OPERATION, CLOUDIFY_START_OPERATION, CLOUDIFY_STOP_OPERATION, - CLOUDIFY_DELETE_OPERATION, CLOUDIFY_UNLINK_OPERATION]: + if operation_name in [CLOUDIFY_CONFIGURE_OPERATION, + CLOUDIFY_START_OPERATION, + CLOUDIFY_STOP_OPERATION, + CLOUDIFY_DELETE_OPERATION, + CLOUDIFY_UNLINK_OPERATION]: _ctx.logger.info("ignoring action since resource_id is not set") return False @@ -1172,7 +1183,7 @@ def cosmo_list(self, obj_type_single, **kw): """ Sugar for list_XXXs()['XXXs'] """ obj_type_plural = self.cosmo_plural(obj_type_single) for obj in getattr(self, 'list_' + obj_type_plural)(**kw)[ - obj_type_plural]: + obj_type_plural]: yield obj def cosmo_delete_resource(self, obj_type_single, obj_id): From f3403e094c64950678c532dcbc5275776ee2cf9a Mon Sep 17 00:00:00 2001 From: Ahmad Musa Date: Tue, 29 Oct 2019 18:18:27 +0200 Subject: [PATCH 7/9] fixed mock tests --- glance_plugin/tests/test.py | 3 +++ keystone_plugin/tests/test_project.py | 21 +++++++++++++++++ keystone_plugin/tests/test_user.py | 9 +++++++ neutron_plugin/tests/test_port.py | 15 ++++++++++++ neutron_plugin/tests/test_rbac_policy.py | 30 ++++++++++++++++++++++++ nova_plugin/tests/test_flavor.py | 9 +++++++ nova_plugin/tests/test_host_aggregate.py | 18 ++++++++++++++ nova_plugin/tests/test_server.py | 9 +++++++ 8 files changed, 114 insertions(+) diff --git a/glance_plugin/tests/test.py b/glance_plugin/tests/test.py index 4a88cba4..cf7e6ec2 100644 --- a/glance_plugin/tests/test.py +++ b/glance_plugin/tests/test.py @@ -123,6 +123,9 @@ class TestStartImage(unittest.TestCase): 'test-image-start.yaml') @mock.patch('glance_plugin.image.create') + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) @workflow_test(blueprint_path, copy_plugin_yaml=True) def test_image_lifecycle_start(self, cfy_local, *_): test_vars = { diff --git a/keystone_plugin/tests/test_project.py b/keystone_plugin/tests/test_project.py index db3eadfe..9ee06f60 100644 --- a/keystone_plugin/tests/test_project.py +++ b/keystone_plugin/tests/test_project.py @@ -85,6 +85,9 @@ def mock_ctx(self, test_vars, test_id, @mock.patch('openstack_plugin_common._handle_kw', autospec=True, return_value=None) + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) def test_keystone_project_create_and_delete(self, *_): quota = {'nova': {'cpu': 120}, 'neutron': {'networks': 100}} @@ -122,6 +125,9 @@ def test_keystone_project_create_and_delete(self, *_): self.assertNotIn(OPENSTACK_TYPE_PROPERTY, ctx.instance.runtime_properties) + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) def test_assign_user(self, *_): test_vars = { 'project': {}, @@ -146,6 +152,9 @@ def test_assign_user(self, *_): self.assertEqual({self.test_user: self.test_role}, mock_project._users) + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) def test_assign_users_not_unique(self, *_): test_vars = { 'project': {}, @@ -171,6 +180,9 @@ def test_assign_users_not_unique(self, *_): cinder_client=mock.MagicMock(), # cinder_client neutron_client=mock.MagicMock()) # neutron_client + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) def test_assign_user_roles_not_unique(self, *_): test_vars = { 'project': {}, @@ -194,6 +206,9 @@ def test_assign_user_roles_not_unique(self, *_): cinder_client=mock.MagicMock(), # cinder_client neutron_client=mock.MagicMock()) # neutron_client + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) def test_update_project(self, *_): test_vars = { 'project': {}, @@ -215,6 +230,9 @@ def test_update_project(self, *_): ctx.instance.runtime_properties[ OPENSTACK_NAME_PROPERTY]) + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) def test_list_projects(self, *_): test_vars = { 'project': {}, @@ -237,6 +255,9 @@ def test_list_projects(self, *_): self.assertEqual(1, len(ctx.instance.runtime_properties[project_list])) + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) def test_get_quota(self, *_): nova_quota = {'cpu': 120} cinder_quota = {'volumes': 30} diff --git a/keystone_plugin/tests/test_user.py b/keystone_plugin/tests/test_user.py index 4e7d89d2..ddd01077 100644 --- a/keystone_plugin/tests/test_user.py +++ b/keystone_plugin/tests/test_user.py @@ -71,6 +71,9 @@ def mock_ctx(self, test_vars, test_id, @mock.patch('openstack_plugin_common._handle_kw', autospec=True, return_value=None) + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) def test_keystone_user_create_and_delete(self, *_): test_vars = { 'user': {}, @@ -100,6 +103,9 @@ def test_keystone_user_create_and_delete(self, *_): self.assertNotIn(OPENSTACK_TYPE_PROPERTY, ctx.instance.runtime_properties) + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) def test_update_user(self, *_): test_vars = { 'user': {}, @@ -117,6 +123,9 @@ def test_update_user(self, *_): ctx.instance.runtime_properties[ OPENSTACK_NAME_PROPERTY]) + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) def test_list_users(self, *_): test_vars = { 'user': {}, diff --git a/neutron_plugin/tests/test_port.py b/neutron_plugin/tests/test_port.py index 00b64993..623c82a3 100644 --- a/neutron_plugin/tests/test_port.py +++ b/neutron_plugin/tests/test_port.py @@ -57,6 +57,9 @@ def test_port_delete(self): mock_neutron.body) @mock.patch('openstack_plugin_common._handle_kw') + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) def test_delete(self, *_): node_props = { 'fixed_ip': '', @@ -116,6 +119,9 @@ def test_port_update(self): {}, port) @mock.patch('openstack_plugin_common._handle_kw') + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) def test_create(self, *_): node_props = { 'fixed_ip': '', @@ -263,6 +269,9 @@ def show_subnet(self, subnet_id=None): class TestPortSG(unittest.TestCase): @mock.patch('openstack_plugin_common._handle_kw') + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) def test_connect_sg_to_port(self, *_): mock_neutron = MockNeutronClient(update=True) ctx = MockCloudifyContext( @@ -280,6 +289,9 @@ def test_connect_sg_to_port(self, *_): self.assertIsNone(ctx.operation._operation_retry) @mock.patch('openstack_plugin_common._handle_kw') + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) def test_connect_sg_to_port_race_condition(self, *_): mock_neutron = MockNeutronClient(update=False) @@ -298,6 +310,9 @@ def test_connect_sg_to_port_race_condition(self, *_): OperationRetry) @mock.patch('openstack_plugin_common._handle_kw') + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) def test_disconnect_sg_to_port(self, *_): mock_neutron = MockNeutronClient(update=True) ctx = MockCloudifyContext( diff --git a/neutron_plugin/tests/test_rbac_policy.py b/neutron_plugin/tests/test_rbac_policy.py index 3a9687bc..264d12bd 100644 --- a/neutron_plugin/tests/test_rbac_policy.py +++ b/neutron_plugin/tests/test_rbac_policy.py @@ -180,6 +180,9 @@ def mock_all(self, relationships=None, **kwargs): autospec=True, return_value=None ) + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) def test_create_and_delete(self, *_): # given ctx, neutron_client, _ = self.mock_all() @@ -229,6 +232,9 @@ def test_create_and_delete(self, *_): autospec=True, return_value=None ) + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) def test_create_and_delete_external_resource(self, *_): # given ctx, neutron_client, _ = self.mock_all(use_external_resource=True) @@ -276,6 +282,9 @@ def test_create_and_delete_external_resource(self, *_): autospec=True, return_value=None ) + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) def test_create_and_delete_using_relationship(self, *_): # given ctx, neutron_client, _ = self.mock_all( @@ -338,6 +347,9 @@ def test_create_and_delete_using_relationship(self, *_): autospec=True, return_value=None ) + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) def test_fail_create_using_multiple_relationships(self, *_): # given ctx, neutron_client, _ = self.mock_all( @@ -362,6 +374,9 @@ def test_fail_create_using_multiple_relationships(self, *_): autospec=True, return_value=None ) + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) def test_fail_create_using_relationship_with_missing_data(self, *_): # given ctx, neutron_client, _ = self.mock_all( @@ -387,6 +402,9 @@ def test_fail_create_using_relationship_with_missing_data(self, *_): autospec=True, return_value=None ) + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) def test_fail_create_using_relationship_and_properties(self, *_): # given ctx, neutron_client, _ = self.mock_all( @@ -409,6 +427,9 @@ def test_fail_create_using_relationship_and_properties(self, *_): autospec=True, return_value=None ) + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) def test_create_and_delete_using_args(self, *_): # given ctx, neutron_client, _ = self.mock_all( @@ -463,6 +484,9 @@ def test_create_and_delete_using_args(self, *_): autospec=True, return_value=None ) + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) def test_fail_create_using_relationship_and_args(self, *_): # given ctx, neutron_client, _ = self.mock_all( @@ -489,6 +513,9 @@ def test_fail_create_using_relationship_and_args(self, *_): autospec=True, return_value=None ) + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) def test_list(self, *_): # given ctx, neutron_client, _ = self.mock_all() @@ -515,6 +542,9 @@ def test_list(self, *_): autospec=True, return_value=None ) + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) def test_find_and_delete(self, *_): # given ctx, neutron_client, _ = self.mock_all() diff --git a/nova_plugin/tests/test_flavor.py b/nova_plugin/tests/test_flavor.py index 64a7b5c1..d384d11a 100644 --- a/nova_plugin/tests/test_flavor.py +++ b/nova_plugin/tests/test_flavor.py @@ -95,6 +95,9 @@ def mock_ctx(self, autospec=True, return_value=None ) + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) def test_flavor_create_and_delete(self, *_): # given test_vars = { @@ -146,6 +149,9 @@ def test_flavor_create_and_delete(self, *_): autospec=True, return_value=None ) + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) def test_flavor_create_and_delete_with_extra_specs_and_tenants(self, *_): # given test_vars_tenant_id = 'some_tenant_id' @@ -222,6 +228,9 @@ def test_flavor_create_and_delete_with_extra_specs_and_tenants(self, *_): ctx.instance.runtime_properties ) + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) def test_list_flavors(self, *_): # given test_vars = { diff --git a/nova_plugin/tests/test_host_aggregate.py b/nova_plugin/tests/test_host_aggregate.py index 8482dd88..21f12c22 100644 --- a/nova_plugin/tests/test_host_aggregate.py +++ b/nova_plugin/tests/test_host_aggregate.py @@ -101,6 +101,9 @@ def mock_ctx(self, autospec=True, return_value=None ) + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) def test_add_hosts(self, *_): # given test_vars_host1 = 'cf4301' @@ -158,6 +161,9 @@ def test_add_hosts(self, *_): autospec=True, return_value=None ) + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) def test_remove_hosts(self, *_): # given test_vars_host1 = 'cf4301' @@ -214,6 +220,9 @@ def test_remove_hosts(self, *_): autospec=True, return_value=None ) + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) def test_create_and_delete(self, *_): # given test_vars_host1 = 'cf4301' @@ -309,6 +318,9 @@ def test_create_and_delete(self, *_): autospec=True, return_value=None ) + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) @mock.patch( 'openstack_plugin_common.get_resource_by_name_or_id', autospec=True, @@ -377,6 +389,9 @@ def test_create_and_delete_external_resource(self, *_): autospec=True, return_value=None ) + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) def test_update(self, *_): # given test_vars_hosts = ['cf4301', 'openstack-kilo-t2.novalocal'] @@ -455,6 +470,9 @@ def test_update(self, *_): autospec=True, return_value=None ) + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) def test_list(self, *_): # given test_vars_host1 = 'cf4301' diff --git a/nova_plugin/tests/test_server.py b/nova_plugin/tests/test_server.py index 1e892d5f..5068cd48 100644 --- a/nova_plugin/tests/test_server.py +++ b/nova_plugin/tests/test_server.py @@ -53,6 +53,9 @@ class TestServer(unittest.TestCase): @mock.patch('nova_plugin.server.create') @mock.patch('nova_plugin.server._set_network_and_ip_runtime_properties') + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) @workflow_test(blueprint_path, copy_plugin_yaml=True) def test_nova_server_lifecycle_start(self, cfy_local, *_): @@ -80,6 +83,9 @@ def mock_get_server_by_context(*_): @workflow_test(blueprint_path, copy_plugin_yaml=True) @mock.patch('nova_plugin.server.create') @mock.patch('nova_plugin.server._set_network_and_ip_runtime_properties') + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) def test_nova_server_lifecycle_start_after_stop(self, cfy_local, *_): test_vars = { @@ -111,6 +117,9 @@ def mock_get_server_by_context(_): @workflow_test(blueprint_path, copy_plugin_yaml=True) @mock.patch('nova_plugin.server.create') @mock.patch('nova_plugin.server._set_network_and_ip_runtime_properties') + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) def test_nova_server_lifecycle_start_unknown_status(self, cfy_local, *_): test_vars = { 'counter': 0, From 3bf92320103984e1efd667951eaf4d962be8208a Mon Sep 17 00:00:00 2001 From: Ahmad Musa Date: Thu, 31 Oct 2019 15:20:08 +0200 Subject: [PATCH 8/9] handle relation instance operations --- cinder_plugin/tests/test_volume.py | 31 ++++- openstack_plugin_common/__init__.py | 185 ++++++++++++++++++++++------ 2 files changed, 174 insertions(+), 42 deletions(-) diff --git a/cinder_plugin/tests/test_volume.py b/cinder_plugin/tests/test_volume.py index 66f58cfb..914c7045 100644 --- a/cinder_plugin/tests/test_volume.py +++ b/cinder_plugin/tests/test_volume.py @@ -154,10 +154,13 @@ def test_delete(self): self.assertTrue(OPENSTACK_NAME_PROPERTY not in ctx_m.instance.runtime_properties) + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) @mock.patch('openstack_plugin_common.NovaClientWithSugar') @mock.patch('openstack_plugin_common.CinderClientWithSugar') @mock.patch.object(volume, 'wait_until_status', return_value=(None, True)) - def test_attach(self, wait_until_status_m, cinder_m, nova_m): + def test_attach(self, wait_until_status_m, cinder_m, nova_m, *_): volume_id = '00000000-0000-0000-0000-000000000000' server_id = '11111111-1111-1111-1111-111111111111' device_name = '/dev/fake' @@ -273,13 +276,20 @@ def _test_cleanup__after_attach_fails( num_tries=10, timeout=2) - def test_cleanup_after_waituntilstatus_throws_recoverable_error(self): + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) + def test_cleanup_after_waituntilstatus_throws_recoverable_error(self, *_): err = cfy_exc.RecoverableError('Some recoverable error') with mock.patch.object(volume, 'wait_until_status', side_effect=[err, (None, True)]) as wait_mock: self._test_cleanup__after_attach_fails(type(err), True, wait_mock) - def test_cleanup_after_waituntilstatus_throws_any_not_nonrecov_error(self): + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) + def test_cleanup_after_waituntilstatus_throws_any_not_nonrecov_error(self, + *_): class ArbitraryNonRecoverableException(Exception): pass err = ArbitraryNonRecoverableException('An exception') @@ -287,21 +297,30 @@ class ArbitraryNonRecoverableException(Exception): side_effect=[err, (None, True)]) as wait_mock: self._test_cleanup__after_attach_fails(type(err), True, wait_mock) - def test_cleanup_after_waituntilstatus_lets_nonrecov_errors_pass(self): + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) + def test_cleanup_after_waituntilstatus_lets_nonrecov_errors_pass(self, *_): err = cfy_exc.NonRecoverableError('Some non recoverable error') with mock.patch.object(volume, 'wait_until_status', side_effect=[err, (None, True)]) as wait_mock: self._test_cleanup__after_attach_fails(type(err), False, wait_mock) @mock.patch.object(volume, 'wait_until_status', return_value=(None, False)) - def test_cleanup_after_waituntilstatus_times_out(self, wait_mock): + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) + def test_cleanup_after_waituntilstatus_times_out(self, wait_mock, *_): self._test_cleanup__after_attach_fails(cfy_exc.RecoverableError, True, wait_mock) + @mock.patch('openstack_plugin_common' + '._check_valid_resource_id_with_operation', + autospec=True, return_value=True) @mock.patch('openstack_plugin_common.NovaClientWithSugar') @mock.patch('openstack_plugin_common.CinderClientWithSugar') @mock.patch.object(volume, 'wait_until_status', return_value=(None, True)) - def test_detach(self, wait_until_status_m, cinder_m, nova_m): + def test_detach(self, wait_until_status_m, cinder_m, nova_m, *_): volume_id = '00000000-0000-0000-0000-000000000000' server_id = '11111111-1111-1111-1111-111111111111' attachment_id = '22222222-2222-2222-2222-222222222222' diff --git a/openstack_plugin_common/__init__.py b/openstack_plugin_common/__init__.py index 8e730b40..8818b182 100644 --- a/openstack_plugin_common/__init__.py +++ b/openstack_plugin_common/__init__.py @@ -100,11 +100,16 @@ } CLOUDIFY_CREATE_OPERATION = 'cloudify.interfaces.lifecycle.create' +CLOUDIFY_PRE_CONFIGURE_OPERATION = 'cloudify.interfaces.lifecycle.preconfigure' CLOUDIFY_CONFIGURE_OPERATION = 'cloudify.interfaces.lifecycle.configure' +CLOUDIFY_POST_CONFIGURE_OPERATION = 'cloudify.interfaces.lifecycle.' \ + 'postconfigure' CLOUDIFY_START_OPERATION = 'cloudify.interfaces.lifecycle.start' CLOUDIFY_STOP_OPERATION = 'cloudify.interfaces.lifecycle.stop' CLOUDIFY_DELETE_OPERATION = 'cloudify.interfaces.lifecycle.delete' CLOUDIFY_UNLINK_OPERATION = 'cloudify.interfaces.relationship_lifecycle.unlink' +CLOUDIFY_ESTABLISH_OPERATION = 'cloudify.interfaces.relationship_lifecycle.' \ + 'establish' # TODO: Move this to cloudify-plugins-common (code freeze currently @@ -888,6 +893,7 @@ def wrapper(*args, **kw): try: return f(*args, **kw) except neutron_exceptions.NeutronClientException as e: + _check_valid_resource_id_with_operation(kw, True) if e.status_code in _non_recoverable_error_codes: _re_raise(e, recoverable=False, status_code=e.status_code) else: @@ -906,8 +912,10 @@ def wrapper(*args, **kw): try: return f(*args, **kw) except nova_exceptions.OverLimit as e: + _check_valid_resource_id_with_operation(kw, True) _re_raise(e, recoverable=True, retry_after=e.retry_after) except nova_exceptions.ClientException as e: + _check_valid_resource_id_with_operation(kw, True) if e.code in _non_recoverable_error_codes: _re_raise(e, recoverable=False, status_code=e.code) else: @@ -926,6 +934,7 @@ def wrapper(*args, **kw): try: return f(*args, **kw) except cinder_exceptions.ClientException as e: + _check_valid_resource_id_with_operation(kw, True) if e.code in _non_recoverable_error_codes: _re_raise(e, recoverable=False, status_code=e.code) else: @@ -944,6 +953,7 @@ def wrapper(*args, **kw): try: return f(*args, **kw) except glance_exceptions.ClientException as e: + _check_valid_resource_id_with_operation(kw, True) if e.code in _non_recoverable_error_codes: _re_raise(e, recoverable=False, status_code=e.code) else: @@ -962,6 +972,7 @@ def wrapper(*args, **kw): try: return f(*args, **kw) except keystone_exceptions.HTTPError as e: + _check_valid_resource_id_with_operation(kw, True) if e.http_status in _non_recoverable_error_codes: _re_raise(e, recoverable=False, status_code=e.http_status) else: @@ -972,7 +983,7 @@ def wrapper(*args, **kw): return wrapper -def _check_valid_resource_id_with_operation(kw): +def _check_valid_resource_id_with_operation(kw, exception=False): """ function used to check if we should do the requested operation from workflow or not ;given runtime properties @@ -982,57 +993,159 @@ def _check_valid_resource_id_with_operation(kw): _ctx = _find_context_in_kw(kw) or ctx resource_id = None - node_instance_created = False - relation_instance_created = False - # get resource id and operation_name + operation_name = _ctx.operation.name if _ctx.type == context.NODE_INSTANCE: resource_id = _ctx.instance.runtime_properties.get( OPENSTACK_ID_PROPERTY) - node_instance_created = _ctx.instance.runtime_properties.get('created') elif _ctx.type == context.RELATIONSHIP_INSTANCE: resource_id = _ctx.source.instance.runtime_properties.get( OPENSTACK_ID_PROPERTY) - relation_instance_created = _ctx.source.instance.runtime_properties. \ - get('created') - operation_name = _ctx.operation.name # check resource_id if resource_id: - # if create and resource_id provided with external resource - # return True otherwise False and assign created + instance_id = _ctx.source.instance.id \ + if _ctx.type == context.RELATIONSHIP_INSTANCE \ + else _ctx.instance.id if operation_name == CLOUDIFY_CREATE_OPERATION: - if is_external_resource(_ctx): - return True - _ctx.logger.info("resource is already created") - if _ctx.type == context.NODE_INSTANCE: - _ctx.instance.runtime_properties['created'] = True - elif _ctx.type == context.RELATIONSHIP_INSTANCE: - _ctx.source.instance.runtime_properties['created'] = True - return False - # if operation not create remove the created flag from runtime - # properties and return True to do the operation - elif operation_name in [CLOUDIFY_STOP_OPERATION, - CLOUDIFY_DELETE_OPERATION, - CLOUDIFY_UNLINK_OPERATION]: - if node_instance_created: - del _ctx.instance.runtime_properties['created'] - elif relation_instance_created: - del _ctx.source.instance.runtime_properties['created'] - return True - # any other operation skip since it is already created - if node_instance_created: - return False - elif relation_instance_created: - return False - + if not is_external_resource(_ctx): + _ctx.logger.info("resource is already created") + return False + else: + if not exception: + if _ctx.instance.runtime_properties.get( + 'create_' + instance_id): + _ctx.logger.info("resource is already created") + return False + else: + _ctx.instance.runtime_properties['create_' + + instance_id] = True + else: + del _ctx.instance.runtime_properties['create_' + + instance_id] + elif operation_name == CLOUDIFY_CONFIGURE_OPERATION: + if not exception: + if _ctx.instance.runtime_properties.get('conf_'+instance_id): + _ctx.logger.info("resource is already configured") + return False + else: + _ctx.instance.runtime_properties['conf_' + + instance_id] = True + else: + del _ctx.instance.runtime_properties['conf_' + + instance_id] + elif operation_name == CLOUDIFY_START_OPERATION: + if not exception: + if _ctx.instance.runtime_properties.get('start_'+instance_id): + _ctx.logger.info("resource is already started") + return False + else: + if _ctx.instance.runtime_properties. \ + get('stop_' + instance_id): + del _ctx.instance.runtime_properties['stop_' + + instance_id] + _ctx.instance.runtime_properties['start_' + + instance_id] = True + else: + _ctx.instance.runtime_properties['stop_' + + instance_id] = True + del _ctx.instance.runtime_properties['start_' + + instance_id] + elif operation_name == CLOUDIFY_STOP_OPERATION: + if not exception: + if _ctx.instance.runtime_properties.get('stop_'+instance_id): + _ctx.logger.info("resource is already stopped") + return False + else: + if _ctx.instance.runtime_properties. \ + get('start_' + instance_id): + del _ctx.instance.runtime_properties['start_' + + instance_id] + _ctx.instance.runtime_properties['stop_' + + instance_id] = True + else: + _ctx.instance.runtime_properties['start_' + + instance_id] = True + del _ctx.instance.runtime_properties['stop_' + + instance_id] + elif operation_name == CLOUDIFY_DELETE_OPERATION: + if not exception: + if _ctx.instance.runtime_properties. \ + get('conf_' + instance_id): + del _ctx.instance.runtime_properties['conf_' + + instance_id] + if is_external_resource(_ctx): + if _ctx.instance.runtime_properties. \ + get('create_' + instance_id): + del _ctx.instance.runtime_properties['create_' + + instance_id] + else: + _ctx.instance.runtime_properties['conf_' + + instance_id] = True + if is_external_resource(_ctx): + _ctx.instance.runtime_properties['create_' + + instance_id] = True + if _ctx.type == context.RELATIONSHIP_INSTANCE: + target_id = _ctx.target.instance.id + if operation_name == CLOUDIFY_ESTABLISH_OPERATION: + if not exception: + if _ctx.source.instance.runtime_properties. \ + get('rel_' + target_id): + _ctx.logger.info("resource relation to {} already " + "established".format(target_id)) + return False + else: + _ctx.source.instance.runtime_properties['rel_' + + target_id] \ + = True + else: + del _ctx.source.instance.runtime_properties['rel_' + + target_id] + elif operation_name == CLOUDIFY_UNLINK_OPERATION: + if not exception: + if _ctx.source.instance.runtime_properties. \ + get('rel_' + target_id): + del _ctx.source.instance.runtime_properties['rel_' + + target_id] + else: + _ctx.source.instance.runtime_properties['rel_' + + target_id] = True + elif operation_name == CLOUDIFY_PRE_CONFIGURE_OPERATION: + if not exception: + if _ctx.source.instance.runtime_properties. \ + get('pre_rel_' + target_id): + _ctx.logger.info("resource pre-conf relation " + "to {} already " + "established".format(target_id)) + return False + _ctx.source.instance.runtime_properties['pre_rel_' + + target_id] = True + else: + del _ctx.source.instance.runtime_properties['pre_rel_' + + target_id] + elif operation_name == CLOUDIFY_POST_CONFIGURE_OPERATION: + if not exception: + if _ctx.source.instance.runtime_properties. \ + get('post_rel_' + target_id): + _ctx.logger.info("resource post-conf relation " + "to {} already " + "established".format(target_id)) + return False + _ctx.source.instance.runtime_properties['post_rel_' + + target_id] = True + else: + del _ctx.source.instance.runtime_properties['post_rel_' + + target_id] else: # skip operations since resource_id is not assigned to take action - if operation_name in [CLOUDIFY_CONFIGURE_OPERATION, + if operation_name in [CLOUDIFY_PRE_CONFIGURE_OPERATION, + CLOUDIFY_CONFIGURE_OPERATION, + CLOUDIFY_POST_CONFIGURE_OPERATION, CLOUDIFY_START_OPERATION, CLOUDIFY_STOP_OPERATION, CLOUDIFY_DELETE_OPERATION, - CLOUDIFY_UNLINK_OPERATION]: + CLOUDIFY_UNLINK_OPERATION, + CLOUDIFY_ESTABLISH_OPERATION]: _ctx.logger.info("ignoring action since resource_id is not set") return False From 513d96b5609aadabd23b30a8f996b26cfa65484e Mon Sep 17 00:00:00 2001 From: Ahmad Musa Date: Sun, 3 Nov 2019 17:22:44 +0200 Subject: [PATCH 9/9] reformat code and add test cases --- openstack_plugin_common/__init__.py | 340 +++++++----- .../tests/openstack_client_tests.py | 519 +++++++++++++++++- 2 files changed, 708 insertions(+), 151 deletions(-) diff --git a/openstack_plugin_common/__init__.py b/openstack_plugin_common/__init__.py index 8818b182..ef1ab215 100644 --- a/openstack_plugin_common/__init__.py +++ b/openstack_plugin_common/__init__.py @@ -111,6 +111,24 @@ CLOUDIFY_ESTABLISH_OPERATION = 'cloudify.interfaces.relationship_lifecycle.' \ 'establish' +CLOUDIFY_CONF_CYCLE_OPERATION = (CLOUDIFY_CREATE_OPERATION, + CLOUDIFY_CONFIGURE_OPERATION, + CLOUDIFY_START_OPERATION, + CLOUDIFY_STOP_OPERATION) + +CLOUDIFY_RELATION_CYCLE_OPERATION = (CLOUDIFY_ESTABLISH_OPERATION, + CLOUDIFY_PRE_CONFIGURE_OPERATION, + CLOUDIFY_POST_CONFIGURE_OPERATION) + +CLOUDIFY_OPERATIONS_WITHOUT_CREATE = (CLOUDIFY_PRE_CONFIGURE_OPERATION, + CLOUDIFY_CONFIGURE_OPERATION, + CLOUDIFY_POST_CONFIGURE_OPERATION, + CLOUDIFY_START_OPERATION, + CLOUDIFY_STOP_OPERATION, + CLOUDIFY_DELETE_OPERATION, + CLOUDIFY_UNLINK_OPERATION, + CLOUDIFY_ESTABLISH_OPERATION) + # TODO: Move this to cloudify-plugins-common (code freeze currently # in effect). @@ -978,11 +996,169 @@ def wrapper(*args, **kw): else: raise except keystone_exceptions.ClientException as e: + _check_valid_resource_id_with_operation(kw, True) _re_raise(e, recoverable=False) return wrapper +def handle_conf_cycle_operations(_ctx, operation_name, instance_id, exception): + """ + function to handle CLOUDIFY_CONF_CYCLE_OPERATION given exception + if no exception the function add runtime property for that operation + if exception remove the runtime property, handle start/stop operations flag + :param _ctx: + :param operation_name: + :param instance_id: + :param exception: + :return: + """ + op_prefix = operation_name.split(".")[-1] + runtime_prop = op_prefix + '_' + instance_id + + if not exception: + # if runtime for this operation already set return false + if _ctx.instance.runtime_properties.get(runtime_prop): + _ctx.logger.info(op_prefix + ' already done') + return False + else: + # set runtime property of the operation and return true + _ctx.instance.runtime_properties[runtime_prop] = True + + # if operation start , delete stop property if exists + if operation_name == CLOUDIFY_START_OPERATION and \ + _ctx.instance.runtime_properties.get( + 'stop_' + instance_id): + del _ctx.instance.runtime_properties['stop_' + + instance_id] + + # if operation stop , delete start property if exists + elif operation_name == CLOUDIFY_STOP_OPERATION and \ + _ctx.instance.runtime_properties.get( + 'start_' + instance_id): + del _ctx.instance.runtime_properties['start_' + + instance_id] + else: # in exception + # remove the runtime for the operation + del _ctx.instance.runtime_properties[runtime_prop] + + # if operation start , add stop property + if operation_name == CLOUDIFY_START_OPERATION: + _ctx.instance.runtime_properties[ + 'stop_' + instance_id] = True + + # if operation stop , add start property + elif operation_name == CLOUDIFY_STOP_OPERATION: + _ctx.instance.runtime_properties[ + 'start_' + instance_id] = True + return True + + +def handle_delete_operations(_ctx, instance_id, exception): + """ + function to handle delete operation given exception + affected flags are for : configure,create and stop operations + if exception set the affected flags to True + if no exception delete the affected flags + :param _ctx: + :param instance_id: + :param exception: + :return: + """ + # in case no exception delete configure, create and stop properties + # in case of exception add configure, create and stop properties + for op in (CLOUDIFY_CREATE_OPERATION, + CLOUDIFY_CONFIGURE_OPERATION, + CLOUDIFY_STOP_OPERATION): + runtime_prop = op.split(".")[-1] + '_' + instance_id + if not exception: + if _ctx.instance.runtime_properties.get(runtime_prop): + del _ctx.instance.runtime_properties[runtime_prop] + else: + _ctx.instance.runtime_properties[runtime_prop] = True + return True + + +def handle_rel_cycle_operations(_ctx, operation_name, exception): + """ + function to handle CLOUDIFY_RELATION_CYCLE_OPERATION given exception + if no exception add the runtime flags for the operations + if exception delete the runtime flags + :param _ctx: + :param operation_name: + :param exception: + :return: + """ + # rely on target since relation could be to more than one instance + op_prefix = operation_name.split(".")[-1] + target_id = _ctx.target.instance.id + runtime_prop = op_prefix + '_' + target_id + if not exception: + if _ctx.source.instance.runtime_properties.get( + runtime_prop): + _ctx.logger.info(op_prefix + ' already done') + return False + else: + _ctx.source.instance.runtime_properties[runtime_prop] \ + = True + else: + del _ctx.source.instance.runtime_properties[runtime_prop] + return True + + +def handle_unlink_operations(_ctx, exception): + """ + function to handle unlink operation given exception + if no exception we remove establish runtime flag + if exception we set establish runtime flag to True + :param _ctx: + :param exception: + :return: + """ + # in case of unlink operation we remove establish in case of + # no exception otherwise we return the establish property + target_id = _ctx.target.instance.id + runtime_prop = CLOUDIFY_ESTABLISH_OPERATION.split(".")[-1] + '_' + \ + target_id + if not exception: + if _ctx.source.instance.runtime_properties.get( + runtime_prop): + del _ctx.source.instance.runtime_properties[ + runtime_prop] + else: + _ctx.source.instance.runtime_properties[runtime_prop] \ + = True + return True + + +def handle_no_resource_id_operations(_ctx, operation_name, instance_id, + exception): + """ + function to handle instance operations with no resource_id given exception + if no exception and create_operation set create runtime flag to true , + skip any other since we don't have resource_id to take action + if exception and create_operation delete create runtime flag + :param _ctx: + :param operation_name: + :param instance_id: + :param exception: + :return: + """ + op_prefix = operation_name.split(".")[-1] + runtime_prop = op_prefix + '_' + instance_id + + if operation_name == CLOUDIFY_CREATE_OPERATION: + if not exception: + _ctx.instance.runtime_properties[runtime_prop] = True + else: + del _ctx.instance.runtime_properties[runtime_prop] + # skip operations since resource_id is not assigned to take action + elif operation_name in CLOUDIFY_OPERATIONS_WITHOUT_CREATE: + _ctx.logger.info("ignoring action since resource_id is not set") + return False + return True + + def _check_valid_resource_id_with_operation(kw, exception=False): """ function used to check if we should do the requested operation from @@ -993,161 +1169,37 @@ def _check_valid_resource_id_with_operation(kw, exception=False): _ctx = _find_context_in_kw(kw) or ctx resource_id = None - # get resource id and operation_name - operation_name = _ctx.operation.name + instance_id = None + + operation_name = _ctx.operation.name or "" + if _ctx.type == context.NODE_INSTANCE: resource_id = _ctx.instance.runtime_properties.get( OPENSTACK_ID_PROPERTY) + instance_id = _ctx.instance.id elif _ctx.type == context.RELATIONSHIP_INSTANCE: resource_id = _ctx.source.instance.runtime_properties.get( OPENSTACK_ID_PROPERTY) + instance_id = _ctx.source.instance.id # check resource_id if resource_id: - instance_id = _ctx.source.instance.id \ - if _ctx.type == context.RELATIONSHIP_INSTANCE \ - else _ctx.instance.id - if operation_name == CLOUDIFY_CREATE_OPERATION: - if not is_external_resource(_ctx): - _ctx.logger.info("resource is already created") - return False - else: - if not exception: - if _ctx.instance.runtime_properties.get( - 'create_' + instance_id): - _ctx.logger.info("resource is already created") - return False - else: - _ctx.instance.runtime_properties['create_' + - instance_id] = True - else: - del _ctx.instance.runtime_properties['create_' + - instance_id] - elif operation_name == CLOUDIFY_CONFIGURE_OPERATION: - if not exception: - if _ctx.instance.runtime_properties.get('conf_'+instance_id): - _ctx.logger.info("resource is already configured") - return False - else: - _ctx.instance.runtime_properties['conf_' + - instance_id] = True - else: - del _ctx.instance.runtime_properties['conf_' + - instance_id] - elif operation_name == CLOUDIFY_START_OPERATION: - if not exception: - if _ctx.instance.runtime_properties.get('start_'+instance_id): - _ctx.logger.info("resource is already started") - return False - else: - if _ctx.instance.runtime_properties. \ - get('stop_' + instance_id): - del _ctx.instance.runtime_properties['stop_' + - instance_id] - _ctx.instance.runtime_properties['start_' + - instance_id] = True - else: - _ctx.instance.runtime_properties['stop_' + - instance_id] = True - del _ctx.instance.runtime_properties['start_' + - instance_id] - elif operation_name == CLOUDIFY_STOP_OPERATION: - if not exception: - if _ctx.instance.runtime_properties.get('stop_'+instance_id): - _ctx.logger.info("resource is already stopped") - return False - else: - if _ctx.instance.runtime_properties. \ - get('start_' + instance_id): - del _ctx.instance.runtime_properties['start_' + - instance_id] - _ctx.instance.runtime_properties['stop_' + - instance_id] = True - else: - _ctx.instance.runtime_properties['start_' + - instance_id] = True - del _ctx.instance.runtime_properties['stop_' + - instance_id] + if operation_name in CLOUDIFY_CONF_CYCLE_OPERATION: + return handle_conf_cycle_operations(_ctx, operation_name, + instance_id, exception) + elif operation_name == CLOUDIFY_DELETE_OPERATION: - if not exception: - if _ctx.instance.runtime_properties. \ - get('conf_' + instance_id): - del _ctx.instance.runtime_properties['conf_' + - instance_id] - if is_external_resource(_ctx): - if _ctx.instance.runtime_properties. \ - get('create_' + instance_id): - del _ctx.instance.runtime_properties['create_' + - instance_id] - else: - _ctx.instance.runtime_properties['conf_' + - instance_id] = True - if is_external_resource(_ctx): - _ctx.instance.runtime_properties['create_' + - instance_id] = True - if _ctx.type == context.RELATIONSHIP_INSTANCE: - target_id = _ctx.target.instance.id - if operation_name == CLOUDIFY_ESTABLISH_OPERATION: - if not exception: - if _ctx.source.instance.runtime_properties. \ - get('rel_' + target_id): - _ctx.logger.info("resource relation to {} already " - "established".format(target_id)) - return False - else: - _ctx.source.instance.runtime_properties['rel_' + - target_id] \ - = True - else: - del _ctx.source.instance.runtime_properties['rel_' + - target_id] - elif operation_name == CLOUDIFY_UNLINK_OPERATION: - if not exception: - if _ctx.source.instance.runtime_properties. \ - get('rel_' + target_id): - del _ctx.source.instance.runtime_properties['rel_' + - target_id] - else: - _ctx.source.instance.runtime_properties['rel_' + - target_id] = True - elif operation_name == CLOUDIFY_PRE_CONFIGURE_OPERATION: - if not exception: - if _ctx.source.instance.runtime_properties. \ - get('pre_rel_' + target_id): - _ctx.logger.info("resource pre-conf relation " - "to {} already " - "established".format(target_id)) - return False - _ctx.source.instance.runtime_properties['pre_rel_' + - target_id] = True - else: - del _ctx.source.instance.runtime_properties['pre_rel_' + - target_id] - elif operation_name == CLOUDIFY_POST_CONFIGURE_OPERATION: - if not exception: - if _ctx.source.instance.runtime_properties. \ - get('post_rel_' + target_id): - _ctx.logger.info("resource post-conf relation " - "to {} already " - "established".format(target_id)) - return False - _ctx.source.instance.runtime_properties['post_rel_' + - target_id] = True - else: - del _ctx.source.instance.runtime_properties['post_rel_' + - target_id] + return handle_delete_operations(_ctx, instance_id, exception) + + elif operation_name in CLOUDIFY_RELATION_CYCLE_OPERATION: + return handle_rel_cycle_operations(_ctx, operation_name, exception) + + elif operation_name == CLOUDIFY_UNLINK_OPERATION: + return handle_unlink_operations(_ctx, exception) + else: - # skip operations since resource_id is not assigned to take action - if operation_name in [CLOUDIFY_PRE_CONFIGURE_OPERATION, - CLOUDIFY_CONFIGURE_OPERATION, - CLOUDIFY_POST_CONFIGURE_OPERATION, - CLOUDIFY_START_OPERATION, - CLOUDIFY_STOP_OPERATION, - CLOUDIFY_DELETE_OPERATION, - CLOUDIFY_UNLINK_OPERATION, - CLOUDIFY_ESTABLISH_OPERATION]: - _ctx.logger.info("ignoring action since resource_id is not set") - return False + return handle_no_resource_id_operations(_ctx, operation_name, + instance_id, exception) return True diff --git a/openstack_plugin_common/tests/openstack_client_tests.py b/openstack_plugin_common/tests/openstack_client_tests.py index b833582f..b52e1317 100644 --- a/openstack_plugin_common/tests/openstack_client_tests.py +++ b/openstack_plugin_common/tests/openstack_client_tests.py @@ -20,9 +20,13 @@ import __builtin__ as builtins import mock +from cloudify.constants import NODE_INSTANCE, RELATIONSHIP_INSTANCE from cloudify.exceptions import NonRecoverableError -from cloudify.mocks import MockCloudifyContext +from cloudify.mocks import MockCloudifyContext, MockNodeInstanceContext, \ + MockContext, MockNodeContext +from cloudify.state import current_ctx + import openstack_plugin_common as common @@ -316,16 +320,16 @@ def test___init___multi_region(self, m_session): } with mock.patch.object( - builtins, 'open', - mock.mock_open( - read_data=""" + builtins, 'open', + mock.mock_open( + read_data=""" { "region": "region from file", "other": "this one should get through" } """ - ), - create=True, + ), + create=True, ): common.OpenStackClient('fred', mock_client_class, cfg) @@ -333,7 +337,7 @@ def test___init___multi_region(self, m_session): region_name='test-region', other='this one should get through', session=m_session.return_value, - ) + ) def test__validate_auth_params_missing(self): with self.assertRaises(NonRecoverableError): @@ -739,10 +743,12 @@ def _test_quota_validation(self, amount, quota, failure_expected): def mock_cosmo_list(_): return [x for x in range(0, amount)] + client.cosmo_list = mock_cosmo_list def mock_get_quota(_): return quota + client.get_quota = mock_get_quota if failure_expected: @@ -837,6 +843,7 @@ def _raise_error(*_): def _return_something(*_): return mock.MagicMock() + return_value = _return_something if exists else _raise_error if exists: properties.update({'resource_id': 'rid'}) @@ -879,3 +886,501 @@ def test_raise_quota_error(self): create_if_missing=True, exists=False, client_mock_provided=client_mock) + + +class ValidateResourceIdTests(unittest.TestCase): + + def mock_ctx(self, + ctx_type, + test_vars, + test_id, + test_deployment_id, + test_operation_name, + runtime_properties=None): + ctx = MockContext() + + ctx.node = MockNodeContext(properties=test_vars) + ctx.instance = \ + MockNodeInstanceContext(id=test_id, + runtime_properties=runtime_properties) + ctx.deployment = mock.Mock() + ctx.deployment.id = test_deployment_id + ctx.operation = mock.Mock() + ctx.operation.name = test_operation_name + ctx.type = ctx_type + if ctx_type == RELATIONSHIP_INSTANCE: + ctx.instance = None + ctx.source = MockNodeContext(properties=test_vars) + ctx.source.instance = \ + MockNodeInstanceContext(id=test_id, + runtime_properties=runtime_properties) + ctx.target = MockNodeContext(properties={}) + ctx.target.instance = \ + MockNodeInstanceContext(id='port_12c45', runtime_properties={}) + ctx.logger = mock.Mock() + + current_ctx.set(ctx) + return ctx + + def _test_validate_resource_id(self, + ctx_type, + instance_id, + operation_name, + runtime_properties=None, + exception=False): + node_context = self.mock_ctx(ctx_type, + test_vars={}, + test_id=instance_id, + test_deployment_id='1-sdtw2', + test_operation_name=operation_name, + runtime_properties=runtime_properties) + + kwargs = { + 'ctx': node_context + } + + result = common._check_valid_resource_id_with_operation(kwargs, + exception) + + return node_context, result + + def test_create_with_no_resource_id_no_exception(self): + result, validate_res = \ + self._test_validate_resource_id(NODE_INSTANCE, + 'xyz1', common. + CLOUDIFY_CREATE_OPERATION, {}, + False) + runtime_prop = 'create_xyz1' + self.assertTrue(result.instance.runtime_properties.get(runtime_prop)) + self.assertTrue(validate_res) + + def test_create_with_no_resource_id_with_exception(self): + result, validate_res = \ + self._test_validate_resource_id(NODE_INSTANCE, + 'xyz1', common. + CLOUDIFY_CREATE_OPERATION, + {'create_xyz1': True}, + True) + runtime_prop = 'create_xyz1' + self.assertIsNone(result.instance.runtime_properties.get(runtime_prop)) + self.assertTrue(validate_res) + + def test_create_with_resource_id_no_exception(self): + result, validate_res = \ + self._test_validate_resource_id(NODE_INSTANCE, + 'xyz1', common. + CLOUDIFY_CREATE_OPERATION, + {'external_id': '123-43-312'}, + False) + runtime_prop = 'create_xyz1' + self.assertTrue(result.instance.runtime_properties.get(runtime_prop)) + self.assertTrue(validate_res) + + def test_create_with_resource_id_with_exception(self): + result, validate_res = \ + self._test_validate_resource_id(NODE_INSTANCE, + 'xyz1', common. + CLOUDIFY_CREATE_OPERATION, + {'external_id': '123-43-312', + 'create_xyz1': True}, + True) + runtime_prop = 'create_xyz1' + self.assertIsNone(result.instance.runtime_properties.get(runtime_prop)) + self.assertTrue(validate_res) + + def test_configure_with_resource_id_no_exception(self): + result, validate_res = \ + self._test_validate_resource_id(NODE_INSTANCE, + 'xyz1', common. + CLOUDIFY_CONFIGURE_OPERATION, + {'external_id': '123-43-312'}, + False) + runtime_prop = 'configure_xyz1' + self.assertTrue(result.instance.runtime_properties.get(runtime_prop)) + self.assertTrue(validate_res) + + def test_configure_with_resource_id_with_exception(self): + result, validate_res = \ + self._test_validate_resource_id(NODE_INSTANCE, + 'xyz1', common. + CLOUDIFY_CONFIGURE_OPERATION, + {'configure_xyz1': True, + 'external_id': '123-43-312'}, + True) + runtime_prop = 'configure_xyz1' + self.assertIsNone(result.instance.runtime_properties.get(runtime_prop)) + self.assertTrue(validate_res) + + def test_configure_no_resource_id_with_exception(self): + result, validate_res = \ + self._test_validate_resource_id(NODE_INSTANCE, + 'xyz1', common. + CLOUDIFY_CONFIGURE_OPERATION, + {}, + True) + runtime_prop = 'configure_xyz1' + self.assertIsNone(result.instance.runtime_properties.get(runtime_prop)) + self.assertFalse(validate_res) + + def test_configure_no_resource_id_no_exception(self): + result, validate_res = \ + self._test_validate_resource_id(NODE_INSTANCE, + 'xyz1', common. + CLOUDIFY_CONFIGURE_OPERATION, + {}, + False) + runtime_prop = 'configure_xyz1' + self.assertIsNone(result.instance.runtime_properties.get(runtime_prop)) + self.assertFalse(validate_res) + + def test_start_with_resource_id_no_exception(self): + result, validate_res = \ + self._test_validate_resource_id(NODE_INSTANCE, + 'xyz1', common. + CLOUDIFY_START_OPERATION, + {'external_id': '123-43-312'}, + False) + runtime_prop = 'start_xyz1' + self.assertTrue(result.instance.runtime_properties.get(runtime_prop)) + self.assertTrue(validate_res) + + def test_start_no_resource_id_no_exception(self): + result, validate_res = \ + self._test_validate_resource_id(NODE_INSTANCE, + 'xyz1', common. + CLOUDIFY_START_OPERATION, + {}, + False) + runtime_prop = 'start_xyz1' + self.assertIsNone(result.instance.runtime_properties.get(runtime_prop)) + self.assertFalse(validate_res) + + def test_start_no_resource_id_with_exception(self): + result, validate_res = \ + self._test_validate_resource_id(NODE_INSTANCE, + 'xyz1', common. + CLOUDIFY_START_OPERATION, + {}, + True) + runtime_prop = 'start_xyz1' + self.assertIsNone(result.instance.runtime_properties.get(runtime_prop)) + self.assertFalse(validate_res) + + def test_start_with_resource_id_with_exception(self): + result, validate_res = \ + self._test_validate_resource_id(NODE_INSTANCE, + 'xyz1', common. + CLOUDIFY_START_OPERATION, + {'start_xyz1': True, + 'external_id': '123-43-312'}, + True) + runtime_prop = 'start_xyz1' + self.assertIsNone(result.instance.runtime_properties.get(runtime_prop)) + self.assertTrue(validate_res) + + def test_stop_with_resource_id_no_exception(self): + result, validate_res = \ + self._test_validate_resource_id(NODE_INSTANCE, + 'xyz1', common. + CLOUDIFY_STOP_OPERATION, + {'external_id': '123-43-312'}, + False) + runtime_prop = 'stop_xyz1' + self.assertTrue(result.instance.runtime_properties.get(runtime_prop)) + self.assertTrue(validate_res) + + def test_stop_with_resource_id_with_exception(self): + result, validate_res = \ + self._test_validate_resource_id(NODE_INSTANCE, + 'xyz1', common. + CLOUDIFY_STOP_OPERATION, + {'stop_xyz1': True, + 'external_id': '123-43-312'}, + True) + runtime_prop = 'stop_xyz1' + self.assertIsNone(result.instance.runtime_properties.get(runtime_prop)) + self.assertTrue(validate_res) + + def test_stop_no_resource_id_no_exception(self): + result, validate_res = \ + self._test_validate_resource_id(NODE_INSTANCE, + 'xyz1', common. + CLOUDIFY_STOP_OPERATION, + {}, + False) + runtime_prop = 'stop_xyz1' + self.assertIsNone(result.instance.runtime_properties.get(runtime_prop)) + self.assertFalse(validate_res) + + def test_stop_no_resource_id_with_exception(self): + result, validate_res = \ + self._test_validate_resource_id(NODE_INSTANCE, + 'xyz1', common. + CLOUDIFY_STOP_OPERATION, + {}, + True) + runtime_prop = 'stop_xyz1' + self.assertIsNone(result.instance.runtime_properties.get(runtime_prop)) + self.assertFalse(validate_res) + + def test_delete_with_resource_id_no_exception(self): + result, validate_res = \ + self._test_validate_resource_id(NODE_INSTANCE, + 'xyz1', common. + CLOUDIFY_DELETE_OPERATION, + {'external_id': '123-43-312', + 'create_xyz1': True, + 'configure_xyz1': True}, + False) + runtime_prop = 'create_xyz1' + self.assertIsNone(result.instance.runtime_properties.get(runtime_prop)) + runtime_prop = 'configure_xyz1' + self.assertIsNone(result.instance.runtime_properties.get(runtime_prop)) + self.assertTrue(validate_res) + + def test_delete_with_resource_id_with_exception(self): + result, validate_res = \ + self._test_validate_resource_id(NODE_INSTANCE, + 'xyz1', common. + CLOUDIFY_DELETE_OPERATION, + {'external_id': '123-43-312'}, + True) + runtime_prop = 'create_xyz1' + self.assertTrue(result.instance.runtime_properties.get(runtime_prop)) + runtime_prop = 'configure_xyz1' + self.assertTrue(result.instance.runtime_properties.get(runtime_prop)) + self.assertTrue(validate_res) + + def test_delete_no_resource_id_no_exception(self): + result, validate_res = \ + self._test_validate_resource_id(NODE_INSTANCE, + 'xyz1', common. + CLOUDIFY_DELETE_OPERATION, + {}, + False) + runtime_prop = 'create_xyz1' + self.assertIsNone(result.instance.runtime_properties.get(runtime_prop)) + self.assertFalse(validate_res) + + def test_delete_no_resource_id_with_exception(self): + result, validate_res = \ + self._test_validate_resource_id(NODE_INSTANCE, + 'xyz1', common. + CLOUDIFY_DELETE_OPERATION, + {}, + True) + runtime_prop = 'create_xyz1' + self.assertIsNone(result.instance.runtime_properties.get(runtime_prop)) + self.assertFalse(validate_res) + + def test_pre_conf_with_resource_id_no_exception(self): + result, validate_res = \ + self._test_validate_resource_id(RELATIONSHIP_INSTANCE, 'xyz1', + common. + CLOUDIFY_PRE_CONFIGURE_OPERATION, + {'external_id': '123-43-312', + 'create_xyz1': True, + 'configure_xyz1': True}, + False) + runtime_prop = 'preconfigure_port_12c45' + self.assertTrue(result.source.instance.runtime_properties. + get(runtime_prop)) + self.assertTrue(validate_res) + + def test_pre_conf_with_resource_id_with_exception(self): + result, validate_res = \ + self._test_validate_resource_id(RELATIONSHIP_INSTANCE, 'xyz1', + common. + CLOUDIFY_PRE_CONFIGURE_OPERATION, + {'external_id': '123-43-312', + 'create_xyz1': True, + 'configure_xyz1': True, + 'preconfigure_port_12c45': True}, + True) + runtime_prop = 'preconfigure_port_12c45' + self.assertIsNone(result.source.instance.runtime_properties. + get(runtime_prop)) + self.assertTrue(validate_res) + + def test_pre_conf_no_resource_id_no_exception(self): + result, validate_res = \ + self._test_validate_resource_id(RELATIONSHIP_INSTANCE, 'xyz1', + common. + CLOUDIFY_PRE_CONFIGURE_OPERATION, + {}, + False) + runtime_prop = 'preconfigure_port_12c45' + self.assertIsNone(result.source.instance.runtime_properties. + get(runtime_prop)) + self.assertFalse(validate_res) + + def test_pre_conf_no_resource_id_with_exception(self): + result, validate_res = \ + self._test_validate_resource_id(RELATIONSHIP_INSTANCE, 'xyz1', + common. + CLOUDIFY_PRE_CONFIGURE_OPERATION, + {}, + True) + runtime_prop = 'preconfigure_port_12c45' + self.assertIsNone(result.source.instance.runtime_properties. + get(runtime_prop)) + self.assertFalse(validate_res) + + def test_post_conf_with_resource_id_no_exception(self): + result, validate_res = \ + self._test_validate_resource_id(RELATIONSHIP_INSTANCE, 'xyz1', + common. + CLOUDIFY_POST_CONFIGURE_OPERATION, + {'external_id': '123-43-312', + 'create_xyz1': True, + 'configure_xyz1': True}, + False) + runtime_prop = 'postconfigure_port_12c45' + self.assertTrue(result.source.instance.runtime_properties. + get(runtime_prop)) + self.assertTrue(validate_res) + + def test_post_conf_with_resource_id_with_exception(self): + result, validate_res = \ + self._test_validate_resource_id(RELATIONSHIP_INSTANCE, 'xyz1', + common. + CLOUDIFY_POST_CONFIGURE_OPERATION, + {'external_id': '123-43-312', + 'create_xyz1': True, + 'configure_xyz1': True, + 'postconfigure_port_12c45': True}, + True) + runtime_prop = 'postconfigure_port_12c45' + self.assertIsNone(result.source.instance.runtime_properties. + get(runtime_prop)) + self.assertTrue(validate_res) + + def test_post_conf_no_resource_id_no_exception(self): + result, validate_res = \ + self._test_validate_resource_id(RELATIONSHIP_INSTANCE, 'xyz1', + common. + CLOUDIFY_POST_CONFIGURE_OPERATION, + {}, + False) + runtime_prop = 'postconfigure_port_12c45' + self.assertIsNone(result.source.instance.runtime_properties. + get(runtime_prop)) + self.assertFalse(validate_res) + + def test_post_conf_no_resource_id_with_exception(self): + result, validate_res = \ + self._test_validate_resource_id(RELATIONSHIP_INSTANCE, 'xyz1', + common. + CLOUDIFY_POST_CONFIGURE_OPERATION, + {}, + True) + runtime_prop = 'postconfigure_port_12c45' + self.assertIsNone(result.source.instance.runtime_properties. + get(runtime_prop)) + self.assertFalse(validate_res) + + def test_establish_with_resource_id_no_exception(self): + result, validate_res = \ + self._test_validate_resource_id(RELATIONSHIP_INSTANCE, 'xyz1', + common. + CLOUDIFY_ESTABLISH_OPERATION, + {'external_id': '123-43-312', + 'create_xyz1': True, + 'configure_xyz1': True}, + False) + runtime_prop = 'establish_port_12c45' + self.assertTrue(result.source.instance.runtime_properties. + get(runtime_prop)) + self.assertTrue(validate_res) + + def test_establish_with_resource_id_with_exception(self): + result, validate_res = \ + self._test_validate_resource_id(RELATIONSHIP_INSTANCE, 'xyz1', + common. + CLOUDIFY_ESTABLISH_OPERATION, + {'external_id': '123-43-312', + 'create_xyz1': True, + 'configure_xyz1': True, + 'establish_port_12c45': True}, + True) + runtime_prop = 'establish_port_12c45' + self.assertIsNone(result.source.instance.runtime_properties. + get(runtime_prop)) + self.assertTrue(validate_res) + + def test_establish_no_resource_id_no_exception(self): + result, validate_res = \ + self._test_validate_resource_id(RELATIONSHIP_INSTANCE, 'xyz1', + common. + CLOUDIFY_ESTABLISH_OPERATION, + {}, + False) + runtime_prop = 'establish_port_12c45' + self.assertIsNone(result.source.instance.runtime_properties. + get(runtime_prop)) + self.assertFalse(validate_res) + + def test_establish_no_resource_id_with_exception(self): + result, validate_res = \ + self._test_validate_resource_id(RELATIONSHIP_INSTANCE, 'xyz1', + common. + CLOUDIFY_ESTABLISH_OPERATION, + {}, + True) + runtime_prop = 'establish_port_12c45' + self.assertIsNone(result.source.instance.runtime_properties. + get(runtime_prop)) + self.assertFalse(validate_res) + + def test_unlink_with_resource_id_no_exception(self): + result, validate_res = \ + self._test_validate_resource_id(RELATIONSHIP_INSTANCE, 'xyz1', + common. + CLOUDIFY_UNLINK_OPERATION, + {'external_id': '123-43-312', + 'create_xyz1': True, + 'configure_xyz1': True, + 'establish_port_12c45': True}, + False) + runtime_prop = 'establish_port_12c45' + self.assertIsNone(result.source.instance.runtime_properties. + get(runtime_prop)) + self.assertTrue(validate_res) + + def test_unlink_with_resource_id_with_exception(self): + result, validate_res = \ + self._test_validate_resource_id(RELATIONSHIP_INSTANCE, 'xyz1', + common. + CLOUDIFY_UNLINK_OPERATION, + {'external_id': '123-43-312', + 'create_xyz1': True, + 'configure_xyz1': True}, + True) + runtime_prop = 'establish_port_12c45' + self.assertTrue(result.source.instance.runtime_properties. + get(runtime_prop)) + self.assertTrue(validate_res) + + def test_unlink_no_resource_id_no_exception(self): + result, validate_res = \ + self._test_validate_resource_id(RELATIONSHIP_INSTANCE, 'xyz1', + common. + CLOUDIFY_UNLINK_OPERATION, + {}, + False) + runtime_prop = 'establish_port_12c45' + self.assertIsNone(result.source.instance.runtime_properties. + get(runtime_prop)) + self.assertFalse(validate_res) + + def test_unlink_no_resource_id_with_exception(self): + result, validate_res = \ + self._test_validate_resource_id(RELATIONSHIP_INSTANCE, 'xyz1', + common. + CLOUDIFY_UNLINK_OPERATION, + {}, + True) + runtime_prop = 'establish_port_12c45' + self.assertIsNone(result.source.instance.runtime_properties. + get(runtime_prop)) + self.assertFalse(validate_res)