From 9b6a2fc77b13170210c8f113532a20edbe5b660c Mon Sep 17 00:00:00 2001 From: Jason Zhang Date: Sun, 27 Apr 2025 20:11:53 +0800 Subject: [PATCH] add workspace --- .../varfmt.cpython-312.pyc.140643178709184 | 0 .../c7n_huaweicloud/c7n_huaweicloud/client.py | 13 +- .../c7n_huaweicloud/resources/resource_map.py | 1 + .../c7n_huaweicloud/resources/workspace.py | 272 ++++++++++++++++++ .../data/flights/workspace_connection_status | 55 ++++ .../tests/data/flights/workspace_query | 55 ++++ .../tests/data/flights/workspace_terminate | 110 +++++++ .../data/flights/workspace_terminate_batch | 110 +++++++ tools/c7n_huaweicloud/tests/test_workspace.py | 119 ++++++++ 9 files changed, 734 insertions(+), 1 deletion(-) create mode 100644 c7n/__pycache__/varfmt.cpython-312.pyc.140643178709184 create mode 100644 tools/c7n_huaweicloud/c7n_huaweicloud/resources/workspace.py create mode 100644 tools/c7n_huaweicloud/tests/data/flights/workspace_connection_status create mode 100644 tools/c7n_huaweicloud/tests/data/flights/workspace_query create mode 100644 tools/c7n_huaweicloud/tests/data/flights/workspace_terminate create mode 100644 tools/c7n_huaweicloud/tests/data/flights/workspace_terminate_batch create mode 100644 tools/c7n_huaweicloud/tests/test_workspace.py diff --git a/c7n/__pycache__/varfmt.cpython-312.pyc.140643178709184 b/c7n/__pycache__/varfmt.cpython-312.pyc.140643178709184 new file mode 100644 index 000000000..e69de29bb diff --git a/tools/c7n_huaweicloud/c7n_huaweicloud/client.py b/tools/c7n_huaweicloud/c7n_huaweicloud/client.py index 34b6409c6..dfc21df01 100644 --- a/tools/c7n_huaweicloud/c7n_huaweicloud/client.py +++ b/tools/c7n_huaweicloud/c7n_huaweicloud/client.py @@ -103,6 +103,9 @@ from huaweicloudsdkram.v1.region.ram_region import RamRegion from huaweicloudsdkcc.v3 import CcClient, ListCentralNetworksRequest from huaweicloudsdkcc.v3.region.cc_region import CcRegion +from huaweicloudsdkworkspace.v2 import WorkspaceClient, ListDesktopsDetailRequest +from huaweicloudsdkworkspace.v2.region.workspace_region import WorkspaceRegion + log = logging.getLogger("custodian.huaweicloud.client") @@ -300,6 +303,13 @@ def client(self, service): .with_region(ImsRegion.value_of(self.region)) .build() ) + elif service == "workspace": + client = ( + WorkspaceClient.new_builder() + .with_credentials(credentials) + .with_region(WorkspaceRegion.value_of(self.region)) + .build() + ) elif ( service == "cbr-backup" or service == "cbr-vault" or service == "cbr-policy" ): @@ -474,7 +484,8 @@ def request(self, service): request = ListOrganizationalUnitsRequest() elif service == "org-account": request = ListAccountsRequest() - + elif service == "workspace": + request = ListDesktopsDetailRequest() elif service == "kms": request = ListKeysRequest() request.body = ListKeysRequestBody(key_spec="ALL") diff --git a/tools/c7n_huaweicloud/c7n_huaweicloud/resources/resource_map.py b/tools/c7n_huaweicloud/c7n_huaweicloud/resources/resource_map.py index b588a9839..8bc94ee6d 100644 --- a/tools/c7n_huaweicloud/c7n_huaweicloud/resources/resource_map.py +++ b/tools/c7n_huaweicloud/c7n_huaweicloud/resources/resource_map.py @@ -45,4 +45,5 @@ "huaweicloud.antiddos-eip": "c7n_huaweicloud.resources.antiddos.Eip", "huaweicloud.kafka": "c7n_huaweicloud.resources.kafka.Kafka", "huaweicloud.cc-cloud-connection": "c7n_huaweicloud.resources.cc.CloudConnection", + "huaweicloud.workspace-desktop": "c7n_huaweicloud.resources.workspace.Workspace" } diff --git a/tools/c7n_huaweicloud/c7n_huaweicloud/resources/workspace.py b/tools/c7n_huaweicloud/c7n_huaweicloud/resources/workspace.py new file mode 100644 index 000000000..40c88334b --- /dev/null +++ b/tools/c7n_huaweicloud/c7n_huaweicloud/resources/workspace.py @@ -0,0 +1,272 @@ +# Copyright The Cloud Custodian Authors. +# SPDX-License-Identifier: Apache-2.0 + +import logging + +from c7n.filters import Filter +from c7n.utils import type_schema, local_session + +from c7n_huaweicloud.provider import resources +from c7n_huaweicloud.query import QueryResourceManager, TypeInfo +from c7n_huaweicloud.actions.base import HuaweiCloudBaseAction +from huaweicloudsdkworkspace.v2 import BatchDeleteDesktopsRequest + +log = logging.getLogger('custodian.huaweicloud.workspace') + + +@resources.register('workspace-desktop') +class Workspace(QueryResourceManager): + """Huawei Cloud Workspace Resource Manager + + This resource type manages cloud desktop instances in Huawei Cloud Workspace service. + """ + class resource_type(TypeInfo): + service = 'workspace' + enum_spec = ('list_desktops_detail', 'desktops', 'offset') + id = 'desktop_id' + name = 'computer_name' + tag_resource_type = 'workspace-desktop' + date = 'created' + # Enable configuration audit support + config_resource_support = True + + def augment(self, resources): + """Enhance resource data + + This method ensures each resource has a valid ID field and adds additional + information as needed. + + :param resources: List of resource objects + :return: Enhanced resource object list + """ + for r in resources: + # Ensure each resource has an ID field + if 'id' not in r and self.resource_type.id in r: + r['id'] = r[self.resource_type.id] + + # Convert tags to standard format + if 'tags' in r: + r['Tags'] = self.normalize_tags(r['tags']) + + return resources + + def normalize_tags(self, tags): + """Convert tags to standard format + + :param tags: Original tag data + :return: Normalized tag dictionary + """ + if not tags: + return {} + + if isinstance(tags, dict): + return tags + + normalized = {} + for tag in tags: + if isinstance(tag, dict): + if 'key' in tag and 'value' in tag: + normalized[tag['key']] = tag['value'] + else: + for k, v in tag.items(): + normalized[k] = v + elif isinstance(tag, str) and '=' in tag: + k, v = tag.split('=', 1) + normalized[k] = v + + return normalized + + +@Workspace.filter_registry.register('connection-status') +class ConnectionStatusFilter(Filter): + """Filter desktops based on user connection information + + :example: + + .. code-block:: yaml + + policies: + - name: find-unregister-desktops + resource: huaweicloud.workspaces + filters: + - type: connection-status + op: eq + value: UNREGISTER + """ + schema = { + 'type': 'object', + 'properties': { + 'type': {'enum': ['connection-status']}, + 'op': {'enum': ['eq', 'ne', 'in', 'not-in']}, + 'value': {'oneOf': [{'type': 'string'}, {'type': 'array', 'items': {'type': 'string'}}]} + }, + 'required': ['type', 'op', 'value'] + } + schema_alias = False + annotation_key = 'c7n:ConnectionStatus' + + def process(self, resources, event=None): + op = self.data.get('op') + expected = self.data.get('value') + + results = [] + for r in resources: + login_status = r.get('login_status') + + if login_status is None: + continue + + if op == 'eq' and login_status == expected: + results.append(r) + elif op == 'ne' and login_status != expected: + results.append(r) + elif op == 'in' and login_status in expected: + results.append(r) + elif op == 'not-in' and login_status not in expected: + results.append(r) + + return results + + +@Workspace.action_registry.register('terminate') +class TerminateWorkspace(HuaweiCloudBaseAction): + """Terminate cloud desktops + + This action uses DeleteDesktop or BatchDeleteDesktops API to terminate one or more cloud desktop instances. + + :example: + + .. code-block:: yaml + + policies: + - name: terminate-inactive-workspaces + resource: huaweicloud.workspaces + filters: + - type: connection-status + op: eq + value: UNREGISTER + actions: + - terminate + """ + + schema = type_schema('terminate') + + def process(self, resources): + """Process resources in batch + + :param resources: List of resources to process + :return: Operation results + """ + if not resources: + return [] + + return self.batch_terminate(resources) + + def batch_terminate(self, resources): + """Terminate cloud desktops in batch + + :param resources: List of resources + :return: Operation results + """ + session = local_session(self.manager.session_factory) + client = session.client('workspace') + + # Extract desktop IDs + desktop_ids = [r['id'] for r in resources] + + # Process up to 100 at a time + results = [] + for i in range(0, len(desktop_ids), 100): + batch = desktop_ids[i:i+100] + try: + request = BatchDeleteDesktopsRequest() + request.body = {"desktop_ids": batch} + response = client.batch_delete_desktops(request) + results.append(response.to_dict()) + self.log.info(f"Successfully submitted termination request for {len(batch)} desktops") + except Exception as e: + self.log.error(f"Failed to terminate desktops: {e}") + + return results + + def perform_action(self, resource): + return super().perform_action(resource) + +# Example Policies +""" +Here are some common Huawei Cloud Workspace policy examples: + +1. Mark Inactive Desktops Policy: +```yaml +policies: + - name: terminate-inactive-workspaces + resource: huaweicloud.workspaces + filters: + - type: connection-status + op: eq + value: UNREGISTER + actions: + - terminate +``` + +2. Terminate Marked Inactive Desktops Policy: +```yaml +policies: + - name: terminate-marked-workspaces + resource: huaweicloud.workspaces + description: | + Terminate desktops marked for cleanup + filters: + - type: marked-for-op + op: terminate + tag: custodian_cleanup + actions: + - terminate +``` + +3. Tag Untagged Desktops: +```yaml +policies: + - name: tag-untagged-workspaces + resource: huaweicloud.workspaces + description: | + Add Owner tag to desktops missing it + filters: + - tag:Owner: absent + actions: + - type: tag + key: Owner + value: Unknown +``` + +4. Auto-tag Desktop Creator: +```yaml +policies: + - name: tag-workspace-creator + resource: huaweicloud.workspaces + description: | + Listen for desktop creation events and auto-tag creator + mode: + type: cloudtrace + events: + - source: "Workspace" + event: "createDesktop" + ids: "desktop_id" + actions: + - type: auto-tag-user + tag: Creator +``` + +5. Find Non-compliant Desktops: +```yaml +policies: + - name: find-noncompliant-workspaces + resource: huaweicloud.workspaces + description: | + Find desktops that don't comply with security rules + filters: + - type: config-compliance + rules: + - workspace-security-rule +``` +""" diff --git a/tools/c7n_huaweicloud/tests/data/flights/workspace_connection_status b/tools/c7n_huaweicloud/tests/data/flights/workspace_connection_status new file mode 100644 index 000000000..2c183d8e1 --- /dev/null +++ b/tools/c7n_huaweicloud/tests/data/flights/workspace_connection_status @@ -0,0 +1,55 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, zstd + Connection: + - keep-alive + Content-Type: + - application/json + Host: + - workspace.ap-southeast-1.myhuaweicloud.com + User-Agent: + - huaweicloud-usdk-python/3.0 + X-Project-Id: + - ap-southeat-1 + X-Sdk-Date: + - 20250427T114555Z + method: GET + uri: https://workspace.ap-southeast-1.myhuaweicloud.com/v2/ap-southeat-1/desktops/detail?limit=100&offset=0 + response: + body: + string: '{"desktops":[{"desktop_id":"test-desktop-id","computer_name":"test-desktop","status":"ACTIVE","login_status":"UNREGISTER","user_name":"test-user","created":"2025-04-27T11:45:55Z","tags":[{"key":"environment","value":"testing"}],"security_groups":[{"id":"sg-12345678","name":"default"}],"product":{"product_id":"product-123","flavor_id":"flavor-123","type":"DEDICATED","cpu":"4","memory":"8GB"}}],"total_count":1}' + headers: + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Sun, 27 Apr 2025 11:45:55 GMT + Server: + - CloudWAF + Set-Cookie: + - HWWAFSESTIME=1745754355448; path=/ + - HWWAFSESID=a452b73af1f922b862; path=/ + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Request-Id: + - e81554430f5027311dda7372e5c00f0d + X-XSS-Protection: + - 1; mode=block; + status: + code: 200 + message: success +version: 1 diff --git a/tools/c7n_huaweicloud/tests/data/flights/workspace_query b/tools/c7n_huaweicloud/tests/data/flights/workspace_query new file mode 100644 index 000000000..db8ee74b9 --- /dev/null +++ b/tools/c7n_huaweicloud/tests/data/flights/workspace_query @@ -0,0 +1,55 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, zstd + Connection: + - keep-alive + Content-Type: + - application/json + Host: + - workspace.ap-southeast-1.myhuaweicloud.com + User-Agent: + - huaweicloud-usdk-python/3.0 + X-Project-Id: + - ap-southeat-1 + X-Sdk-Date: + - 20250427T114555Z + method: GET + uri: https://workspace.ap-southeast-1.myhuaweicloud.com/v2/ap-southeat-1/desktops/detail?limit=100&offset=0 + response: + body: + string: '{"desktops":[{"desktop_id":"test-desktop-id","computer_name":"test-desktop","status":"ACTIVE","login_status":"UNREGISTER","user_name":"test-user","created":"2025-04-27T11:45:55Z","tags":[{"key":"environment","value":"testing"}],"security_groups":[{"id":"sg-12345678","name":"default"}],"product":{"product_id":"product-123","flavor_id":"flavor-123","type":"DEDICATED","cpu":"4","memory":"8GB"}}],"total_count":1}' + headers: + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Sun, 27 Apr 2025 11:45:55 GMT + Server: + - CloudWAF + Set-Cookie: + - HWWAFSESID=b22a9736f9382367b1; path=/ + - HWWAFSESTIME=1745754355391; path=/ + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Request-Id: + - cfbf4ebb1948efc3c156b9e7d1df0dcf + X-XSS-Protection: + - 1; mode=block; + status: + code: 200 + message: success +version: 1 diff --git a/tools/c7n_huaweicloud/tests/data/flights/workspace_terminate b/tools/c7n_huaweicloud/tests/data/flights/workspace_terminate new file mode 100644 index 000000000..c84867502 --- /dev/null +++ b/tools/c7n_huaweicloud/tests/data/flights/workspace_terminate @@ -0,0 +1,110 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, zstd + Connection: + - keep-alive + Content-Type: + - application/json + Host: + - workspace.ap-southeast-1.myhuaweicloud.com + User-Agent: + - huaweicloud-usdk-python/3.0 + X-Project-Id: + - ap-southeat-1 + X-Sdk-Date: + - 20250427T114555Z + method: GET + uri: https://workspace.ap-southeast-1.myhuaweicloud.com/v2/ap-southeat-1/desktops/detail?limit=100&offset=0 + response: + body: + string: '{"desktops":[{"desktop_id":"test-desktop-id","computer_name":"test-desktop","status":"ACTIVE","login_status":"UNREGISTER","user_name":"test-user","created":"2025-04-27T11:45:55Z","tags":[{"key":"environment","value":"testing"}],"security_groups":[{"id":"sg-12345678","name":"default"}],"product":{"product_id":"product-123","flavor_id":"flavor-123","type":"DEDICATED","cpu":"4","memory":"8GB"}}],"total_count":1}' + headers: + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Sun, 27 Apr 2025 11:45:55 GMT + Server: + - CloudWAF + Set-Cookie: + - HWWAFSESID=b7801762f626697290; path=/ + - HWWAFSESTIME=1745754355553; path=/ + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Request-Id: + - fcc96c424f11bd09a7f7a1b2bb2a8d49 + X-XSS-Protection: + - 1; mode=block; + status: + code: 200 + message: success +- request: + body: '{"desktop_ids": ["test-desktop-id"]}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, zstd + Connection: + - keep-alive + Content-Length: + - '36' + Content-Type: + - application/json + Host: + - workspace.ap-southeast-1.myhuaweicloud.com + User-Agent: + - huaweicloud-usdk-python/3.0 + X-Project-Id: + - ap-southeat-1 + X-Sdk-Date: + - 20250427T120725Z + method: POST + uri: https://workspace.ap-southeast-1.myhuaweicloud.com/v2/ap-southeat-1/desktops/batch-delete + response: + body: + string: '{"error_msg":"Incorrect IAM authentication information: Unauthorized","error_code":"APIGW.0301","request_id":"40640f4489b9cf59b776b28e30523ef5"} + + ' + headers: + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Sun, 27 Apr 2025 12:07:25 GMT + Server: + - CloudWAF + Set-Cookie: + - HWWAFSESTIME=1745755645420; path=/ + - HWWAFSESID=fa78a731a06dec85e0; path=/ + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Request-Id: + - 40640f4489b9cf59b776b28e30523ef5 + X-XSS-Protection: + - 1; mode=block; + status: + code: 401 + message: Unauthorized +version: 1 diff --git a/tools/c7n_huaweicloud/tests/data/flights/workspace_terminate_batch b/tools/c7n_huaweicloud/tests/data/flights/workspace_terminate_batch new file mode 100644 index 000000000..1584fca27 --- /dev/null +++ b/tools/c7n_huaweicloud/tests/data/flights/workspace_terminate_batch @@ -0,0 +1,110 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, zstd + Connection: + - keep-alive + Content-Type: + - application/json + Host: + - workspace.ap-southeast-1.myhuaweicloud.com + User-Agent: + - huaweicloud-usdk-python/3.0 + X-Project-Id: + - ap-southeat-1 + X-Sdk-Date: + - 20250427T114555Z + method: GET + uri: https://workspace.ap-southeast-1.myhuaweicloud.com/v2/ap-southeat-1/desktops/detail?limit=100&offset=0 + response: + body: + string: '{"desktops":[{"desktop_id":"test-desktop-id-1","computer_name":"test-desktop-1","status":"ACTIVE","login_status":"UNREGISTER","user_name":"test-user-1","created":"2025-04-27T11:45:55Z","tags":[{"key":"environment","value":"testing"}],"security_groups":[{"id":"sg-12345678","name":"default"}],"product":{"product_id":"product-123","flavor_id":"flavor-123","type":"DEDICATED","cpu":"4","memory":"8GB"}},{"desktop_id":"test-desktop-id-2","computer_name":"test-desktop-2","status":"ACTIVE","login_status":"UNREGISTER","user_name":"test-user-2","created":"2025-04-27T11:45:55Z","tags":[{"key":"environment","value":"testing"}],"security_groups":[{"id":"sg-12345678","name":"default"}],"product":{"product_id":"product-123","flavor_id":"flavor-123","type":"DEDICATED","cpu":"4","memory":"8GB"}}],"total_count":2}' + headers: + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Sun, 27 Apr 2025 11:45:55 GMT + Server: + - CloudWAF + Set-Cookie: + - HWWAFSESID=a229c737a1c276c37a; path=/ + - HWWAFSESTIME=1745754355676; path=/ + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Request-Id: + - db3a35b0eeef70dd59c088b99005194d + X-XSS-Protection: + - 1; mode=block; + status: + code: 200 + message: success +- request: + body: '{"desktop_ids": ["test-desktop-id-1", "test-desktop-id-2"]}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, zstd + Connection: + - keep-alive + Content-Length: + - '59' + Content-Type: + - application/json + Host: + - workspace.ap-southeast-1.myhuaweicloud.com + User-Agent: + - huaweicloud-usdk-python/3.0 + X-Project-Id: + - ap-southeat-1 + X-Sdk-Date: + - 20250427T120725Z + method: POST + uri: https://workspace.ap-southeast-1.myhuaweicloud.com/v2/ap-southeat-1/desktops/batch-delete + response: + body: + string: '{"error_msg":"Incorrect IAM authentication information: Unauthorized","error_code":"APIGW.0301","request_id":"eedf0f99a86539821010252a4cc67960"} + + ' + headers: + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Sun, 27 Apr 2025 12:07:25 GMT + Server: + - CloudWAF + Set-Cookie: + - HWWAFSESID=e99e0736964012bd2b; path=/ + - HWWAFSESTIME=1745755645470; path=/ + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Request-Id: + - eedf0f99a86539821010252a4cc67960 + X-XSS-Protection: + - 1; mode=block; + status: + code: 401 + message: Unauthorized +version: 1 diff --git a/tools/c7n_huaweicloud/tests/test_workspace.py b/tools/c7n_huaweicloud/tests/test_workspace.py new file mode 100644 index 000000000..0e740a5ee --- /dev/null +++ b/tools/c7n_huaweicloud/tests/test_workspace.py @@ -0,0 +1,119 @@ +# Copyright The Cloud Custodian Authors. +# SPDX-License-Identifier: Apache-2.0 + +from unittest.mock import patch +from huaweicloud_common import BaseTest + + +class WorkspaceTest(BaseTest): + """Test class for Huawei Cloud Workspace resources""" + + # ========================= + # Resource Query Tests + # ========================= + def test_workspace_query(self): + """Test basic workspace resource query""" + factory = self.replay_flight_data('workspace_query') + p = self.load_policy({ + 'name': 'workspace-query-test', + 'resource': 'huaweicloud.workspace-desktop'}, + session_factory=factory) + resources = p.run() + self.assertEqual(len(resources), 1) + self.assertEqual(resources[0]['computer_name'], "test-desktop") + # Verify tag normalization + self.assertTrue('Tags' in resources[0]) + self.assertEqual(resources[0]['Tags'], {'environment': 'testing'}) + + # ========================= + # Filter Tests + # ========================= + def test_connection_status_filter(self): + """Test connection status filter""" + factory = self.replay_flight_data('workspace_connection_status') + # Test equal operator + p_eq = self.load_policy({ + 'name': 'workspace-connection-status-eq', + 'resource': 'huaweicloud.workspace-desktop', + 'filters': [{ + 'type': 'connection-status', + 'op': 'eq', + 'value': 'UNREGISTER' + }]}, + session_factory=factory) + resources_eq = p_eq.run() + self.assertEqual(len(resources_eq), 1) + + # Test not equal operator + p_ne = self.load_policy({ + 'name': 'workspace-connection-status-ne', + 'resource': 'huaweicloud.workspace-desktop', + 'filters': [{ + 'type': 'connection-status', + 'op': 'ne', + 'value': 'REGISTERED' + }]}, + session_factory=factory) + resources_ne = p_ne.run() + self.assertEqual(len(resources_ne), 1) + + # Test in operator + p_in = self.load_policy({ + 'name': 'workspace-connection-status-in', + 'resource': 'huaweicloud.workspace-desktop', + 'filters': [{ + 'type': 'connection-status', + 'op': 'in', + 'value': ['UNREGISTER', 'OFFLINE'] + }]}, + session_factory=factory) + resources_in = p_in.run() + self.assertEqual(len(resources_in), 1) + + # Test not-in operator + p_not_in = self.load_policy({ + 'name': 'workspace-connection-status-not-in', + 'resource': 'huaweicloud.workspace-desktop', + 'filters': [{ + 'type': 'connection-status', + 'op': 'not-in', + 'value': ['REGISTERED', 'ONLINE'] + }]}, + session_factory=factory) + resources_not_in = p_not_in.run() + self.assertEqual(len(resources_not_in), 1) + + # ========================= + # Action Tests + # ========================= + def test_terminate_action(self): + """Test terminate action""" + factory = self.replay_flight_data('workspace_terminate') + p = self.load_policy({ + 'name': 'workspace-terminate-test', + 'resource': 'huaweicloud.workspace-desktop', + 'filters': [{ + 'type': 'connection-status', + 'op': 'eq', + 'value': 'UNREGISTER' + }], + 'actions': ['terminate']}, + session_factory=factory) + resources = p.run() + self.assertEqual(len(resources), 1) + + def test_terminate_batch_action(self): + """Test batch terminate action""" + factory = self.replay_flight_data('workspace_terminate_batch') + p = self.load_policy({ + 'name': 'workspace-terminate-batch-test', + 'resource': 'huaweicloud.workspace-desktop', + 'filters': [{ + 'type': 'connection-status', + 'op': 'eq', + 'value': 'UNREGISTER' + }], + 'actions': ['terminate']}, + session_factory=factory) + resources = p.run() + self.assertEqual(len(resources), 2) # Testing batch termination of 2 desktops