anyscale 0.24.86__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- anyscale/__init__.py +181 -0
- anyscale/_private/anyscale_client/README.md +16 -0
- anyscale/_private/anyscale_client/__init__.py +8 -0
- anyscale/_private/anyscale_client/anyscale_client.py +1847 -0
- anyscale/_private/anyscale_client/common.py +593 -0
- anyscale/_private/anyscale_client/fake_anyscale_client.py +1080 -0
- anyscale/_private/docgen/README.md +15 -0
- anyscale/_private/docgen/__main__.py +700 -0
- anyscale/_private/docgen/api.md +1106 -0
- anyscale/_private/docgen/generator.py +559 -0
- anyscale/_private/docgen/generator_legacy.py +104 -0
- anyscale/_private/docgen/models.md +2261 -0
- anyscale/_private/models/__init__.py +2 -0
- anyscale/_private/models/image_uri.py +116 -0
- anyscale/_private/models/model_base.py +251 -0
- anyscale/_private/sdk/__init__.py +102 -0
- anyscale/_private/sdk/base_sdk.py +35 -0
- anyscale/_private/sdk/timer.py +46 -0
- anyscale/_private/utils/__init__.py +0 -0
- anyscale/_private/utils/progress_util.py +85 -0
- anyscale/_private/workload/__init__.py +2 -0
- anyscale/_private/workload/workload_config.py +195 -0
- anyscale/_private/workload/workload_sdk.py +324 -0
- anyscale/aggregated_instance_usage/__init__.py +36 -0
- anyscale/aggregated_instance_usage/_private/aggregated_instance_usage_sdk.py +30 -0
- anyscale/aggregated_instance_usage/commands.py +42 -0
- anyscale/aggregated_instance_usage/models.py +85 -0
- anyscale/anyscale-cloud-setup-gcp-oa.yaml +88 -0
- anyscale/anyscale-cloud-setup-gcp.yaml +113 -0
- anyscale/anyscale-cloud-setup-oa.yaml +121 -0
- anyscale/anyscale-cloud-setup.yaml +327 -0
- anyscale/anyscale_pydantic/HISTORY.md +1254 -0
- anyscale/anyscale_pydantic/LICENSE +21 -0
- anyscale/anyscale_pydantic/PKG-INFO +1351 -0
- anyscale/anyscale_pydantic/README.md +7 -0
- anyscale/anyscale_pydantic/__init__.py +131 -0
- anyscale/anyscale_pydantic/_hypothesis_plugin.py +391 -0
- anyscale/anyscale_pydantic/annotated_types.py +72 -0
- anyscale/anyscale_pydantic/class_validators.py +361 -0
- anyscale/anyscale_pydantic/color.py +494 -0
- anyscale/anyscale_pydantic/config.py +191 -0
- anyscale/anyscale_pydantic/dataclasses.py +478 -0
- anyscale/anyscale_pydantic/datetime_parse.py +248 -0
- anyscale/anyscale_pydantic/decorator.py +264 -0
- anyscale/anyscale_pydantic/env_settings.py +350 -0
- anyscale/anyscale_pydantic/error_wrappers.py +162 -0
- anyscale/anyscale_pydantic/errors.py +646 -0
- anyscale/anyscale_pydantic/fields.py +1256 -0
- anyscale/anyscale_pydantic/generics.py +400 -0
- anyscale/anyscale_pydantic/json.py +112 -0
- anyscale/anyscale_pydantic/main.py +1109 -0
- anyscale/anyscale_pydantic/mypy.py +943 -0
- anyscale/anyscale_pydantic/networks.py +739 -0
- anyscale/anyscale_pydantic/parse.py +66 -0
- anyscale/anyscale_pydantic/py.typed +0 -0
- anyscale/anyscale_pydantic/schema.py +1164 -0
- anyscale/anyscale_pydantic/tools.py +92 -0
- anyscale/anyscale_pydantic/types.py +1206 -0
- anyscale/anyscale_pydantic/typing.py +603 -0
- anyscale/anyscale_pydantic/utils.py +803 -0
- anyscale/anyscale_pydantic/validators.py +765 -0
- anyscale/anyscale_pydantic/version.py +38 -0
- anyscale/anyscale_schema.json +9 -0
- anyscale/api.py +215 -0
- anyscale/api_utils/README.md +2 -0
- anyscale/api_utils/__init__.py +0 -0
- anyscale/api_utils/common_utils.py +81 -0
- anyscale/api_utils/exceptions/__init__.py +0 -0
- anyscale/api_utils/exceptions/job_errors.py +2 -0
- anyscale/api_utils/job_logs_util.py +116 -0
- anyscale/api_utils/job_util.py +22 -0
- anyscale/api_utils/logs_util.py +61 -0
- anyscale/authenticate.py +298 -0
- anyscale/aws_iam_policies.py +465 -0
- anyscale/background/__init__.py +0 -0
- anyscale/background/job_runner.py +64 -0
- anyscale/cli_logger.py +378 -0
- anyscale/client/.gitignore +66 -0
- anyscale/client/.openapi-generator/VERSION +1 -0
- anyscale/client/.openapi-generator-ignore +23 -0
- anyscale/client/README.md +1070 -0
- anyscale/client/git_push.sh +58 -0
- anyscale/client/openapi_client/__init__.py +667 -0
- anyscale/client/openapi_client/api/__init__.py +6 -0
- anyscale/client/openapi_client/api/default_api.py +40922 -0
- anyscale/client/openapi_client/api_client.py +647 -0
- anyscale/client/openapi_client/configuration.py +373 -0
- anyscale/client/openapi_client/exceptions.py +120 -0
- anyscale/client/openapi_client/models/__init__.py +652 -0
- anyscale/client/openapi_client/models/access_config.py +122 -0
- anyscale/client/openapi_client/models/aggregated_instance_usage_with_cost_model.py +733 -0
- anyscale/client/openapi_client/models/aggregatedinstanceusagewithcostmodel_list_response.py +147 -0
- anyscale/client/openapi_client/models/aica_endpoint.py +527 -0
- anyscale/client/openapi_client/models/aica_endpoint_event.py +433 -0
- anyscale/client/openapi_client/models/aica_endpoint_event_level.py +103 -0
- anyscale/client/openapi_client/models/aica_endpoint_event_type.py +120 -0
- anyscale/client/openapi_client/models/aica_endpoint_scope.py +102 -0
- anyscale/client/openapi_client/models/aica_model.py +398 -0
- anyscale/client/openapi_client/models/aica_model_accelerator_map.py +123 -0
- anyscale/client/openapi_client/models/aica_model_configuration.py +209 -0
- anyscale/client/openapi_client/models/aica_observability_urls.py +178 -0
- anyscale/client/openapi_client/models/aicaendpoint_list_response.py +147 -0
- anyscale/client/openapi_client/models/aicaendpoint_response.py +121 -0
- anyscale/client/openapi_client/models/aicaendpointevent_list_response.py +147 -0
- anyscale/client/openapi_client/models/aicamodel_list_response.py +147 -0
- anyscale/client/openapi_client/models/aicamodel_response.py +121 -0
- anyscale/client/openapi_client/models/aioa_cloud_waitlist_record.py +254 -0
- anyscale/client/openapi_client/models/aioacloudwaitlistrecord_response.py +121 -0
- anyscale/client/openapi_client/models/alert_type.py +103 -0
- anyscale/client/openapi_client/models/anyscale_aws_account.py +121 -0
- anyscale/client/openapi_client/models/anyscale_service_account.py +256 -0
- anyscale/client/openapi_client/models/anyscale_version_response.py +121 -0
- anyscale/client/openapi_client/models/anyscaleawsaccount_response.py +121 -0
- anyscale/client/openapi_client/models/anyscaled_credential_response.py +121 -0
- anyscale/client/openapi_client/models/anyscaledcredentialresponse_response.py +121 -0
- anyscale/client/openapi_client/models/anyscaleserviceaccount_response.py +121 -0
- anyscale/client/openapi_client/models/anyscaleversionresponse_response.py +121 -0
- anyscale/client/openapi_client/models/api_key_parameters.py +147 -0
- anyscale/client/openapi_client/models/app_config.py +436 -0
- anyscale/client/openapi_client/models/app_config_config_schema.py +235 -0
- anyscale/client/openapi_client/models/appconfig_list_response.py +147 -0
- anyscale/client/openapi_client/models/appconfig_response.py +121 -0
- anyscale/client/openapi_client/models/application_type.py +99 -0
- anyscale/client/openapi_client/models/applied_snapshot.py +175 -0
- anyscale/client/openapi_client/models/apply_production_service_v2_model.py +490 -0
- anyscale/client/openapi_client/models/archive_status.py +101 -0
- anyscale/client/openapi_client/models/archived_logs_info.py +164 -0
- anyscale/client/openapi_client/models/archivedlogsinfo_response.py +121 -0
- anyscale/client/openapi_client/models/attach_machine_pool_to_cloud_request.py +152 -0
- anyscale/client/openapi_client/models/attachmachinepooltocloudresponse_response.py +121 -0
- anyscale/client/openapi_client/models/aviary_model_config_v2.py +358 -0
- anyscale/client/openapi_client/models/aws_credentials.py +181 -0
- anyscale/client/openapi_client/models/aws_memory_db_cluster_config.py +148 -0
- anyscale/client/openapi_client/models/aws_region_and_zones.py +123 -0
- anyscale/client/openapi_client/models/aws_region_info.py +152 -0
- anyscale/client/openapi_client/models/awsregionandzones_response.py +121 -0
- anyscale/client/openapi_client/models/bank_account_information.py +239 -0
- anyscale/client/openapi_client/models/base_job_status.py +105 -0
- anyscale/client/openapi_client/models/baseimagesenum.py +2130 -0
- anyscale/client/openapi_client/models/batch_response_batched_result_organization_invitation_base.py +121 -0
- anyscale/client/openapi_client/models/batched_result_organization_invitation_base.py +173 -0
- anyscale/client/openapi_client/models/billing_information.py +181 -0
- anyscale/client/openapi_client/models/billing_version_code.py +100 -0
- anyscale/client/openapi_client/models/body_aws_marketplace_registration_api_v2_organization_billing_aws_marketplace_registration_post.py +121 -0
- anyscale/client/openapi_client/models/buffer_registration.py +285 -0
- anyscale/client/openapi_client/models/build.py +607 -0
- anyscale/client/openapi_client/models/build_log_response.py +123 -0
- anyscale/client/openapi_client/models/build_registration.py +285 -0
- anyscale/client/openapi_client/models/build_response.py +121 -0
- anyscale/client/openapi_client/models/build_status.py +104 -0
- anyscale/client/openapi_client/models/buildlogresponse_response.py +121 -0
- anyscale/client/openapi_client/models/card.py +181 -0
- anyscale/client/openapi_client/models/card_id.py +108 -0
- anyscale/client/openapi_client/models/card_list_response.py +147 -0
- anyscale/client/openapi_client/models/change_password_params.py +148 -0
- anyscale/client/openapi_client/models/cleanup_leaked_grafana_dashboard_response.py +208 -0
- anyscale/client/openapi_client/models/cleanupleakedgrafanadashboardresponse_response.py +121 -0
- anyscale/client/openapi_client/models/clone_experimental_workspace.py +151 -0
- anyscale/client/openapi_client/models/cloud.py +802 -0
- anyscale/client/openapi_client/models/cloud_analytics_event.py +351 -0
- anyscale/client/openapi_client/models/cloud_analytics_event_cloud_provider_error.py +152 -0
- anyscale/client/openapi_client/models/cloud_analytics_event_cloud_resource.py +117 -0
- anyscale/client/openapi_client/models/cloud_analytics_event_command_name.py +103 -0
- anyscale/client/openapi_client/models/cloud_analytics_event_error.py +150 -0
- anyscale/client/openapi_client/models/cloud_analytics_event_name.py +109 -0
- anyscale/client/openapi_client/models/cloud_collaborator.py +175 -0
- anyscale/client/openapi_client/models/cloud_collaborator_value.py +177 -0
- anyscale/client/openapi_client/models/cloud_collaborators_query.py +122 -0
- anyscale/client/openapi_client/models/cloud_config.py +206 -0
- anyscale/client/openapi_client/models/cloud_data_bucket_access_mode.py +100 -0
- anyscale/client/openapi_client/models/cloud_data_bucket_file_type.py +102 -0
- anyscale/client/openapi_client/models/cloud_data_bucket_presigned_upload_info.py +268 -0
- anyscale/client/openapi_client/models/cloud_data_bucket_presigned_upload_request.py +152 -0
- anyscale/client/openapi_client/models/cloud_data_bucket_presigned_upload_scheme.py +100 -0
- anyscale/client/openapi_client/models/cloud_data_bucket_presigned_url_request.py +209 -0
- anyscale/client/openapi_client/models/cloud_data_bucket_presigned_url_response.py +296 -0
- anyscale/client/openapi_client/models/cloud_data_bucket_presigned_url_scheme.py +100 -0
- anyscale/client/openapi_client/models/cloud_data_bucket_request_scope.py +100 -0
- anyscale/client/openapi_client/models/cloud_hosting_type.py +100 -0
- anyscale/client/openapi_client/models/cloud_list_response.py +147 -0
- anyscale/client/openapi_client/models/cloud_name_options.py +121 -0
- anyscale/client/openapi_client/models/cloud_overview_dashboard.py +175 -0
- anyscale/client/openapi_client/models/cloud_project_collaborator.py +175 -0
- anyscale/client/openapi_client/models/cloud_project_collaborator_value.py +121 -0
- anyscale/client/openapi_client/models/cloud_provider.py +102 -0
- anyscale/client/openapi_client/models/cloud_providers.py +103 -0
- anyscale/client/openapi_client/models/cloud_region_and_zones.py +123 -0
- anyscale/client/openapi_client/models/cloud_region_info.py +152 -0
- anyscale/client/openapi_client/models/cloud_resource.py +740 -0
- anyscale/client/openapi_client/models/cloud_resource_gcp.py +691 -0
- anyscale/client/openapi_client/models/cloud_response.py +121 -0
- anyscale/client/openapi_client/models/cloud_state.py +104 -0
- anyscale/client/openapi_client/models/cloud_status.py +100 -0
- anyscale/client/openapi_client/models/cloud_type.py +100 -0
- anyscale/client/openapi_client/models/cloud_types.py +100 -0
- anyscale/client/openapi_client/models/cloud_version.py +100 -0
- anyscale/client/openapi_client/models/cloud_waitlist_status.py +102 -0
- anyscale/client/openapi_client/models/cloud_with_cloud_resource.py +830 -0
- anyscale/client/openapi_client/models/cloud_with_cloud_resource_gcp.py +830 -0
- anyscale/client/openapi_client/models/cloudcollaborator_list_response.py +147 -0
- anyscale/client/openapi_client/models/clouddatabucketpresigneduploadinfo_response.py +121 -0
- anyscale/client/openapi_client/models/clouddatabucketpresignedurlresponse_response.py +121 -0
- anyscale/client/openapi_client/models/cloudoverviewdashboard_response.py +121 -0
- anyscale/client/openapi_client/models/cloudregionandzones_response.py +121 -0
- anyscale/client/openapi_client/models/cloudresource_response.py +121 -0
- anyscale/client/openapi_client/models/cloudresourcegcp_response.py +121 -0
- anyscale/client/openapi_client/models/cloudwithcloudresource_response.py +121 -0
- anyscale/client/openapi_client/models/cloudwithcloudresourcegcp_response.py +121 -0
- anyscale/client/openapi_client/models/cluster_auth_response.py +148 -0
- anyscale/client/openapi_client/models/cluster_config.py +178 -0
- anyscale/client/openapi_client/models/cluster_config_with_session_idle_timeout.py +204 -0
- anyscale/client/openapi_client/models/cluster_environments_query.py +290 -0
- anyscale/client/openapi_client/models/cluster_event.py +174 -0
- anyscale/client/openapi_client/models/cluster_events_output.py +175 -0
- anyscale/client/openapi_client/models/cluster_features.py +152 -0
- anyscale/client/openapi_client/models/cluster_management_stack_versions.py +100 -0
- anyscale/client/openapi_client/models/cluster_startup.py +208 -0
- anyscale/client/openapi_client/models/cluster_status.py +104 -0
- anyscale/client/openapi_client/models/cluster_status_details.py +100 -0
- anyscale/client/openapi_client/models/clusterauthresponse_response.py +121 -0
- anyscale/client/openapi_client/models/clusterconfig_response.py +121 -0
- anyscale/client/openapi_client/models/clusterconfigwithsessionidletimeout_response.py +121 -0
- anyscale/client/openapi_client/models/clustereventsoutput_response.py +121 -0
- anyscale/client/openapi_client/models/clusterfeatures_response.py +121 -0
- anyscale/client/openapi_client/models/company_size.py +103 -0
- anyscale/client/openapi_client/models/compute_node_type.py +292 -0
- anyscale/client/openapi_client/models/compute_stack.py +100 -0
- anyscale/client/openapi_client/models/compute_template.py +415 -0
- anyscale/client/openapi_client/models/compute_template_config.py +461 -0
- anyscale/client/openapi_client/models/compute_template_query.py +316 -0
- anyscale/client/openapi_client/models/computetemplate_response.py +121 -0
- anyscale/client/openapi_client/models/computetemplateconfig_response.py +121 -0
- anyscale/client/openapi_client/models/create_aica_endpoint.py +210 -0
- anyscale/client/openapi_client/models/create_aioa_cloud_waitlist.py +173 -0
- anyscale/client/openapi_client/models/create_analytics_event.py +122 -0
- anyscale/client/openapi_client/models/create_app_config.py +235 -0
- anyscale/client/openapi_client/models/create_app_config_configuration_schema.py +235 -0
- anyscale/client/openapi_client/models/create_billing_version.py +181 -0
- anyscale/client/openapi_client/models/create_bug_report_response.py +152 -0
- anyscale/client/openapi_client/models/create_build.py +263 -0
- anyscale/client/openapi_client/models/create_byod_app_config.py +180 -0
- anyscale/client/openapi_client/models/create_byod_app_config_configuration_schema.py +206 -0
- anyscale/client/openapi_client/models/create_byod_build.py +152 -0
- anyscale/client/openapi_client/models/create_cloud_collaborator.py +148 -0
- anyscale/client/openapi_client/models/create_cloud_resource.py +682 -0
- anyscale/client/openapi_client/models/create_cloud_resource_gcp.py +633 -0
- anyscale/client/openapi_client/models/create_cloud_with_cloud_resource.py +546 -0
- anyscale/client/openapi_client/models/create_cluster_compute_config.py +463 -0
- anyscale/client/openapi_client/models/create_compute_template.py +229 -0
- anyscale/client/openapi_client/models/create_compute_template_config.py +464 -0
- anyscale/client/openapi_client/models/create_dataset.py +200 -0
- anyscale/client/openapi_client/models/create_experimental_workspace.py +435 -0
- anyscale/client/openapi_client/models/create_experimental_workspace_from_job.py +123 -0
- anyscale/client/openapi_client/models/create_fine_tuning_hyperparameters.py +156 -0
- anyscale/client/openapi_client/models/create_fine_tuning_job_product_request.py +353 -0
- anyscale/client/openapi_client/models/create_instance_usage_budget.py +253 -0
- anyscale/client/openapi_client/models/create_internal_production_job.py +262 -0
- anyscale/client/openapi_client/models/create_job_queue_config.py +206 -0
- anyscale/client/openapi_client/models/create_job_queue_requests.py +323 -0
- anyscale/client/openapi_client/models/create_machine_pool_request.py +151 -0
- anyscale/client/openapi_client/models/create_machine_pool_response.py +123 -0
- anyscale/client/openapi_client/models/create_machine_request.py +151 -0
- anyscale/client/openapi_client/models/create_machine_response.py +123 -0
- anyscale/client/openapi_client/models/create_metronome_webhook_notification.py +175 -0
- anyscale/client/openapi_client/models/create_notification_channel_record.py +146 -0
- anyscale/client/openapi_client/models/create_organization_configuration.py +199 -0
- anyscale/client/openapi_client/models/create_organization_invitation.py +121 -0
- anyscale/client/openapi_client/models/create_otp_return_api_model.py +148 -0
- anyscale/client/openapi_client/models/create_production_job_config.py +347 -0
- anyscale/client/openapi_client/models/create_resource_quota.py +293 -0
- anyscale/client/openapi_client/models/create_schedule.py +263 -0
- anyscale/client/openapi_client/models/create_session_from_snapshot_options.py +565 -0
- anyscale/client/openapi_client/models/create_session_in_db.py +434 -0
- anyscale/client/openapi_client/models/create_session_response.py +174 -0
- anyscale/client/openapi_client/models/create_user.py +439 -0
- anyscale/client/openapi_client/models/create_user_project_collaborator.py +148 -0
- anyscale/client/openapi_client/models/create_user_project_collaborator_value.py +121 -0
- anyscale/client/openapi_client/models/create_workspace_from_template.py +263 -0
- anyscale/client/openapi_client/models/createbugreportresponse_response.py +121 -0
- anyscale/client/openapi_client/models/createcomputetemplateconfig_response.py +121 -0
- anyscale/client/openapi_client/models/createmachinepoolresponse_response.py +121 -0
- anyscale/client/openapi_client/models/createmachineresponse_response.py +121 -0
- anyscale/client/openapi_client/models/createotpreturnapimodel_response.py +121 -0
- anyscale/client/openapi_client/models/createsessionresponse_response.py +121 -0
- anyscale/client/openapi_client/models/credit_card_information.py +268 -0
- anyscale/client/openapi_client/models/customer_alert_status.py +101 -0
- anyscale/client/openapi_client/models/customer_billing_type.py +101 -0
- anyscale/client/openapi_client/models/dataplane_services.py +102 -0
- anyscale/client/openapi_client/models/dataset.py +416 -0
- anyscale/client/openapi_client/models/dataset_list_response.py +150 -0
- anyscale/client/openapi_client/models/dataset_response.py +121 -0
- anyscale/client/openapi_client/models/dataset_upload.py +148 -0
- anyscale/client/openapi_client/models/datasetupload_response.py +121 -0
- anyscale/client/openapi_client/models/decorated_application_template.py +493 -0
- anyscale/client/openapi_client/models/decorated_build.py +664 -0
- anyscale/client/openapi_client/models/decorated_compute_template.py +446 -0
- anyscale/client/openapi_client/models/decorated_compute_template_config.py +490 -0
- anyscale/client/openapi_client/models/decorated_interactive_session.py +793 -0
- anyscale/client/openapi_client/models/decorated_job.py +793 -0
- anyscale/client/openapi_client/models/decorated_job_queue.py +639 -0
- anyscale/client/openapi_client/models/decorated_job_submission.py +575 -0
- anyscale/client/openapi_client/models/decorated_list_service_api_model.py +670 -0
- anyscale/client/openapi_client/models/decorated_production_job.py +805 -0
- anyscale/client/openapi_client/models/decorated_production_job_state_transition.py +319 -0
- anyscale/client/openapi_client/models/decorated_production_service_v2_api_model.py +641 -0
- anyscale/client/openapi_client/models/decorated_production_service_v2_version_api_model.py +437 -0
- anyscale/client/openapi_client/models/decorated_runtime_env.py +488 -0
- anyscale/client/openapi_client/models/decorated_schedule.py +552 -0
- anyscale/client/openapi_client/models/decorated_serve_deployment.py +711 -0
- anyscale/client/openapi_client/models/decorated_service_event_api_model.py +513 -0
- anyscale/client/openapi_client/models/decorated_session.py +1789 -0
- anyscale/client/openapi_client/models/decorated_support_request.py +283 -0
- anyscale/client/openapi_client/models/decorated_unified_job.py +466 -0
- anyscale/client/openapi_client/models/decoratedapplicationtemplate_list_response.py +147 -0
- anyscale/client/openapi_client/models/decoratedapplicationtemplate_response.py +121 -0
- anyscale/client/openapi_client/models/decoratedbuild_list_response.py +147 -0
- anyscale/client/openapi_client/models/decoratedbuild_response.py +121 -0
- anyscale/client/openapi_client/models/decoratedcomputetemplate_list_response.py +147 -0
- anyscale/client/openapi_client/models/decoratedcomputetemplate_response.py +121 -0
- anyscale/client/openapi_client/models/decoratedinteractivesession_list_response.py +147 -0
- anyscale/client/openapi_client/models/decoratedinteractivesession_response.py +121 -0
- anyscale/client/openapi_client/models/decoratedjob_list_response.py +147 -0
- anyscale/client/openapi_client/models/decoratedjob_response.py +121 -0
- anyscale/client/openapi_client/models/decoratedjobqueue_list_response.py +147 -0
- anyscale/client/openapi_client/models/decoratedjobqueue_response.py +121 -0
- anyscale/client/openapi_client/models/decoratedjobsubmission_list_response.py +147 -0
- anyscale/client/openapi_client/models/decoratedjobsubmission_response.py +121 -0
- anyscale/client/openapi_client/models/decoratedlistserviceapimodel_list_response.py +147 -0
- anyscale/client/openapi_client/models/decoratedproductionjob_list_response.py +147 -0
- anyscale/client/openapi_client/models/decoratedproductionjob_response.py +121 -0
- anyscale/client/openapi_client/models/decoratedproductionjobstatetransition_list_response.py +147 -0
- anyscale/client/openapi_client/models/decoratedproductionservicev2_apimodel_response.py +121 -0
- anyscale/client/openapi_client/models/decoratedproductionservicev2_versionapimodel_list_response.py +147 -0
- anyscale/client/openapi_client/models/decoratedruntimeenv_list_response.py +147 -0
- anyscale/client/openapi_client/models/decoratedruntimeenv_response.py +121 -0
- anyscale/client/openapi_client/models/decoratedschedule_list_response.py +147 -0
- anyscale/client/openapi_client/models/decoratedschedule_response.py +121 -0
- anyscale/client/openapi_client/models/decoratedservedeployment_list_response.py +147 -0
- anyscale/client/openapi_client/models/decoratedservedeployment_response.py +121 -0
- anyscale/client/openapi_client/models/decoratedserviceeventapimodel_list_response.py +147 -0
- anyscale/client/openapi_client/models/decoratedsession_list_response.py +147 -0
- anyscale/client/openapi_client/models/decoratedsession_response.py +121 -0
- anyscale/client/openapi_client/models/decoratedsupportrequest_list_response.py +147 -0
- anyscale/client/openapi_client/models/decoratedsupportrequest_response.py +121 -0
- anyscale/client/openapi_client/models/decoratedunifiedjob_list_response.py +147 -0
- anyscale/client/openapi_client/models/decoratedunifiedjob_response.py +121 -0
- anyscale/client/openapi_client/models/delete_machine_pool_request.py +123 -0
- anyscale/client/openapi_client/models/delete_machine_request.py +206 -0
- anyscale/client/openapi_client/models/deleted_platform_fine_tuned_model.py +148 -0
- anyscale/client/openapi_client/models/deletedplatformfinetunedmodel_response.py +121 -0
- anyscale/client/openapi_client/models/deletemachinepoolresponse_response.py +121 -0
- anyscale/client/openapi_client/models/detach_machine_pool_from_cloud_request.py +152 -0
- anyscale/client/openapi_client/models/detachmachinepoolfromcloudresponse_response.py +121 -0
- anyscale/client/openapi_client/models/dismissal_type.py +100 -0
- anyscale/client/openapi_client/models/editable_cloud_resource.py +206 -0
- anyscale/client/openapi_client/models/editable_cloud_resource_gcp.py +178 -0
- anyscale/client/openapi_client/models/error.py +174 -0
- anyscale/client/openapi_client/models/event_level.py +104 -0
- anyscale/client/openapi_client/models/execute_command_response.py +175 -0
- anyscale/client/openapi_client/models/execute_interactive_command_options.py +147 -0
- anyscale/client/openapi_client/models/execute_shell_command_options.py +121 -0
- anyscale/client/openapi_client/models/executecommandresponse_response.py +121 -0
- anyscale/client/openapi_client/models/experimental_workspace.py +748 -0
- anyscale/client/openapi_client/models/experimental_workspaces_sort_field.py +100 -0
- anyscale/client/openapi_client/models/experimentalworkspace_list_response.py +147 -0
- anyscale/client/openapi_client/models/experimentalworkspace_response.py +121 -0
- anyscale/client/openapi_client/models/external_service_status.py +147 -0
- anyscale/client/openapi_client/models/external_service_status_response.py +250 -0
- anyscale/client/openapi_client/models/external_terminal_command.py +280 -0
- anyscale/client/openapi_client/models/externalservicestatusresponse_response.py +121 -0
- anyscale/client/openapi_client/models/feature_compatibility.py +152 -0
- anyscale/client/openapi_client/models/feature_flag_response.py +121 -0
- anyscale/client/openapi_client/models/featureflagresponse_response.py +121 -0
- anyscale/client/openapi_client/models/fine_tune_type.py +100 -0
- anyscale/client/openapi_client/models/fine_tuned_model.py +412 -0
- anyscale/client/openapi_client/models/fine_tuning_job_status.py +103 -0
- anyscale/client/openapi_client/models/finetunedmodel_list_response.py +147 -0
- anyscale/client/openapi_client/models/finetunedmodel_response.py +121 -0
- anyscale/client/openapi_client/models/finish_ft_job_request.py +204 -0
- anyscale/client/openapi_client/models/finish_ft_job_request_v2.py +183 -0
- anyscale/client/openapi_client/models/gcp_file_store_config.py +175 -0
- anyscale/client/openapi_client/models/gcp_memorystore_instance_config.py +148 -0
- anyscale/client/openapi_client/models/grafana_dashboard.py +201 -0
- anyscale/client/openapi_client/models/grpc_protocol_config.py +178 -0
- anyscale/client/openapi_client/models/ha_job_error_types.py +103 -0
- anyscale/client/openapi_client/models/ha_job_event_level.py +101 -0
- anyscale/client/openapi_client/models/ha_job_event_origin.py +100 -0
- anyscale/client/openapi_client/models/ha_job_goal_states.py +102 -0
- anyscale/client/openapi_client/models/ha_job_states.py +109 -0
- anyscale/client/openapi_client/models/ha_job_type.py +100 -0
- anyscale/client/openapi_client/models/ha_jobs_sort_field.py +105 -0
- anyscale/client/openapi_client/models/head_ip.py +121 -0
- anyscale/client/openapi_client/models/headip_response.py +121 -0
- anyscale/client/openapi_client/models/http_protocol_config.py +150 -0
- anyscale/client/openapi_client/models/http_validation_error.py +120 -0
- anyscale/client/openapi_client/models/idle_termination_status.py +104 -0
- anyscale/client/openapi_client/models/import_aica_model.py +241 -0
- anyscale/client/openapi_client/models/instance_usage_budget.py +572 -0
- anyscale/client/openapi_client/models/instance_usage_budget_evaluation_period.py +100 -0
- anyscale/client/openapi_client/models/instanceusagebudget_list_response.py +147 -0
- anyscale/client/openapi_client/models/instanceusagebudget_response.py +121 -0
- anyscale/client/openapi_client/models/integration_details.py +120 -0
- anyscale/client/openapi_client/models/interactive_session_logs.py +152 -0
- anyscale/client/openapi_client/models/interactivesessionlogs_response.py +121 -0
- anyscale/client/openapi_client/models/internal_production_job.py +663 -0
- anyscale/client/openapi_client/models/internalproductionjob_response.py +121 -0
- anyscale/client/openapi_client/models/invoice.py +413 -0
- anyscale/client/openapi_client/models/invoice_list_response.py +147 -0
- anyscale/client/openapi_client/models/invoice_status.py +102 -0
- anyscale/client/openapi_client/models/invoices_query.py +150 -0
- anyscale/client/openapi_client/models/job_access.py +102 -0
- anyscale/client/openapi_client/models/job_queue.py +467 -0
- anyscale/client/openapi_client/models/job_queue_config.py +122 -0
- anyscale/client/openapi_client/models/job_queue_execution_mode.py +101 -0
- anyscale/client/openapi_client/models/job_queue_spec.py +263 -0
- anyscale/client/openapi_client/models/job_queue_state.py +100 -0
- anyscale/client/openapi_client/models/job_queues_query.py +262 -0
- anyscale/client/openapi_client/models/job_run_type.py +101 -0
- anyscale/client/openapi_client/models/job_state_log_level_types.py +100 -0
- anyscale/client/openapi_client/models/job_status.py +105 -0
- anyscale/client/openapi_client/models/job_submissions_sort_field.py +101 -0
- anyscale/client/openapi_client/models/jobqueue_response.py +121 -0
- anyscale/client/openapi_client/models/jobs_logs.py +152 -0
- anyscale/client/openapi_client/models/jobs_logs_query_info.py +181 -0
- anyscale/client/openapi_client/models/jobs_sort_field.py +104 -0
- anyscale/client/openapi_client/models/jobslogs_response.py +121 -0
- anyscale/client/openapi_client/models/jobslogsqueryinfo_response.py +121 -0
- anyscale/client/openapi_client/models/json_patch_operation.py +200 -0
- anyscale/client/openapi_client/models/kubernetes_manager_registration_request.py +123 -0
- anyscale/client/openapi_client/models/kubernetes_manager_registration_response.py +123 -0
- anyscale/client/openapi_client/models/kubernetesmanagerregistrationresponse_response.py +121 -0
- anyscale/client/openapi_client/models/lb_resource.py +123 -0
- anyscale/client/openapi_client/models/lbresource_response.py +121 -0
- anyscale/client/openapi_client/models/list_machine_pools_response.py +123 -0
- anyscale/client/openapi_client/models/list_machines_response.py +121 -0
- anyscale/client/openapi_client/models/list_resource_quotas_query.py +234 -0
- anyscale/client/openapi_client/models/list_response_metadata.py +146 -0
- anyscale/client/openapi_client/models/listmachinepoolsresponse_response.py +121 -0
- anyscale/client/openapi_client/models/listmachinesresponse_response.py +121 -0
- anyscale/client/openapi_client/models/log_detail.py +187 -0
- anyscale/client/openapi_client/models/log_details.py +151 -0
- anyscale/client/openapi_client/models/log_download_config.py +206 -0
- anyscale/client/openapi_client/models/log_download_request.py +150 -0
- anyscale/client/openapi_client/models/log_download_result.py +207 -0
- anyscale/client/openapi_client/models/log_file_chunk.py +439 -0
- anyscale/client/openapi_client/models/log_filter.py +430 -0
- anyscale/client/openapi_client/models/log_item.py +181 -0
- anyscale/client/openapi_client/models/log_item_batch.py +151 -0
- anyscale/client/openapi_client/models/log_level_types.py +100 -0
- anyscale/client/openapi_client/models/log_stream.py +151 -0
- anyscale/client/openapi_client/models/logdetails_response.py +121 -0
- anyscale/client/openapi_client/models/logdownloadresult_response.py +121 -0
- anyscale/client/openapi_client/models/login_user_params.py +205 -0
- anyscale/client/openapi_client/models/logitembatch_response.py +121 -0
- anyscale/client/openapi_client/models/logs_output.py +202 -0
- anyscale/client/openapi_client/models/logsoutput_response.py +121 -0
- anyscale/client/openapi_client/models/logstream_response.py +121 -0
- anyscale/client/openapi_client/models/long_running_workload.py +256 -0
- anyscale/client/openapi_client/models/longrunningworkload_list_response.py +147 -0
- anyscale/client/openapi_client/models/machine_allocation_state.py +100 -0
- anyscale/client/openapi_client/models/machine_connection_state.py +100 -0
- anyscale/client/openapi_client/models/machine_info.py +466 -0
- anyscale/client/openapi_client/models/machine_pool.py +266 -0
- anyscale/client/openapi_client/models/metronome_customer_info_model.py +148 -0
- anyscale/client/openapi_client/models/metronome_dashboard_type.py +101 -0
- anyscale/client/openapi_client/models/metronomecustomerinfomodel_list_response.py +147 -0
- anyscale/client/openapi_client/models/metronomecustomerinfomodel_response.py +121 -0
- anyscale/client/openapi_client/models/mini_build.py +267 -0
- anyscale/client/openapi_client/models/mini_cloud.py +321 -0
- anyscale/client/openapi_client/models/mini_cluster.py +148 -0
- anyscale/client/openapi_client/models/mini_compute_template.py +228 -0
- anyscale/client/openapi_client/models/mini_compute_template_config.py +121 -0
- anyscale/client/openapi_client/models/mini_job_run.py +599 -0
- anyscale/client/openapi_client/models/mini_namespace.py +148 -0
- anyscale/client/openapi_client/models/mini_organization.py +148 -0
- anyscale/client/openapi_client/models/mini_production_job.py +202 -0
- anyscale/client/openapi_client/models/mini_project.py +205 -0
- anyscale/client/openapi_client/models/mini_runtime_environment.py +147 -0
- anyscale/client/openapi_client/models/mini_schedule.py +180 -0
- anyscale/client/openapi_client/models/mini_user.py +266 -0
- anyscale/client/openapi_client/models/minibuild_list_response.py +147 -0
- anyscale/client/openapi_client/models/minicomputetemplate_list_response.py +147 -0
- anyscale/client/openapi_client/models/miniproject_list_response.py +147 -0
- anyscale/client/openapi_client/models/monitor_logs_extension.py +100 -0
- anyscale/client/openapi_client/models/named_entity.py +148 -0
- anyscale/client/openapi_client/models/nfs_mount_target.py +151 -0
- anyscale/client/openapi_client/models/node_registration_aws.py +152 -0
- anyscale/client/openapi_client/models/node_registration_gcp.py +123 -0
- anyscale/client/openapi_client/models/node_registration_k8_s.py +178 -0
- anyscale/client/openapi_client/models/node_registration_provisioned.py +150 -0
- anyscale/client/openapi_client/models/node_registration_v2.py +279 -0
- anyscale/client/openapi_client/models/node_type.py +100 -0
- anyscale/client/openapi_client/models/notification_channel_email_config.py +121 -0
- anyscale/client/openapi_client/models/notification_channel_webhook_config.py +121 -0
- anyscale/client/openapi_client/models/onboarding_user_cards_query.py +122 -0
- anyscale/client/openapi_client/models/organization.py +490 -0
- anyscale/client/openapi_client/models/organization_availability.py +148 -0
- anyscale/client/openapi_client/models/organization_collaborator.py +259 -0
- anyscale/client/openapi_client/models/organization_configuration.py +280 -0
- anyscale/client/openapi_client/models/organization_configuration_response.py +227 -0
- anyscale/client/openapi_client/models/organization_invitation.py +255 -0
- anyscale/client/openapi_client/models/organization_invitation_base.py +121 -0
- anyscale/client/openapi_client/models/organization_marketing_questions.py +198 -0
- anyscale/client/openapi_client/models/organization_permission_level.py +100 -0
- anyscale/client/openapi_client/models/organization_project_collaborator.py +175 -0
- anyscale/client/openapi_client/models/organization_project_collaborator_value.py +148 -0
- anyscale/client/openapi_client/models/organization_public_identifier.py +121 -0
- anyscale/client/openapi_client/models/organization_response.py +121 -0
- anyscale/client/openapi_client/models/organization_summary.py +229 -0
- anyscale/client/openapi_client/models/organization_usage_alert.py +210 -0
- anyscale/client/openapi_client/models/organization_usage_alert_severity.py +100 -0
- anyscale/client/openapi_client/models/organizationavailability_response.py +121 -0
- anyscale/client/openapi_client/models/organizationcollaborator_list_response.py +147 -0
- anyscale/client/openapi_client/models/organizationconfiguration_list_response.py +147 -0
- anyscale/client/openapi_client/models/organizationconfigurationresponse_response.py +121 -0
- anyscale/client/openapi_client/models/organizationinvitation_list_response.py +147 -0
- anyscale/client/openapi_client/models/organizationinvitation_response.py +121 -0
- anyscale/client/openapi_client/models/organizationinvitationbase_response.py +121 -0
- anyscale/client/openapi_client/models/organizationprojectcollaborator_list_response.py +147 -0
- anyscale/client/openapi_client/models/organizationpublicidentifier_response.py +121 -0
- anyscale/client/openapi_client/models/organizationusagealert_list_response.py +147 -0
- anyscale/client/openapi_client/models/page_query.py +153 -0
- anyscale/client/openapi_client/models/pause_schedule.py +123 -0
- anyscale/client/openapi_client/models/permission_level.py +101 -0
- anyscale/client/openapi_client/models/platform_fine_tuning_job.py +577 -0
- anyscale/client/openapi_client/models/platformfinetuningjob_list_response.py +147 -0
- anyscale/client/openapi_client/models/platformfinetuningjob_response.py +121 -0
- anyscale/client/openapi_client/models/product_autoscaler_flag.py +122 -0
- anyscale/client/openapi_client/models/product_type.py +100 -0
- anyscale/client/openapi_client/models/productautoscalerflag_response.py +121 -0
- anyscale/client/openapi_client/models/production_job.py +437 -0
- anyscale/client/openapi_client/models/production_job_config.py +348 -0
- anyscale/client/openapi_client/models/production_job_event.py +378 -0
- anyscale/client/openapi_client/models/production_job_event_scope_filter.py +101 -0
- anyscale/client/openapi_client/models/production_job_state_transition.py +293 -0
- anyscale/client/openapi_client/models/productionjob_response.py +121 -0
- anyscale/client/openapi_client/models/productionjobevent_list_response.py +147 -0
- anyscale/client/openapi_client/models/project.py +554 -0
- anyscale/client/openapi_client/models/project_base.py +121 -0
- anyscale/client/openapi_client/models/project_collaborator.py +175 -0
- anyscale/client/openapi_client/models/project_collaborator_value.py +175 -0
- anyscale/client/openapi_client/models/project_collaborators_put_message.py +121 -0
- anyscale/client/openapi_client/models/project_create_message.py +148 -0
- anyscale/client/openapi_client/models/project_default_session_name.py +121 -0
- anyscale/client/openapi_client/models/project_delete_message.py +121 -0
- anyscale/client/openapi_client/models/project_list_response.py +147 -0
- anyscale/client/openapi_client/models/project_patch_message.py +121 -0
- anyscale/client/openapi_client/models/project_response.py +121 -0
- anyscale/client/openapi_client/models/projectbase_response.py +121 -0
- anyscale/client/openapi_client/models/projectcollaborator_list_response.py +147 -0
- anyscale/client/openapi_client/models/projectdefaultsessionname_response.py +121 -0
- anyscale/client/openapi_client/models/projects_sort_field.py +101 -0
- anyscale/client/openapi_client/models/projects_violating_tree_hierarchy_response.py +121 -0
- anyscale/client/openapi_client/models/projectsviolatingtreehierarchyresponse_response.py +121 -0
- anyscale/client/openapi_client/models/protocols.py +150 -0
- anyscale/client/openapi_client/models/provider_metadata.py +205 -0
- anyscale/client/openapi_client/models/providermetadata_response.py +121 -0
- anyscale/client/openapi_client/models/python_modules.py +150 -0
- anyscale/client/openapi_client/models/quota.py +198 -0
- anyscale/client/openapi_client/models/ray_gcs_external_storage_config.py +178 -0
- anyscale/client/openapi_client/models/ray_runtime_env_config.py +262 -0
- anyscale/client/openapi_client/models/read_billing_version.py +210 -0
- anyscale/client/openapi_client/models/readbillingversion_list_response.py +147 -0
- anyscale/client/openapi_client/models/replica_details.py +152 -0
- anyscale/client/openapi_client/models/replica_state.py +104 -0
- anyscale/client/openapi_client/models/request_email_magic_link_response.py +147 -0
- anyscale/client/openapi_client/models/request_otp_return_api_model.py +148 -0
- anyscale/client/openapi_client/models/request_password_reset_params.py +147 -0
- anyscale/client/openapi_client/models/requestemailmagiclinkresponse_response.py +121 -0
- anyscale/client/openapi_client/models/requestotpreturnapimodel_response.py +121 -0
- anyscale/client/openapi_client/models/reset_password_params.py +148 -0
- anyscale/client/openapi_client/models/resource_quota.py +465 -0
- anyscale/client/openapi_client/models/resource_quota_status.py +123 -0
- anyscale/client/openapi_client/models/resourcequota_list_response.py +147 -0
- anyscale/client/openapi_client/models/resourcequota_response.py +121 -0
- anyscale/client/openapi_client/models/resources.py +234 -0
- anyscale/client/openapi_client/models/resubmit_ft_job_request.py +121 -0
- anyscale/client/openapi_client/models/rollback_service_model.py +122 -0
- anyscale/client/openapi_client/models/rollout_strategy.py +100 -0
- anyscale/client/openapi_client/models/s3_download_location.py +148 -0
- anyscale/client/openapi_client/models/schedule_config.py +151 -0
- anyscale/client/openapi_client/models/serve_deployment_grafana_dashboard_status.py +101 -0
- anyscale/client/openapi_client/models/serve_deployment_logs.py +152 -0
- anyscale/client/openapi_client/models/serve_deployment_state.py +104 -0
- anyscale/client/openapi_client/models/servedeploymentlogs_response.py +121 -0
- anyscale/client/openapi_client/models/server_session_token.py +121 -0
- anyscale/client/openapi_client/models/serversessiontoken_response.py +121 -0
- anyscale/client/openapi_client/models/service_config.py +178 -0
- anyscale/client/openapi_client/models/service_event_current_state.py +108 -0
- anyscale/client/openapi_client/models/service_event_level.py +102 -0
- anyscale/client/openapi_client/models/service_event_origin.py +103 -0
- anyscale/client/openapi_client/models/service_event_scope.py +103 -0
- anyscale/client/openapi_client/models/service_event_scope_filter.py +104 -0
- anyscale/client/openapi_client/models/service_event_type.py +125 -0
- anyscale/client/openapi_client/models/service_event_verbose_message_model.py +180 -0
- anyscale/client/openapi_client/models/service_goal_states.py +100 -0
- anyscale/client/openapi_client/models/service_observability_urls.py +206 -0
- anyscale/client/openapi_client/models/service_sort_field.py +101 -0
- anyscale/client/openapi_client/models/service_type.py +100 -0
- anyscale/client/openapi_client/models/service_usage.py +353 -0
- anyscale/client/openapi_client/models/service_version_state.py +106 -0
- anyscale/client/openapi_client/models/serviceeventverbosemessagemodel_list_response.py +147 -0
- anyscale/client/openapi_client/models/session.py +834 -0
- anyscale/client/openapi_client/models/session_access.py +102 -0
- anyscale/client/openapi_client/models/session_autosync_sessions_update_message.py +121 -0
- anyscale/client/openapi_client/models/session_command.py +413 -0
- anyscale/client/openapi_client/models/session_command_finish_options.py +226 -0
- anyscale/client/openapi_client/models/session_command_id.py +121 -0
- anyscale/client/openapi_client/models/session_command_types.py +100 -0
- anyscale/client/openapi_client/models/session_create_message.py +148 -0
- anyscale/client/openapi_client/models/session_delete_message.py +121 -0
- anyscale/client/openapi_client/models/session_describe.py +175 -0
- anyscale/client/openapi_client/models/session_details.py +148 -0
- anyscale/client/openapi_client/models/session_event.py +267 -0
- anyscale/client/openapi_client/models/session_event_cause.py +150 -0
- anyscale/client/openapi_client/models/session_event_types.py +111 -0
- anyscale/client/openapi_client/models/session_execute_message.py +121 -0
- anyscale/client/openapi_client/models/session_finish_command_message.py +175 -0
- anyscale/client/openapi_client/models/session_history_item.py +146 -0
- anyscale/client/openapi_client/models/session_kill_command_message.py +121 -0
- anyscale/client/openapi_client/models/session_list_response.py +147 -0
- anyscale/client/openapi_client/models/session_patch_message.py +121 -0
- anyscale/client/openapi_client/models/session_response.py +121 -0
- anyscale/client/openapi_client/models/session_ssh_key.py +148 -0
- anyscale/client/openapi_client/models/session_starting_up_data.py +146 -0
- anyscale/client/openapi_client/models/session_state.py +111 -0
- anyscale/client/openapi_client/models/session_state_change_message.py +121 -0
- anyscale/client/openapi_client/models/session_state_data.py +146 -0
- anyscale/client/openapi_client/models/session_stopping_data.py +146 -0
- anyscale/client/openapi_client/models/sessioncommand_list_response.py +147 -0
- anyscale/client/openapi_client/models/sessioncommandid_response.py +121 -0
- anyscale/client/openapi_client/models/sessiondescribe_response.py +121 -0
- anyscale/client/openapi_client/models/sessiondetails_response.py +121 -0
- anyscale/client/openapi_client/models/sessionevent_list_response.py +147 -0
- anyscale/client/openapi_client/models/sessionhistoryitem_list_response.py +147 -0
- anyscale/client/openapi_client/models/sessions_sort_field.py +104 -0
- anyscale/client/openapi_client/models/sessionsshkey_response.py +121 -0
- anyscale/client/openapi_client/models/setup_initialize_session_options.py +225 -0
- anyscale/client/openapi_client/models/show_otp_source_return_api_model.py +121 -0
- anyscale/client/openapi_client/models/showotpsourcereturnapimodel_response.py +121 -0
- anyscale/client/openapi_client/models/snapshot_create_message.py +148 -0
- anyscale/client/openapi_client/models/snapshot_delete_message.py +148 -0
- anyscale/client/openapi_client/models/snapshot_patch_message.py +148 -0
- anyscale/client/openapi_client/models/socket_message_schemas.py +499 -0
- anyscale/client/openapi_client/models/socket_message_types.py +113 -0
- anyscale/client/openapi_client/models/socketmessageschemas_response.py +121 -0
- anyscale/client/openapi_client/models/socketmessagetypes_response.py +121 -0
- anyscale/client/openapi_client/models/sort_order.py +100 -0
- anyscale/client/openapi_client/models/sso_login_info.py +151 -0
- anyscale/client/openapi_client/models/ssologininfo_response.py +121 -0
- anyscale/client/openapi_client/models/start_session_options.py +146 -0
- anyscale/client/openapi_client/models/stop_session_options.py +227 -0
- anyscale/client/openapi_client/models/stream_publish_request.py +239 -0
- anyscale/client/openapi_client/models/subnet_id_with_availability_zone_aws.py +148 -0
- anyscale/client/openapi_client/models/support_requests_query.py +178 -0
- anyscale/client/openapi_client/models/supportedbaseimagesenum.py +1570 -0
- anyscale/client/openapi_client/models/templatized_compute_configs.py +202 -0
- anyscale/client/openapi_client/models/templatized_decorated_application_templates.py +202 -0
- anyscale/client/openapi_client/models/templatizedcomputeconfigs_response.py +121 -0
- anyscale/client/openapi_client/models/templatizeddecoratedapplicationtemplates_response.py +121 -0
- anyscale/client/openapi_client/models/text_query.py +178 -0
- anyscale/client/openapi_client/models/timestamped_logs_output.py +148 -0
- anyscale/client/openapi_client/models/timestampedlogsoutput_response.py +121 -0
- anyscale/client/openapi_client/models/tool.py +100 -0
- anyscale/client/openapi_client/models/tracing_config.py +178 -0
- anyscale/client/openapi_client/models/try_login_email_response.py +208 -0
- anyscale/client/openapi_client/models/tryloginemailresponse_response.py +121 -0
- anyscale/client/openapi_client/models/unified_job_sort_field.py +103 -0
- anyscale/client/openapi_client/models/unified_job_status.py +114 -0
- anyscale/client/openapi_client/models/unified_job_type.py +100 -0
- anyscale/client/openapi_client/models/update_cloud_with_cloud_resource.py +178 -0
- anyscale/client/openapi_client/models/update_cloud_with_cloud_resource_gcp.py +178 -0
- anyscale/client/openapi_client/models/update_cluster_dns.py +152 -0
- anyscale/client/openapi_client/models/update_compute_template.py +146 -0
- anyscale/client/openapi_client/models/update_compute_template_config.py +464 -0
- anyscale/client/openapi_client/models/update_endpoint.py +152 -0
- anyscale/client/openapi_client/models/update_machine_pool_request.py +151 -0
- anyscale/client/openapi_client/models/update_model_deployment.py +152 -0
- anyscale/client/openapi_client/models/update_organization_collaborator.py +121 -0
- anyscale/client/openapi_client/models/update_project_collaborator.py +121 -0
- anyscale/client/openapi_client/models/update_resource_quota.py +122 -0
- anyscale/client/openapi_client/models/updatemachinepoolresponse_response.py +121 -0
- anyscale/client/openapi_client/models/upload_session_command_logs_locations.py +148 -0
- anyscale/client/openapi_client/models/uploadsessioncommandlogslocations_response.py +121 -0
- anyscale/client/openapi_client/models/user_info.py +569 -0
- anyscale/client/openapi_client/models/user_resend_email_options.py +147 -0
- anyscale/client/openapi_client/models/user_service_access_types.py +100 -0
- anyscale/client/openapi_client/models/userinfo_response.py +121 -0
- anyscale/client/openapi_client/models/utm_fields.py +224 -0
- anyscale/client/openapi_client/models/ux_instance.py +468 -0
- anyscale/client/openapi_client/models/validate_otp_params_api_model.py +121 -0
- anyscale/client/openapi_client/models/validation_error.py +175 -0
- anyscale/client/openapi_client/models/verify_response.py +147 -0
- anyscale/client/openapi_client/models/verifyresponse_response.py +121 -0
- anyscale/client/openapi_client/models/visibility.py +100 -0
- anyscale/client/openapi_client/models/waitlist_status_response.py +121 -0
- anyscale/client/openapi_client/models/waitlist_status_type.py +100 -0
- anyscale/client/openapi_client/models/waitliststatusresponse_response.py +121 -0
- anyscale/client/openapi_client/models/wand_b_run_details.py +147 -0
- anyscale/client/openapi_client/models/web_terminal.py +121 -0
- anyscale/client/openapi_client/models/webterminal_list_response.py +147 -0
- anyscale/client/openapi_client/models/webterminal_response.py +121 -0
- anyscale/client/openapi_client/models/worker_node_type.py +404 -0
- anyscale/client/openapi_client/models/workload_type.py +102 -0
- anyscale/client/openapi_client/models/workspace_dataplane_artifact.py +181 -0
- anyscale/client/openapi_client/models/workspace_dataplane_artifacts.py +123 -0
- anyscale/client/openapi_client/models/workspace_dataplane_proxied_artifacts.py +178 -0
- anyscale/client/openapi_client/models/workspace_event.py +325 -0
- anyscale/client/openapi_client/models/workspace_event_source.py +100 -0
- anyscale/client/openapi_client/models/workspace_event_source_filter.py +101 -0
- anyscale/client/openapi_client/models/workspace_readme.py +123 -0
- anyscale/client/openapi_client/models/workspace_snapshot_states.py +108 -0
- anyscale/client/openapi_client/models/workspace_template.py +353 -0
- anyscale/client/openapi_client/models/workspace_template_cluster_environment_metadata.py +178 -0
- anyscale/client/openapi_client/models/workspacedataplaneartifacts_response.py +121 -0
- anyscale/client/openapi_client/models/workspacedataplaneproxiedartifacts_response.py +121 -0
- anyscale/client/openapi_client/models/workspaceevent_list_response.py +147 -0
- anyscale/client/openapi_client/models/workspacereadme_response.py +121 -0
- anyscale/client/openapi_client/models/workspacetemplate_list_response.py +147 -0
- anyscale/client/openapi_client/models/workspacetemplateclusterenvironmentmetadata_response.py +121 -0
- anyscale/client/openapi_client/models/write_cloud.py +546 -0
- anyscale/client/openapi_client/models/write_cluster_config.py +123 -0
- anyscale/client/openapi_client/models/write_project.py +226 -0
- anyscale/client/openapi_client/models/write_session.py +147 -0
- anyscale/client/openapi_client/models/write_support_request.py +121 -0
- anyscale/client/openapi_client/rest.py +296 -0
- anyscale/client/requirements.txt +6 -0
- anyscale/client/setup.cfg +2 -0
- anyscale/client/setup.py +40 -0
- anyscale/client/test-requirements.txt +3 -0
- anyscale/client/tox.ini +9 -0
- anyscale/cloud.py +216 -0
- anyscale/cloud_resource.py +1032 -0
- anyscale/cluster.py +138 -0
- anyscale/cluster_compute.py +167 -0
- anyscale/cluster_env.py +173 -0
- anyscale/commands/__init__.py +0 -0
- anyscale/commands/aggregated_instance_usage_commands.py +86 -0
- anyscale/commands/anyscale_api/__init__.py +0 -0
- anyscale/commands/anyscale_api/api_commands.py +23 -0
- anyscale/commands/anyscale_api/session_commands_commands.py +80 -0
- anyscale/commands/anyscale_api/session_operations_commands.py +28 -0
- anyscale/commands/anyscale_api/sessions_commands.py +152 -0
- anyscale/commands/auth_commands.py +41 -0
- anyscale/commands/cloud_commands.py +1011 -0
- anyscale/commands/cloud_commands_util.py +10 -0
- anyscale/commands/cluster_commands.py +476 -0
- anyscale/commands/cluster_env_commands.py +139 -0
- anyscale/commands/command_examples.py +495 -0
- anyscale/commands/compute_config_commands.py +252 -0
- anyscale/commands/config_commands.py +213 -0
- anyscale/commands/exec_commands.py +14 -0
- anyscale/commands/experimental_integrations_commands.py +70 -0
- anyscale/commands/image_commands.py +125 -0
- anyscale/commands/job_commands.py +745 -0
- anyscale/commands/list_commands.py +85 -0
- anyscale/commands/llm/dataset_commands.py +269 -0
- anyscale/commands/llm/group.py +15 -0
- anyscale/commands/llm/models_commands.py +123 -0
- anyscale/commands/login_commands.py +79 -0
- anyscale/commands/logs_commands.py +312 -0
- anyscale/commands/machine_commands.py +116 -0
- anyscale/commands/machine_pool_commands.py +163 -0
- anyscale/commands/migrate_commands.py +84 -0
- anyscale/commands/project_commands.py +203 -0
- anyscale/commands/resource_quota_commands.py +214 -0
- anyscale/commands/schedule_commands.py +436 -0
- anyscale/commands/service_account_commands.py +72 -0
- anyscale/commands/service_commands.py +738 -0
- anyscale/commands/session_commands_hidden.py +179 -0
- anyscale/commands/util.py +152 -0
- anyscale/commands/workspace_commands.py +511 -0
- anyscale/commands/workspace_commands_v2.py +874 -0
- anyscale/component_activity_util.py +83 -0
- anyscale/compute_config/__init__.py +84 -0
- anyscale/compute_config/_private/compute_config_sdk.py +433 -0
- anyscale/compute_config/commands.py +122 -0
- anyscale/compute_config/models.py +630 -0
- anyscale/conf.py +23 -0
- anyscale/connect.py +1323 -0
- anyscale/connect_utils/__init__.py +0 -0
- anyscale/connect_utils/prepare_cluster.py +962 -0
- anyscale/connect_utils/project.py +298 -0
- anyscale/connect_utils/start_interactive_session.py +437 -0
- anyscale/controllers/__init__.py +0 -0
- anyscale/controllers/auth_controller.py +134 -0
- anyscale/controllers/base_controller.py +52 -0
- anyscale/controllers/cloud_controller.py +3609 -0
- anyscale/controllers/cloud_functional_verification_controller.py +858 -0
- anyscale/controllers/cluster_controller.py +720 -0
- anyscale/controllers/cluster_env_controller.py +219 -0
- anyscale/controllers/compute_config_controller.py +351 -0
- anyscale/controllers/config_controller.py +422 -0
- anyscale/controllers/experimental_integrations_controller.py +80 -0
- anyscale/controllers/job_controller.py +647 -0
- anyscale/controllers/jobs_bg_controller.py +0 -0
- anyscale/controllers/list_controller.py +290 -0
- anyscale/controllers/llm/__init__.py +0 -0
- anyscale/controllers/llm/models_controller.py +144 -0
- anyscale/controllers/logs_controller.py +449 -0
- anyscale/controllers/machine_controller.py +37 -0
- anyscale/controllers/machine_pool_controller.py +86 -0
- anyscale/controllers/project_controller.py +281 -0
- anyscale/controllers/resource_quota_controller.py +183 -0
- anyscale/controllers/schedule_controller.py +333 -0
- anyscale/controllers/service_account_controller.py +168 -0
- anyscale/controllers/service_controller.py +453 -0
- anyscale/controllers/workspace_controller.py +253 -0
- anyscale/feature_flags.py +4 -0
- anyscale/fingerprint.py +62 -0
- anyscale/formatters/__init__.py +0 -0
- anyscale/formatters/clouds_formatter.py +65 -0
- anyscale/formatters/common_formatter.py +22 -0
- anyscale/gcp_verification.py +792 -0
- anyscale/image/__init__.py +73 -0
- anyscale/image/_private/image_sdk.py +202 -0
- anyscale/image/commands.py +117 -0
- anyscale/image/models.py +57 -0
- anyscale/integrations.py +329 -0
- anyscale/job/__init__.py +166 -0
- anyscale/job/_private/job_sdk.py +497 -0
- anyscale/job/commands.py +267 -0
- anyscale/job/models.py +500 -0
- anyscale/links.py +4 -0
- anyscale/llm/__init__.py +2 -0
- anyscale/llm/dataset/__init__.py +2 -0
- anyscale/llm/dataset/_private/__init__.py +0 -0
- anyscale/llm/dataset/_private/docs.py +63 -0
- anyscale/llm/dataset/_private/models.py +71 -0
- anyscale/llm/dataset/_private/sdk.py +147 -0
- anyscale/llm/model/__init__.py +2 -0
- anyscale/llm/model/_private/models_sdk.py +62 -0
- anyscale/llm/model/commands.py +93 -0
- anyscale/llm/model/models.py +171 -0
- anyscale/llm/model/sdk.py +62 -0
- anyscale/llm/sdk.py +27 -0
- anyscale/memorydb_supported_zones.json +22 -0
- anyscale/models/job_model.py +457 -0
- anyscale/models/service_model.py +125 -0
- anyscale/project.py +501 -0
- anyscale/schedule/__init__.py +91 -0
- anyscale/schedule/_private/schedule_sdk.py +165 -0
- anyscale/schedule/commands.py +149 -0
- anyscale/schedule/models.py +145 -0
- anyscale/scripts.py +164 -0
- anyscale/sdk/anyscale_client/__init__.py +235 -0
- anyscale/sdk/anyscale_client/api/__init__.py +6 -0
- anyscale/sdk/anyscale_client/api/default_api.py +11625 -0
- anyscale/sdk/anyscale_client/api_client.py +647 -0
- anyscale/sdk/anyscale_client/configuration.py +373 -0
- anyscale/sdk/anyscale_client/exceptions.py +120 -0
- anyscale/sdk/anyscale_client/models/__init__.py +220 -0
- anyscale/sdk/anyscale_client/models/access_config.py +122 -0
- anyscale/sdk/anyscale_client/models/app_config.py +436 -0
- anyscale/sdk/anyscale_client/models/app_config_config_schema.py +235 -0
- anyscale/sdk/anyscale_client/models/appconfig_list_response.py +147 -0
- anyscale/sdk/anyscale_client/models/appconfig_response.py +121 -0
- anyscale/sdk/anyscale_client/models/apply_production_service_v2_model.py +490 -0
- anyscale/sdk/anyscale_client/models/apply_service_model.py +490 -0
- anyscale/sdk/anyscale_client/models/archive_status.py +101 -0
- anyscale/sdk/anyscale_client/models/base_job_status.py +105 -0
- anyscale/sdk/anyscale_client/models/baseimagesenum.py +2130 -0
- anyscale/sdk/anyscale_client/models/build.py +607 -0
- anyscale/sdk/anyscale_client/models/build_list_response.py +147 -0
- anyscale/sdk/anyscale_client/models/build_log_response.py +123 -0
- anyscale/sdk/anyscale_client/models/build_response.py +121 -0
- anyscale/sdk/anyscale_client/models/build_status.py +104 -0
- anyscale/sdk/anyscale_client/models/buildlogresponse_response.py +121 -0
- anyscale/sdk/anyscale_client/models/cloud.py +802 -0
- anyscale/sdk/anyscale_client/models/cloud_config.py +206 -0
- anyscale/sdk/anyscale_client/models/cloud_list_response.py +147 -0
- anyscale/sdk/anyscale_client/models/cloud_providers.py +103 -0
- anyscale/sdk/anyscale_client/models/cloud_response.py +121 -0
- anyscale/sdk/anyscale_client/models/cloud_state.py +104 -0
- anyscale/sdk/anyscale_client/models/cloud_status.py +100 -0
- anyscale/sdk/anyscale_client/models/cloud_type.py +100 -0
- anyscale/sdk/anyscale_client/models/cloud_types.py +100 -0
- anyscale/sdk/anyscale_client/models/cloud_version.py +100 -0
- anyscale/sdk/anyscale_client/models/clouds_query.py +150 -0
- anyscale/sdk/anyscale_client/models/cluster.py +721 -0
- anyscale/sdk/anyscale_client/models/cluster_compute.py +415 -0
- anyscale/sdk/anyscale_client/models/cluster_compute_config.py +461 -0
- anyscale/sdk/anyscale_client/models/cluster_computes_query.py +293 -0
- anyscale/sdk/anyscale_client/models/cluster_environment.py +380 -0
- anyscale/sdk/anyscale_client/models/cluster_environment_build.py +578 -0
- anyscale/sdk/anyscale_client/models/cluster_environment_build_log_response.py +123 -0
- anyscale/sdk/anyscale_client/models/cluster_environment_build_operation.py +237 -0
- anyscale/sdk/anyscale_client/models/cluster_environment_build_status.py +104 -0
- anyscale/sdk/anyscale_client/models/cluster_environments_query.py +290 -0
- anyscale/sdk/anyscale_client/models/cluster_head_node_info.py +152 -0
- anyscale/sdk/anyscale_client/models/cluster_list_response.py +147 -0
- anyscale/sdk/anyscale_client/models/cluster_management_stack_versions.py +100 -0
- anyscale/sdk/anyscale_client/models/cluster_operation.py +266 -0
- anyscale/sdk/anyscale_client/models/cluster_operation_type.py +101 -0
- anyscale/sdk/anyscale_client/models/cluster_response.py +121 -0
- anyscale/sdk/anyscale_client/models/cluster_services_urls.py +430 -0
- anyscale/sdk/anyscale_client/models/cluster_state.py +108 -0
- anyscale/sdk/anyscale_client/models/cluster_status.py +104 -0
- anyscale/sdk/anyscale_client/models/cluster_status_details.py +100 -0
- anyscale/sdk/anyscale_client/models/clustercompute_list_response.py +147 -0
- anyscale/sdk/anyscale_client/models/clustercompute_response.py +121 -0
- anyscale/sdk/anyscale_client/models/clusterenvironment_list_response.py +147 -0
- anyscale/sdk/anyscale_client/models/clusterenvironment_response.py +121 -0
- anyscale/sdk/anyscale_client/models/clusterenvironmentbuild_list_response.py +147 -0
- anyscale/sdk/anyscale_client/models/clusterenvironmentbuild_response.py +121 -0
- anyscale/sdk/anyscale_client/models/clusterenvironmentbuildlogresponse_response.py +121 -0
- anyscale/sdk/anyscale_client/models/clusterenvironmentbuildoperation_response.py +121 -0
- anyscale/sdk/anyscale_client/models/clusteroperation_response.py +121 -0
- anyscale/sdk/anyscale_client/models/clusters_query.py +234 -0
- anyscale/sdk/anyscale_client/models/compute_node_type.py +292 -0
- anyscale/sdk/anyscale_client/models/compute_stack.py +100 -0
- anyscale/sdk/anyscale_client/models/compute_template.py +415 -0
- anyscale/sdk/anyscale_client/models/compute_template_config.py +461 -0
- anyscale/sdk/anyscale_client/models/compute_template_query.py +316 -0
- anyscale/sdk/anyscale_client/models/computetemplate_list_response.py +147 -0
- anyscale/sdk/anyscale_client/models/computetemplate_response.py +121 -0
- anyscale/sdk/anyscale_client/models/computetemplateconfig_response.py +121 -0
- anyscale/sdk/anyscale_client/models/create_app_config.py +235 -0
- anyscale/sdk/anyscale_client/models/create_app_config_configuration_schema.py +235 -0
- anyscale/sdk/anyscale_client/models/create_build.py +263 -0
- anyscale/sdk/anyscale_client/models/create_byod_app_config_configuration_schema.py +206 -0
- anyscale/sdk/anyscale_client/models/create_byod_cluster_environment.py +180 -0
- anyscale/sdk/anyscale_client/models/create_byod_cluster_environment_build.py +152 -0
- anyscale/sdk/anyscale_client/models/create_byod_cluster_environment_configuration_schema.py +208 -0
- anyscale/sdk/anyscale_client/models/create_cloud.py +518 -0
- anyscale/sdk/anyscale_client/models/create_cluster.py +376 -0
- anyscale/sdk/anyscale_client/models/create_cluster_compute.py +229 -0
- anyscale/sdk/anyscale_client/models/create_cluster_compute_config.py +463 -0
- anyscale/sdk/anyscale_client/models/create_cluster_environment.py +235 -0
- anyscale/sdk/anyscale_client/models/create_cluster_environment_build.py +263 -0
- anyscale/sdk/anyscale_client/models/create_cluster_environment_configuration_schema.py +235 -0
- anyscale/sdk/anyscale_client/models/create_compute_template.py +229 -0
- anyscale/sdk/anyscale_client/models/create_compute_template_config.py +464 -0
- anyscale/sdk/anyscale_client/models/create_job_queue_config.py +206 -0
- anyscale/sdk/anyscale_client/models/create_production_job.py +234 -0
- anyscale/sdk/anyscale_client/models/create_production_job_config.py +347 -0
- anyscale/sdk/anyscale_client/models/create_project.py +207 -0
- anyscale/sdk/anyscale_client/models/create_schedule.py +263 -0
- anyscale/sdk/anyscale_client/models/create_session.py +432 -0
- anyscale/sdk/anyscale_client/models/create_session_command.py +152 -0
- anyscale/sdk/anyscale_client/models/create_sso_config.py +150 -0
- anyscale/sdk/anyscale_client/models/grpc_protocol_config.py +178 -0
- anyscale/sdk/anyscale_client/models/ha_job_goal_states.py +102 -0
- anyscale/sdk/anyscale_client/models/ha_job_states.py +109 -0
- anyscale/sdk/anyscale_client/models/http_protocol_config.py +150 -0
- anyscale/sdk/anyscale_client/models/http_validation_error.py +120 -0
- anyscale/sdk/anyscale_client/models/idle_termination_status.py +104 -0
- anyscale/sdk/anyscale_client/models/job.py +466 -0
- anyscale/sdk/anyscale_client/models/job_list_response.py +147 -0
- anyscale/sdk/anyscale_client/models/job_queue_config.py +122 -0
- anyscale/sdk/anyscale_client/models/job_queue_execution_mode.py +101 -0
- anyscale/sdk/anyscale_client/models/job_queue_spec.py +263 -0
- anyscale/sdk/anyscale_client/models/job_run_type.py +101 -0
- anyscale/sdk/anyscale_client/models/job_status.py +105 -0
- anyscale/sdk/anyscale_client/models/jobs_query.py +458 -0
- anyscale/sdk/anyscale_client/models/jobs_sort_field.py +104 -0
- anyscale/sdk/anyscale_client/models/list_response_metadata.py +146 -0
- anyscale/sdk/anyscale_client/models/list_service_model.py +347 -0
- anyscale/sdk/anyscale_client/models/listservicemodel_list_response.py +147 -0
- anyscale/sdk/anyscale_client/models/log_download_result.py +207 -0
- anyscale/sdk/anyscale_client/models/log_file_chunk.py +439 -0
- anyscale/sdk/anyscale_client/models/log_level_types.py +100 -0
- anyscale/sdk/anyscale_client/models/log_stream.py +151 -0
- anyscale/sdk/anyscale_client/models/logdownloadresult_response.py +121 -0
- anyscale/sdk/anyscale_client/models/logstream_response.py +121 -0
- anyscale/sdk/anyscale_client/models/node_type.py +100 -0
- anyscale/sdk/anyscale_client/models/object_storage_config.py +122 -0
- anyscale/sdk/anyscale_client/models/object_storage_config_s3.py +256 -0
- anyscale/sdk/anyscale_client/models/objectstorageconfig_response.py +121 -0
- anyscale/sdk/anyscale_client/models/operation_error.py +123 -0
- anyscale/sdk/anyscale_client/models/operation_progress.py +123 -0
- anyscale/sdk/anyscale_client/models/operation_result.py +150 -0
- anyscale/sdk/anyscale_client/models/organization.py +209 -0
- anyscale/sdk/anyscale_client/models/organization_response.py +121 -0
- anyscale/sdk/anyscale_client/models/page_query.py +153 -0
- anyscale/sdk/anyscale_client/models/pause_schedule.py +123 -0
- anyscale/sdk/anyscale_client/models/production_job.py +437 -0
- anyscale/sdk/anyscale_client/models/production_job_config.py +348 -0
- anyscale/sdk/anyscale_client/models/production_job_state_transition.py +293 -0
- anyscale/sdk/anyscale_client/models/production_service_v2_model.py +612 -0
- anyscale/sdk/anyscale_client/models/production_service_v2_version_model.py +437 -0
- anyscale/sdk/anyscale_client/models/productionjob_list_response.py +147 -0
- anyscale/sdk/anyscale_client/models/productionjob_response.py +121 -0
- anyscale/sdk/anyscale_client/models/productionservicev2_model_response.py +121 -0
- anyscale/sdk/anyscale_client/models/project.py +467 -0
- anyscale/sdk/anyscale_client/models/project_list_response.py +147 -0
- anyscale/sdk/anyscale_client/models/project_response.py +121 -0
- anyscale/sdk/anyscale_client/models/projects_query.py +234 -0
- anyscale/sdk/anyscale_client/models/protocols.py +150 -0
- anyscale/sdk/anyscale_client/models/python_modules.py +150 -0
- anyscale/sdk/anyscale_client/models/python_version.py +105 -0
- anyscale/sdk/anyscale_client/models/ray_gcs_external_storage_config.py +178 -0
- anyscale/sdk/anyscale_client/models/ray_runtime_env_config.py +262 -0
- anyscale/sdk/anyscale_client/models/resources.py +234 -0
- anyscale/sdk/anyscale_client/models/rollback_service_model.py +122 -0
- anyscale/sdk/anyscale_client/models/rollout_strategy.py +100 -0
- anyscale/sdk/anyscale_client/models/runtime_environment.py +406 -0
- anyscale/sdk/anyscale_client/models/runtimeenvironment_response.py +121 -0
- anyscale/sdk/anyscale_client/models/schedule_api_model.py +467 -0
- anyscale/sdk/anyscale_client/models/schedule_config.py +151 -0
- anyscale/sdk/anyscale_client/models/scheduleapimodel_list_response.py +147 -0
- anyscale/sdk/anyscale_client/models/scheduleapimodel_response.py +121 -0
- anyscale/sdk/anyscale_client/models/service_config.py +178 -0
- anyscale/sdk/anyscale_client/models/service_event_current_state.py +108 -0
- anyscale/sdk/anyscale_client/models/service_goal_states.py +100 -0
- anyscale/sdk/anyscale_client/models/service_model.py +612 -0
- anyscale/sdk/anyscale_client/models/service_observability_urls.py +206 -0
- anyscale/sdk/anyscale_client/models/service_sort_field.py +101 -0
- anyscale/sdk/anyscale_client/models/service_type.py +100 -0
- anyscale/sdk/anyscale_client/models/service_version_state.py +106 -0
- anyscale/sdk/anyscale_client/models/servicemodel_list_response.py +147 -0
- anyscale/sdk/anyscale_client/models/servicemodel_response.py +121 -0
- anyscale/sdk/anyscale_client/models/session.py +1535 -0
- anyscale/sdk/anyscale_client/models/session_command.py +350 -0
- anyscale/sdk/anyscale_client/models/session_command_types.py +100 -0
- anyscale/sdk/anyscale_client/models/session_event.py +267 -0
- anyscale/sdk/anyscale_client/models/session_event_cause.py +150 -0
- anyscale/sdk/anyscale_client/models/session_event_types.py +111 -0
- anyscale/sdk/anyscale_client/models/session_list_response.py +147 -0
- anyscale/sdk/anyscale_client/models/session_operation.py +266 -0
- anyscale/sdk/anyscale_client/models/session_operation_type.py +101 -0
- anyscale/sdk/anyscale_client/models/session_response.py +121 -0
- anyscale/sdk/anyscale_client/models/session_starting_up_data.py +146 -0
- anyscale/sdk/anyscale_client/models/session_state.py +111 -0
- anyscale/sdk/anyscale_client/models/session_state_data.py +146 -0
- anyscale/sdk/anyscale_client/models/session_stopping_data.py +146 -0
- anyscale/sdk/anyscale_client/models/sessioncommand_list_response.py +147 -0
- anyscale/sdk/anyscale_client/models/sessioncommand_response.py +121 -0
- anyscale/sdk/anyscale_client/models/sessionevent_list_response.py +147 -0
- anyscale/sdk/anyscale_client/models/sessionoperation_response.py +121 -0
- anyscale/sdk/anyscale_client/models/sessions_query.py +206 -0
- anyscale/sdk/anyscale_client/models/sort_by_clause_jobs_sort_field.py +148 -0
- anyscale/sdk/anyscale_client/models/sort_order.py +100 -0
- anyscale/sdk/anyscale_client/models/sso_config.py +237 -0
- anyscale/sdk/anyscale_client/models/sso_mode.py +101 -0
- anyscale/sdk/anyscale_client/models/ssoconfig_response.py +121 -0
- anyscale/sdk/anyscale_client/models/start_cluster_options.py +178 -0
- anyscale/sdk/anyscale_client/models/start_session_options.py +206 -0
- anyscale/sdk/anyscale_client/models/static_sso_config.py +210 -0
- anyscale/sdk/anyscale_client/models/supportedbaseimagesenum.py +1570 -0
- anyscale/sdk/anyscale_client/models/terminate_cluster_options.py +122 -0
- anyscale/sdk/anyscale_client/models/terminate_session_options.py +206 -0
- anyscale/sdk/anyscale_client/models/text_query.py +178 -0
- anyscale/sdk/anyscale_client/models/tracing_config.py +178 -0
- anyscale/sdk/anyscale_client/models/update_app_config.py +122 -0
- anyscale/sdk/anyscale_client/models/update_cloud.py +150 -0
- anyscale/sdk/anyscale_client/models/update_cluster.py +206 -0
- anyscale/sdk/anyscale_client/models/update_compute_template.py +146 -0
- anyscale/sdk/anyscale_client/models/update_compute_template_config.py +464 -0
- anyscale/sdk/anyscale_client/models/update_organization.py +123 -0
- anyscale/sdk/anyscale_client/models/update_project.py +150 -0
- anyscale/sdk/anyscale_client/models/update_session.py +150 -0
- anyscale/sdk/anyscale_client/models/user_service_access_types.py +100 -0
- anyscale/sdk/anyscale_client/models/ux_instance.py +468 -0
- anyscale/sdk/anyscale_client/models/validation_error.py +175 -0
- anyscale/sdk/anyscale_client/models/worker_node_type.py +404 -0
- anyscale/sdk/anyscale_client/rest.py +296 -0
- anyscale/sdk/anyscale_client/sdk.py +634 -0
- anyscale/service/__init__.py +168 -0
- anyscale/service/_private/service_sdk.py +702 -0
- anyscale/service/commands.py +261 -0
- anyscale/service/models.py +671 -0
- anyscale/shared_anyscale_utils/__init__.py +1 -0
- anyscale/shared_anyscale_utils/aws.py +153 -0
- anyscale/shared_anyscale_utils/bytes_util.py +10 -0
- anyscale/shared_anyscale_utils/conf.py +47 -0
- anyscale/shared_anyscale_utils/default_anyscale_aws.yaml +74 -0
- anyscale/shared_anyscale_utils/default_anyscale_gcp.yaml +80 -0
- anyscale/shared_anyscale_utils/headers.py +38 -0
- anyscale/shared_anyscale_utils/latest_ray_version.py +2 -0
- anyscale/shared_anyscale_utils/project.py +15 -0
- anyscale/shared_anyscale_utils/test_util.py +22 -0
- anyscale/shared_anyscale_utils/tests/__init__.py +1 -0
- anyscale/shared_anyscale_utils/tests/test_asyncio.py +41 -0
- anyscale/shared_anyscale_utils/tests/test_ray_semver.py +63 -0
- anyscale/shared_anyscale_utils/util.py +50 -0
- anyscale/shared_anyscale_utils/utils/__init__.py +2 -0
- anyscale/shared_anyscale_utils/utils/asyncio.py +120 -0
- anyscale/shared_anyscale_utils/utils/byod.py +40 -0
- anyscale/shared_anyscale_utils/utils/collections.py +33 -0
- anyscale/shared_anyscale_utils/utils/id_gen.py +147 -0
- anyscale/shared_anyscale_utils/utils/protected_string.py +89 -0
- anyscale/shared_anyscale_utils/utils/ray_semver.py +81 -0
- anyscale/snapshot.py +46 -0
- anyscale/tables.py +82 -0
- anyscale/util.py +1155 -0
- anyscale/utils/__init__.py +0 -0
- anyscale/utils/cli_version_check_util.py +63 -0
- anyscale/utils/cloud_update_utils.py +862 -0
- anyscale/utils/cloud_utils.py +317 -0
- anyscale/utils/cluster_debug.py +191 -0
- anyscale/utils/connect_helpers.py +155 -0
- anyscale/utils/deprecation_util.py +32 -0
- anyscale/utils/entity_arg_utils.py +43 -0
- anyscale/utils/env_utils.py +17 -0
- anyscale/utils/gcp_managed_setup_utils.py +888 -0
- anyscale/utils/gcp_utils.py +312 -0
- anyscale/utils/imports/__init__.py +0 -0
- anyscale/utils/imports/all.py +13 -0
- anyscale/utils/imports/azure.py +14 -0
- anyscale/utils/imports/gcp.py +59 -0
- anyscale/utils/logs_utils.py +141 -0
- anyscale/utils/name_utils.py +33 -0
- anyscale/utils/network_verification.py +153 -0
- anyscale/utils/ray_utils.py +128 -0
- anyscale/utils/ray_version_checker.py +48 -0
- anyscale/utils/ray_version_utils.py +53 -0
- anyscale/utils/runtime_env.py +487 -0
- anyscale/utils/s3.py +92 -0
- anyscale/utils/user_utils.py +17 -0
- anyscale/utils/workload_types.py +7 -0
- anyscale/utils/workspace_notification.py +39 -0
- anyscale/utils/workspace_utils.py +65 -0
- anyscale/version.py +1 -0
- anyscale/webterminal/__init__.py +0 -0
- anyscale/webterminal/bash-preexec.sh +370 -0
- anyscale/webterminal/command_persister.py +164 -0
- anyscale/webterminal/utils.py +176 -0
- anyscale/webterminal/webterminal.py +311 -0
- anyscale/workspace/__init__.py +270 -0
- anyscale/workspace/_private/workspace_sdk.py +737 -0
- anyscale/workspace/commands.py +472 -0
- anyscale/workspace/models.py +296 -0
- anyscale/workspace_utils.py +35 -0
- anyscale-0.24.86.dist-info/LICENSE +68 -0
- anyscale-0.24.86.dist-info/METADATA +82 -0
- anyscale-0.24.86.dist-info/NOTICE +6 -0
- anyscale-0.24.86.dist-info/RECORD +1131 -0
- anyscale-0.24.86.dist-info/WHEEL +5 -0
- anyscale-0.24.86.dist-info/entry_points.txt +2 -0
- anyscale-0.24.86.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,3609 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Fetches data required and formats output for `anyscale cloud` commands.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import copy
|
|
6
|
+
import json
|
|
7
|
+
from os import getenv
|
|
8
|
+
import re
|
|
9
|
+
import secrets
|
|
10
|
+
import time
|
|
11
|
+
from typing import Any, Dict, List, MutableSequence, Optional, Tuple
|
|
12
|
+
import uuid
|
|
13
|
+
|
|
14
|
+
import boto3
|
|
15
|
+
from botocore.exceptions import ClientError, NoCredentialsError
|
|
16
|
+
import click
|
|
17
|
+
from click import Abort, ClickException
|
|
18
|
+
|
|
19
|
+
from anyscale import __version__ as anyscale_version
|
|
20
|
+
from anyscale.aws_iam_policies import get_anyscale_iam_permissions_ec2_restricted
|
|
21
|
+
from anyscale.cli_logger import CloudSetupLogger
|
|
22
|
+
from anyscale.client.openapi_client.models import (
|
|
23
|
+
AWSMemoryDBClusterConfig,
|
|
24
|
+
CloudAnalyticsEventCloudResource,
|
|
25
|
+
CloudAnalyticsEventCommandName,
|
|
26
|
+
CloudAnalyticsEventName,
|
|
27
|
+
CloudProviders,
|
|
28
|
+
CloudState,
|
|
29
|
+
CloudWithCloudResource,
|
|
30
|
+
CloudWithCloudResourceGCP,
|
|
31
|
+
ClusterManagementStackVersions,
|
|
32
|
+
ComputeStack,
|
|
33
|
+
CreateCloudResource,
|
|
34
|
+
CreateCloudResourceGCP,
|
|
35
|
+
EditableCloudResource,
|
|
36
|
+
EditableCloudResourceGCP,
|
|
37
|
+
NFSMountTarget,
|
|
38
|
+
SubnetIdWithAvailabilityZoneAWS,
|
|
39
|
+
UpdateCloudWithCloudResource,
|
|
40
|
+
UpdateCloudWithCloudResourceGCP,
|
|
41
|
+
WriteCloud,
|
|
42
|
+
)
|
|
43
|
+
from anyscale.client.openapi_client.models.gcp_file_store_config import (
|
|
44
|
+
GCPFileStoreConfig,
|
|
45
|
+
)
|
|
46
|
+
from anyscale.cloud import (
|
|
47
|
+
get_cloud_id_and_name,
|
|
48
|
+
get_cloud_json_from_id,
|
|
49
|
+
get_cloud_resource_by_cloud_id,
|
|
50
|
+
get_organization_id,
|
|
51
|
+
)
|
|
52
|
+
from anyscale.cloud_resource import (
|
|
53
|
+
associate_aws_subnets_with_azs,
|
|
54
|
+
GCS_STORAGE_PREFIX,
|
|
55
|
+
S3_ARN_PREFIX,
|
|
56
|
+
S3_STORAGE_PREFIX,
|
|
57
|
+
verify_aws_cloudformation_stack,
|
|
58
|
+
verify_aws_efs,
|
|
59
|
+
verify_aws_iam_roles,
|
|
60
|
+
verify_aws_memorydb_cluster,
|
|
61
|
+
verify_aws_s3,
|
|
62
|
+
verify_aws_security_groups,
|
|
63
|
+
verify_aws_subnets,
|
|
64
|
+
verify_aws_vpc,
|
|
65
|
+
)
|
|
66
|
+
from anyscale.conf import ANYSCALE_IAM_ROLE_NAME
|
|
67
|
+
from anyscale.controllers.base_controller import BaseController
|
|
68
|
+
from anyscale.controllers.cloud_functional_verification_controller import (
|
|
69
|
+
CloudFunctionalVerificationController,
|
|
70
|
+
CloudFunctionalVerificationType,
|
|
71
|
+
)
|
|
72
|
+
from anyscale.formatters import clouds_formatter
|
|
73
|
+
from anyscale.shared_anyscale_utils.aws import AwsRoleArn
|
|
74
|
+
from anyscale.shared_anyscale_utils.conf import ANYSCALE_ENV
|
|
75
|
+
from anyscale.util import ( # pylint:disable=private-import
|
|
76
|
+
_client,
|
|
77
|
+
_get_aws_efs_mount_target_ip,
|
|
78
|
+
_get_memorydb_cluster_config,
|
|
79
|
+
_get_role,
|
|
80
|
+
_update_external_ids_for_policy,
|
|
81
|
+
confirm,
|
|
82
|
+
GCP_DEPLOYMENT_MANAGER_TIMEOUT_SECONDS_LONG,
|
|
83
|
+
get_anyscale_cross_account_iam_policies,
|
|
84
|
+
get_available_regions,
|
|
85
|
+
get_user_env_aws_account,
|
|
86
|
+
prepare_cloudformation_template,
|
|
87
|
+
REDIS_TLS_ADDRESS_PREFIX,
|
|
88
|
+
)
|
|
89
|
+
from anyscale.utils.cloud_update_utils import (
|
|
90
|
+
CLOUDFORMATION_TIMEOUT_SECONDS_LONG,
|
|
91
|
+
get_or_create_memorydb,
|
|
92
|
+
MEMORYDB_REDIS_PORT,
|
|
93
|
+
try_delete_customer_drifts_policy,
|
|
94
|
+
update_iam_role,
|
|
95
|
+
)
|
|
96
|
+
from anyscale.utils.cloud_utils import (
|
|
97
|
+
_unroll_resources_for_aws_list_call,
|
|
98
|
+
CloudEventProducer,
|
|
99
|
+
CloudSetupError,
|
|
100
|
+
get_errored_resources_and_reasons,
|
|
101
|
+
modify_memorydb_parameter_group,
|
|
102
|
+
validate_aws_credentials,
|
|
103
|
+
verify_anyscale_access,
|
|
104
|
+
wait_for_gcp_lb_resource_termination,
|
|
105
|
+
)
|
|
106
|
+
from anyscale.utils.imports.gcp import (
|
|
107
|
+
try_import_gcp_managed_setup_utils,
|
|
108
|
+
try_import_gcp_utils,
|
|
109
|
+
try_import_gcp_verify_lib,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
ROLE_CREATION_RETRIES = 30
|
|
114
|
+
ROLE_CREATION_INTERVAL_SECONDS = 1
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
CLOUDFORMATION_TIMEOUT_SECONDS = int(getenv("CLOUDFORMATION_TIMEOUT_SECONDS", 300))
|
|
118
|
+
except ValueError:
|
|
119
|
+
raise Exception(
|
|
120
|
+
f"CLOUDFORMATION_TIMEOUT_SECONDS is set to {getenv('CLOUDFORMATION_TIMEOUT_SECONDS')}, which is not a valid integer."
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
GCP_DEPLOYMENT_MANAGER_TIMEOUT_SECONDS = int(
|
|
126
|
+
getenv("GCP_DEPLOYMENT_MANAGER_TIMEOUT_SECONDS", 600)
|
|
127
|
+
)
|
|
128
|
+
except ValueError:
|
|
129
|
+
raise Exception(
|
|
130
|
+
f"GCP_DEPLOYMENT_MANAGER_TIMEOUT_SECONDS is set to {getenv('GCP_DEPLOYMENT_MANAGER_TIMEOUT_SECONDS')}, which is not a valid integer."
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
IGNORE_CAPACITY_ERRORS = getenv("IGNORE_CAPACITY_ERRORS") is not None
|
|
134
|
+
|
|
135
|
+
# Constants forked from ray.autoscaler._private.aws.config
|
|
136
|
+
RAY = "ray-autoscaler"
|
|
137
|
+
DEFAULT_RAY_IAM_ROLE = RAY + "-v1"
|
|
138
|
+
|
|
139
|
+
# Only used in cloud edit.
|
|
140
|
+
BASE_ROLLBACK_COMMAND = "anyscale cloud edit --cloud-id={cloud_id}"
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class CloudController(BaseController):
|
|
144
|
+
def __init__(
|
|
145
|
+
self,
|
|
146
|
+
log: Optional[CloudSetupLogger] = None,
|
|
147
|
+
initialize_auth_api_client: bool = True,
|
|
148
|
+
cli_token: Optional[str] = None,
|
|
149
|
+
):
|
|
150
|
+
"""
|
|
151
|
+
:param log: The logger to use for this controller. If not provided, a new logger will be created.
|
|
152
|
+
:param initialize_auth_api_client: Whether to initialize the auth api client.
|
|
153
|
+
:param cli_token: The CLI token to use for this controller. If provided, the CLI token will be used instead
|
|
154
|
+
of the one in the config file or the one in the environment variable. This is for setting up AIOA clouds only.
|
|
155
|
+
"""
|
|
156
|
+
if log is None:
|
|
157
|
+
log = CloudSetupLogger()
|
|
158
|
+
|
|
159
|
+
super().__init__(
|
|
160
|
+
initialize_auth_api_client=initialize_auth_api_client, cli_token=cli_token,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
self.log = log
|
|
164
|
+
self.log.open_block("Output")
|
|
165
|
+
if self.initialize_auth_api_client:
|
|
166
|
+
self.cloud_event_producer = CloudEventProducer(
|
|
167
|
+
cli_version=anyscale_version, api_client=self.api_client
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
def list_clouds(
|
|
171
|
+
self, cloud_name: Optional[str], cloud_id: Optional[str], max_items: int
|
|
172
|
+
) -> str:
|
|
173
|
+
if cloud_id is not None:
|
|
174
|
+
clouds = [
|
|
175
|
+
self.api_client.get_cloud_api_v2_clouds_cloud_id_get(cloud_id).result
|
|
176
|
+
]
|
|
177
|
+
elif cloud_name is not None:
|
|
178
|
+
clouds = [
|
|
179
|
+
self.api_client.find_cloud_by_name_api_v2_clouds_find_by_name_post(
|
|
180
|
+
{"name": cloud_name}
|
|
181
|
+
).result
|
|
182
|
+
]
|
|
183
|
+
else:
|
|
184
|
+
clouds = self.api_client.list_clouds_api_v2_clouds_get().results
|
|
185
|
+
|
|
186
|
+
clouds_output = clouds[:max_items]
|
|
187
|
+
output = clouds_formatter.format_clouds_output(
|
|
188
|
+
clouds=clouds_output, json_format=False
|
|
189
|
+
)
|
|
190
|
+
return str(output)
|
|
191
|
+
|
|
192
|
+
def _get_anyscale_cross_account_iam_policies(
|
|
193
|
+
self, cloud_id: str, _use_strict_iam_permissions: bool,
|
|
194
|
+
) -> List[Dict[str, str]]:
|
|
195
|
+
iam_policy_parameters = get_anyscale_cross_account_iam_policies()
|
|
196
|
+
if _use_strict_iam_permissions:
|
|
197
|
+
anyscale_iam_permissions_ec2 = get_anyscale_iam_permissions_ec2_restricted(
|
|
198
|
+
cloud_id
|
|
199
|
+
)
|
|
200
|
+
for parameter in iam_policy_parameters:
|
|
201
|
+
if (
|
|
202
|
+
parameter["ParameterKey"]
|
|
203
|
+
== "AnyscaleCrossAccountIAMPolicySteadyState"
|
|
204
|
+
):
|
|
205
|
+
parameter["ParameterValue"] = json.dumps(
|
|
206
|
+
anyscale_iam_permissions_ec2
|
|
207
|
+
)
|
|
208
|
+
break
|
|
209
|
+
return iam_policy_parameters
|
|
210
|
+
|
|
211
|
+
def create_cloudformation_stack( # noqa: PLR0913
|
|
212
|
+
self,
|
|
213
|
+
region: str,
|
|
214
|
+
cloud_id: str,
|
|
215
|
+
anyscale_iam_role_name: str,
|
|
216
|
+
cluster_node_iam_role_name: str,
|
|
217
|
+
enable_head_node_fault_tolerance: bool,
|
|
218
|
+
anyscale_aws_account: str,
|
|
219
|
+
_use_strict_iam_permissions: bool = False, # This should only be used in testing.
|
|
220
|
+
boto3_session: Optional[boto3.Session] = None,
|
|
221
|
+
is_anyscale_hosted: bool = False,
|
|
222
|
+
anyscale_hosted_network_info: Optional[Dict[str, Any]] = None,
|
|
223
|
+
):
|
|
224
|
+
if boto3_session is None:
|
|
225
|
+
boto3_session = boto3.Session(region_name=region)
|
|
226
|
+
|
|
227
|
+
cfn_client = boto3_session.client("cloudformation", region_name=region)
|
|
228
|
+
cfn_stack_name = cloud_id.replace("_", "-").lower()
|
|
229
|
+
|
|
230
|
+
cfn_template_body = prepare_cloudformation_template(
|
|
231
|
+
region,
|
|
232
|
+
cfn_stack_name,
|
|
233
|
+
cloud_id,
|
|
234
|
+
enable_head_node_fault_tolerance,
|
|
235
|
+
boto3_session,
|
|
236
|
+
is_anyscale_hosted=is_anyscale_hosted,
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
parameters = [
|
|
240
|
+
{"ParameterKey": "EnvironmentName", "ParameterValue": ANYSCALE_ENV},
|
|
241
|
+
{"ParameterKey": "AnyscaleCLIVersion", "ParameterValue": anyscale_version},
|
|
242
|
+
{"ParameterKey": "CloudID", "ParameterValue": cloud_id},
|
|
243
|
+
{
|
|
244
|
+
"ParameterKey": "AnyscaleAWSAccountID",
|
|
245
|
+
"ParameterValue": anyscale_aws_account,
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
"ParameterKey": "ClusterNodeIAMRoleName",
|
|
249
|
+
"ParameterValue": cluster_node_iam_role_name,
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
"ParameterKey": "MemoryDBRedisPort",
|
|
253
|
+
"ParameterValue": MEMORYDB_REDIS_PORT,
|
|
254
|
+
},
|
|
255
|
+
]
|
|
256
|
+
if not is_anyscale_hosted:
|
|
257
|
+
parameters.append(
|
|
258
|
+
{
|
|
259
|
+
"ParameterKey": "AnyscaleCrossAccountIAMRoleName",
|
|
260
|
+
"ParameterValue": anyscale_iam_role_name,
|
|
261
|
+
}
|
|
262
|
+
)
|
|
263
|
+
cross_account_iam_policies = self._get_anyscale_cross_account_iam_policies(
|
|
264
|
+
cloud_id, _use_strict_iam_permissions
|
|
265
|
+
)
|
|
266
|
+
for parameter in cross_account_iam_policies:
|
|
267
|
+
parameters.append(parameter)
|
|
268
|
+
|
|
269
|
+
tags: MutableSequence[Any] = []
|
|
270
|
+
if is_anyscale_hosted:
|
|
271
|
+
# Add is-anyscale-hosted tag to the cloudformation stack which
|
|
272
|
+
# the reconcile process will use to determine if the use the
|
|
273
|
+
# shared resources.
|
|
274
|
+
tags.append({"Key": "anyscale:cloud-id", "Value": cloud_id},)
|
|
275
|
+
tags.append({"Key": "anyscale:is-anyscale-hosted", "Value": "true"},)
|
|
276
|
+
|
|
277
|
+
# VPC ID are needed for creating security groups and subnets
|
|
278
|
+
# are needed for mount targets in shared VPC clouds.
|
|
279
|
+
if anyscale_hosted_network_info is not None:
|
|
280
|
+
parameters.append(
|
|
281
|
+
{
|
|
282
|
+
"ParameterKey": "VpcId",
|
|
283
|
+
"ParameterValue": anyscale_hosted_network_info["vpc_id"],
|
|
284
|
+
},
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
cfn_client.create_stack(
|
|
288
|
+
StackName=cfn_stack_name,
|
|
289
|
+
TemplateBody=cfn_template_body,
|
|
290
|
+
Parameters=parameters, # type: ignore
|
|
291
|
+
Capabilities=["CAPABILITY_NAMED_IAM", "CAPABILITY_AUTO_EXPAND"],
|
|
292
|
+
Tags=tags,
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
def run_cloudformation( # noqa: PLR0913
|
|
296
|
+
self,
|
|
297
|
+
region: str,
|
|
298
|
+
cloud_id: str,
|
|
299
|
+
anyscale_iam_role_name: str,
|
|
300
|
+
cluster_node_iam_role_name: str,
|
|
301
|
+
enable_head_node_fault_tolerance: bool,
|
|
302
|
+
anyscale_aws_account: str,
|
|
303
|
+
_use_strict_iam_permissions: bool = False, # This should only be used in testing.
|
|
304
|
+
boto3_session: Optional[boto3.Session] = None,
|
|
305
|
+
) -> Dict[str, Any]:
|
|
306
|
+
"""
|
|
307
|
+
Run cloudformation to create the AWS resources for a cloud.
|
|
308
|
+
|
|
309
|
+
When enable_head_node_fault_tolerance is set to True, a memorydb cluster will be created.
|
|
310
|
+
"""
|
|
311
|
+
if boto3_session is None:
|
|
312
|
+
boto3_session = boto3.Session(region_name=region)
|
|
313
|
+
|
|
314
|
+
cfn_client = boto3_session.client("cloudformation", region_name=region)
|
|
315
|
+
cfn_stack_name = cloud_id.replace("_", "-").lower()
|
|
316
|
+
|
|
317
|
+
cfn_template_body = prepare_cloudformation_template(
|
|
318
|
+
region,
|
|
319
|
+
cfn_stack_name,
|
|
320
|
+
cloud_id,
|
|
321
|
+
enable_head_node_fault_tolerance,
|
|
322
|
+
boto3_session,
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
cross_account_iam_policies = self._get_anyscale_cross_account_iam_policies(
|
|
326
|
+
cloud_id, _use_strict_iam_permissions
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
parameters = [
|
|
330
|
+
{"ParameterKey": "EnvironmentName", "ParameterValue": ANYSCALE_ENV},
|
|
331
|
+
{"ParameterKey": "AnyscaleCLIVersion", "ParameterValue": anyscale_version},
|
|
332
|
+
{"ParameterKey": "CloudID", "ParameterValue": cloud_id},
|
|
333
|
+
{
|
|
334
|
+
"ParameterKey": "AnyscaleAWSAccountID",
|
|
335
|
+
"ParameterValue": anyscale_aws_account,
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
"ParameterKey": "AnyscaleCrossAccountIAMRoleName",
|
|
339
|
+
"ParameterValue": anyscale_iam_role_name,
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
"ParameterKey": "ClusterNodeIAMRoleName",
|
|
343
|
+
"ParameterValue": cluster_node_iam_role_name,
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
"ParameterKey": "MemoryDBRedisPort",
|
|
347
|
+
"ParameterValue": MEMORYDB_REDIS_PORT,
|
|
348
|
+
},
|
|
349
|
+
]
|
|
350
|
+
for parameter in cross_account_iam_policies:
|
|
351
|
+
parameters.append(parameter)
|
|
352
|
+
|
|
353
|
+
self.log.debug("cloudformation body:")
|
|
354
|
+
self.log.debug(cfn_template_body)
|
|
355
|
+
|
|
356
|
+
cfn_client.create_stack(
|
|
357
|
+
StackName=cfn_stack_name,
|
|
358
|
+
TemplateBody=cfn_template_body,
|
|
359
|
+
Parameters=parameters, # type: ignore
|
|
360
|
+
Capabilities=["CAPABILITY_NAMED_IAM", "CAPABILITY_AUTO_EXPAND"],
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
stacks = cfn_client.describe_stacks(StackName=cfn_stack_name)
|
|
364
|
+
cfn_stack = stacks["Stacks"][0]
|
|
365
|
+
cfn_stack_url = f"https://{region}.console.aws.amazon.com/cloudformation/home?region={region}#/stacks/stackinfo?stackId={cfn_stack['StackId']}"
|
|
366
|
+
self.log.info(f"\nTrack progress of cloudformation at {cfn_stack_url}")
|
|
367
|
+
with self.log.spinner("Creating cloud resources through cloudformation..."):
|
|
368
|
+
start_time = time.time()
|
|
369
|
+
end_time = (
|
|
370
|
+
start_time + CLOUDFORMATION_TIMEOUT_SECONDS_LONG
|
|
371
|
+
if enable_head_node_fault_tolerance
|
|
372
|
+
else start_time + CLOUDFORMATION_TIMEOUT_SECONDS
|
|
373
|
+
)
|
|
374
|
+
while time.time() < end_time:
|
|
375
|
+
stacks = cfn_client.describe_stacks(StackName=cfn_stack_name)
|
|
376
|
+
cfn_stack = stacks["Stacks"][0]
|
|
377
|
+
if cfn_stack["StackStatus"] in (
|
|
378
|
+
"CREATE_FAILED",
|
|
379
|
+
"ROLLBACK_COMPLETE",
|
|
380
|
+
"ROLLBACK_IN_PROGRESS",
|
|
381
|
+
):
|
|
382
|
+
bucket_name = f"anyscale-{ANYSCALE_ENV}-data-{cfn_stack_name}"
|
|
383
|
+
try:
|
|
384
|
+
boto3_session.client("s3", region_name=region).delete_bucket(
|
|
385
|
+
Bucket=bucket_name
|
|
386
|
+
)
|
|
387
|
+
self.log.info(f"Successfully deleted {bucket_name}")
|
|
388
|
+
except Exception as e: # noqa: BLE001
|
|
389
|
+
if not (
|
|
390
|
+
isinstance(e, ClientError)
|
|
391
|
+
and e.response["Error"]["Code"] == "NoSuchBucket"
|
|
392
|
+
):
|
|
393
|
+
self.log.error(
|
|
394
|
+
f"Unable to delete the S3 bucket created by the cloud formation stack, please manually delete {bucket_name}"
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
# Describe events to get error reason
|
|
398
|
+
error_details = get_errored_resources_and_reasons(
|
|
399
|
+
cfn_client, cfn_stack_name
|
|
400
|
+
)
|
|
401
|
+
self.log.log_resource_error(
|
|
402
|
+
CloudAnalyticsEventCloudResource.AWS_CLOUDFORMATION,
|
|
403
|
+
f"Cloudformation stack failed to deploy. Detailed errors: {error_details}",
|
|
404
|
+
)
|
|
405
|
+
# Provide link to cloudformation
|
|
406
|
+
raise ClickException(
|
|
407
|
+
f"Failed to set up cloud resources. Please check your cloudformation stack for errors and to ensure that all resources created in this cloudformation stack were deleted: {cfn_stack_url}"
|
|
408
|
+
)
|
|
409
|
+
if cfn_stack["StackStatus"] == "CREATE_COMPLETE":
|
|
410
|
+
if enable_head_node_fault_tolerance:
|
|
411
|
+
modify_memorydb_parameter_group(
|
|
412
|
+
cfn_stack_name, region, boto3_session
|
|
413
|
+
)
|
|
414
|
+
self.log.info(
|
|
415
|
+
f"Cloudformation stack {cfn_stack['StackId']} Completed"
|
|
416
|
+
)
|
|
417
|
+
break
|
|
418
|
+
|
|
419
|
+
time.sleep(1)
|
|
420
|
+
|
|
421
|
+
if time.time() > end_time:
|
|
422
|
+
self.log.log_resource_error(
|
|
423
|
+
CloudAnalyticsEventCloudResource.AWS_CLOUDFORMATION,
|
|
424
|
+
"Cloudformation timed out.",
|
|
425
|
+
)
|
|
426
|
+
raise ClickException(
|
|
427
|
+
f"Timed out creating AWS resources. Please check your cloudformation stack for errors. {cfn_stack['StackId']}"
|
|
428
|
+
)
|
|
429
|
+
return cfn_stack # type: ignore
|
|
430
|
+
|
|
431
|
+
def run_deployment_manager( # noqa: PLR0913
|
|
432
|
+
self,
|
|
433
|
+
factory: Any,
|
|
434
|
+
deployment_name: str,
|
|
435
|
+
cloud_id: str,
|
|
436
|
+
project_id: str,
|
|
437
|
+
region: str,
|
|
438
|
+
anyscale_access_service_account: str,
|
|
439
|
+
workload_identity_pool_name: str,
|
|
440
|
+
anyscale_aws_account: str,
|
|
441
|
+
organization_id: str,
|
|
442
|
+
enable_head_node_fault_tolerance: bool,
|
|
443
|
+
):
|
|
444
|
+
setup_utils = try_import_gcp_managed_setup_utils()
|
|
445
|
+
|
|
446
|
+
anyscale_access_service_account_name = anyscale_access_service_account.split(
|
|
447
|
+
"@"
|
|
448
|
+
)[0]
|
|
449
|
+
deployment_config = setup_utils.generate_deployment_manager_config(
|
|
450
|
+
region,
|
|
451
|
+
project_id,
|
|
452
|
+
cloud_id,
|
|
453
|
+
anyscale_access_service_account_name,
|
|
454
|
+
workload_identity_pool_name,
|
|
455
|
+
anyscale_aws_account,
|
|
456
|
+
organization_id,
|
|
457
|
+
enable_head_node_fault_tolerance,
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
self.log.debug("GCP Deployment Manager resource config:")
|
|
461
|
+
self.log.debug(deployment_config)
|
|
462
|
+
|
|
463
|
+
deployment = {
|
|
464
|
+
"name": deployment_name,
|
|
465
|
+
"target": {"config": {"content": deployment_config,},},
|
|
466
|
+
"labels": [{"key": "anyscale-cloud-id", "value": cloud_id},],
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
deployment_client = factory.build("deploymentmanager", "v2")
|
|
470
|
+
response = (
|
|
471
|
+
deployment_client.deployments()
|
|
472
|
+
.insert(project=project_id, body=deployment)
|
|
473
|
+
.execute()
|
|
474
|
+
)
|
|
475
|
+
deployment_url = f"https://console.cloud.google.com/dm/deployments/details/{deployment_name}?project={project_id}"
|
|
476
|
+
|
|
477
|
+
timeout_seconds = (
|
|
478
|
+
GCP_DEPLOYMENT_MANAGER_TIMEOUT_SECONDS_LONG
|
|
479
|
+
if enable_head_node_fault_tolerance
|
|
480
|
+
else GCP_DEPLOYMENT_MANAGER_TIMEOUT_SECONDS
|
|
481
|
+
)
|
|
482
|
+
self.log.info(
|
|
483
|
+
f"Note that it may take up to {int(timeout_seconds / 60)} minutes to create resources on GCP."
|
|
484
|
+
)
|
|
485
|
+
self.log.info(f"Track progress of Deployment Manager at {deployment_url}")
|
|
486
|
+
with self.log.spinner("Creating cloud resources through Deployment Manager..."):
|
|
487
|
+
start_time = time.time()
|
|
488
|
+
end_time = start_time + timeout_seconds
|
|
489
|
+
while time.time() < end_time:
|
|
490
|
+
current_operation = (
|
|
491
|
+
deployment_client.operations()
|
|
492
|
+
.get(operation=response["name"], project=project_id)
|
|
493
|
+
.execute()
|
|
494
|
+
)
|
|
495
|
+
if current_operation.get("status", None) == "DONE":
|
|
496
|
+
if "error" in current_operation:
|
|
497
|
+
self.log.error(f"Error: {current_operation['error']}")
|
|
498
|
+
self.log.log_resource_error(
|
|
499
|
+
CloudAnalyticsEventCloudResource.GCP_DEPLOYMENT,
|
|
500
|
+
current_operation["error"],
|
|
501
|
+
)
|
|
502
|
+
raise ClickException(
|
|
503
|
+
f"Failed to set up cloud resources. Please check your Deployment Manager for errors and delete all resources created in this deployment: {deployment_url}"
|
|
504
|
+
)
|
|
505
|
+
# happy path
|
|
506
|
+
self.log.info("Deployment succeeded.")
|
|
507
|
+
return True
|
|
508
|
+
time.sleep(1)
|
|
509
|
+
# timeout
|
|
510
|
+
self.log.log_resource_error(
|
|
511
|
+
CloudAnalyticsEventCloudResource.GCP_DEPLOYMENT, "Deployment timed out."
|
|
512
|
+
)
|
|
513
|
+
raise ClickException(
|
|
514
|
+
f"Timed out creating GCP resources. Please check your deployment for errors and delete all resources created in this deployment: {deployment_url}"
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
def get_create_cloud_resource_from_cfn_stack(
|
|
518
|
+
self,
|
|
519
|
+
cfn_stack: Dict[str, Any],
|
|
520
|
+
enable_head_node_fault_tolerance: bool,
|
|
521
|
+
anyscale_hosted_network_info: Optional[Dict[str, Any]] = None,
|
|
522
|
+
anyscale_control_plane_role_arn: str = "",
|
|
523
|
+
) -> CreateCloudResource:
|
|
524
|
+
if "Outputs" not in cfn_stack:
|
|
525
|
+
raise ClickException(
|
|
526
|
+
f"Timed out setting up cloud resources. Please check your cloudformation stack for errors. {cfn_stack['StackId']}"
|
|
527
|
+
)
|
|
528
|
+
|
|
529
|
+
cfn_resources = {}
|
|
530
|
+
for resource in cfn_stack["Outputs"]:
|
|
531
|
+
resource_type = resource["OutputKey"]
|
|
532
|
+
resource_value = resource["OutputValue"]
|
|
533
|
+
assert (
|
|
534
|
+
resource_value is not None
|
|
535
|
+
), f"{resource_type} is not created properly. Please delete the cloud and try creating agian."
|
|
536
|
+
cfn_resources[resource_type] = resource_value
|
|
537
|
+
|
|
538
|
+
aws_subnets_with_availability_zones = (
|
|
539
|
+
json.loads(cfn_resources["SubnetsWithAvailabilityZones"])
|
|
540
|
+
if not anyscale_hosted_network_info
|
|
541
|
+
else anyscale_hosted_network_info["subnet_ids"]
|
|
542
|
+
)
|
|
543
|
+
aws_vpc_id = (
|
|
544
|
+
cfn_resources["VPC"]
|
|
545
|
+
if not anyscale_hosted_network_info
|
|
546
|
+
else anyscale_hosted_network_info["vpc_id"]
|
|
547
|
+
)
|
|
548
|
+
aws_security_groups = [cfn_resources["AnyscaleSecurityGroup"]]
|
|
549
|
+
|
|
550
|
+
aws_efs_id = cfn_resources.get("EFS", None)
|
|
551
|
+
aws_efs_mount_target_ip = cfn_resources.get("EFSMountTargetIP", None)
|
|
552
|
+
anyscale_iam_role_arn = cfn_resources.get(
|
|
553
|
+
"AnyscaleIAMRole", anyscale_control_plane_role_arn
|
|
554
|
+
)
|
|
555
|
+
cluster_node_iam_role_arn = cfn_resources["NodeIamRole"]
|
|
556
|
+
|
|
557
|
+
if enable_head_node_fault_tolerance:
|
|
558
|
+
memorydb = json.loads(cfn_resources["MemoryDB"])
|
|
559
|
+
memorydb_cluster_config = AWSMemoryDBClusterConfig(
|
|
560
|
+
id=memorydb["arn"],
|
|
561
|
+
endpoint=f'{REDIS_TLS_ADDRESS_PREFIX}{memorydb["ClusterEndpointAddress"]}:{MEMORYDB_REDIS_PORT}',
|
|
562
|
+
)
|
|
563
|
+
else:
|
|
564
|
+
memorydb_cluster_config = None
|
|
565
|
+
|
|
566
|
+
bucket_name = cfn_resources.get("S3Bucket", "")
|
|
567
|
+
if bucket_name.startswith(S3_ARN_PREFIX):
|
|
568
|
+
bucket_name = bucket_name[len(S3_ARN_PREFIX) :]
|
|
569
|
+
|
|
570
|
+
return CreateCloudResource(
|
|
571
|
+
aws_vpc_id=aws_vpc_id,
|
|
572
|
+
aws_subnet_ids_with_availability_zones=[
|
|
573
|
+
SubnetIdWithAvailabilityZoneAWS(
|
|
574
|
+
subnet_id=subnet_with_az["subnet_id"],
|
|
575
|
+
availability_zone=subnet_with_az["availability_zone"],
|
|
576
|
+
)
|
|
577
|
+
for subnet_with_az in aws_subnets_with_availability_zones
|
|
578
|
+
],
|
|
579
|
+
aws_iam_role_arns=[anyscale_iam_role_arn, cluster_node_iam_role_arn],
|
|
580
|
+
aws_security_groups=aws_security_groups,
|
|
581
|
+
aws_s3_id=bucket_name,
|
|
582
|
+
aws_efs_id=aws_efs_id,
|
|
583
|
+
aws_efs_mount_target_ip=aws_efs_mount_target_ip,
|
|
584
|
+
aws_cloudformation_stack_id=cfn_stack["StackId"],
|
|
585
|
+
memorydb_cluster_config=memorydb_cluster_config,
|
|
586
|
+
cloud_storage_bucket_name=S3_STORAGE_PREFIX + bucket_name,
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
def update_cloud_with_resources(
|
|
590
|
+
self,
|
|
591
|
+
cfn_stack: Dict[str, Any],
|
|
592
|
+
cloud_id: str,
|
|
593
|
+
enable_head_node_fault_tolerance: bool,
|
|
594
|
+
):
|
|
595
|
+
create_cloud_resource = self.get_create_cloud_resource_from_cfn_stack(
|
|
596
|
+
cfn_stack, enable_head_node_fault_tolerance
|
|
597
|
+
)
|
|
598
|
+
with self.log.spinner("Updating Anyscale cloud with cloud resources..."):
|
|
599
|
+
self.api_client.update_cloud_with_cloud_resource_api_v2_clouds_with_cloud_resource_router_cloud_id_put(
|
|
600
|
+
cloud_id=cloud_id,
|
|
601
|
+
update_cloud_with_cloud_resource=UpdateCloudWithCloudResource(
|
|
602
|
+
cloud_resource_to_update=create_cloud_resource
|
|
603
|
+
),
|
|
604
|
+
)
|
|
605
|
+
|
|
606
|
+
def update_cloud_with_resources_gcp(
|
|
607
|
+
self,
|
|
608
|
+
factory: Any,
|
|
609
|
+
deployment_name: str,
|
|
610
|
+
cloud_id: str,
|
|
611
|
+
project_id: str,
|
|
612
|
+
anyscale_access_service_account: str,
|
|
613
|
+
):
|
|
614
|
+
setup_utils = try_import_gcp_managed_setup_utils()
|
|
615
|
+
gcp_utils = try_import_gcp_utils()
|
|
616
|
+
anyscale_access_service_account_name = anyscale_access_service_account.split(
|
|
617
|
+
"@"
|
|
618
|
+
)[0]
|
|
619
|
+
|
|
620
|
+
cloud_resources = setup_utils.get_deployment_resources(
|
|
621
|
+
factory, deployment_name, project_id, anyscale_access_service_account_name
|
|
622
|
+
)
|
|
623
|
+
gcp_vpc_id = cloud_resources["compute.v1.network"]
|
|
624
|
+
gcp_subnet_ids = [cloud_resources["compute.v1.subnetwork"]]
|
|
625
|
+
gcp_cluster_node_service_account_email = f'{cloud_resources["iam.v1.serviceAccount"]}@{anyscale_access_service_account.split("@")[-1]}'
|
|
626
|
+
gcp_anyscale_iam_service_account_email = anyscale_access_service_account
|
|
627
|
+
gcp_firewall_policy = cloud_resources[
|
|
628
|
+
"gcp-types/compute-v1:networkFirewallPolicies"
|
|
629
|
+
]
|
|
630
|
+
gcp_firewall_policy_ids = [gcp_firewall_policy]
|
|
631
|
+
gcp_cloud_storage_bucket_id = cloud_resources["storage.v1.bucket"]
|
|
632
|
+
gcp_deployment_manager_id = deployment_name
|
|
633
|
+
|
|
634
|
+
gcp_filestore_config = gcp_utils.get_gcp_filestore_config(
|
|
635
|
+
factory,
|
|
636
|
+
project_id,
|
|
637
|
+
gcp_vpc_id,
|
|
638
|
+
cloud_resources["filestore_location"],
|
|
639
|
+
cloud_resources["filestore_instance"],
|
|
640
|
+
self.log,
|
|
641
|
+
)
|
|
642
|
+
memorystore_instance_config = gcp_utils.get_gcp_memorystore_config(
|
|
643
|
+
factory, cloud_resources.get("memorystore_name"),
|
|
644
|
+
)
|
|
645
|
+
try:
|
|
646
|
+
setup_utils.configure_firewall_policy(
|
|
647
|
+
factory, gcp_vpc_id, project_id, gcp_firewall_policy
|
|
648
|
+
)
|
|
649
|
+
|
|
650
|
+
create_cloud_resource_gcp = CreateCloudResourceGCP(
|
|
651
|
+
gcp_vpc_id=gcp_vpc_id,
|
|
652
|
+
gcp_subnet_ids=gcp_subnet_ids,
|
|
653
|
+
gcp_cluster_node_service_account_email=gcp_cluster_node_service_account_email,
|
|
654
|
+
gcp_anyscale_iam_service_account_email=gcp_anyscale_iam_service_account_email,
|
|
655
|
+
gcp_filestore_config=gcp_filestore_config,
|
|
656
|
+
gcp_firewall_policy_ids=gcp_firewall_policy_ids,
|
|
657
|
+
gcp_cloud_storage_bucket_id=gcp_cloud_storage_bucket_id,
|
|
658
|
+
gcp_deployment_manager_id=gcp_deployment_manager_id,
|
|
659
|
+
memorystore_instance_config=memorystore_instance_config,
|
|
660
|
+
cloud_storage_bucket_name=GCS_STORAGE_PREFIX
|
|
661
|
+
+ gcp_cloud_storage_bucket_id,
|
|
662
|
+
)
|
|
663
|
+
self.api_client.update_cloud_with_cloud_resource_api_v2_clouds_with_cloud_resource_gcp_router_cloud_id_put(
|
|
664
|
+
cloud_id=cloud_id,
|
|
665
|
+
update_cloud_with_cloud_resource_gcp=UpdateCloudWithCloudResourceGCP(
|
|
666
|
+
cloud_resource_to_update=create_cloud_resource_gcp,
|
|
667
|
+
),
|
|
668
|
+
)
|
|
669
|
+
except Exception as e: # noqa: BLE001
|
|
670
|
+
self.log.error(str(e))
|
|
671
|
+
for firewall_policy_name in gcp_firewall_policy_ids:
|
|
672
|
+
setup_utils.remove_firewall_policy_associations(
|
|
673
|
+
factory, project_id, firewall_policy_name
|
|
674
|
+
)
|
|
675
|
+
raise ClickException(
|
|
676
|
+
f"Error occurred when updating resources for the cloud {cloud_id}."
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
def prepare_for_managed_cloud_setup(
|
|
680
|
+
self,
|
|
681
|
+
region: str,
|
|
682
|
+
cloud_name: str,
|
|
683
|
+
cluster_management_stack_version: ClusterManagementStackVersions,
|
|
684
|
+
auto_add_user: bool,
|
|
685
|
+
is_aioa: bool = False,
|
|
686
|
+
boto3_session: Optional[boto3.Session] = None,
|
|
687
|
+
) -> Tuple[str, str]:
|
|
688
|
+
regions_available = get_available_regions(boto3_session)
|
|
689
|
+
if region not in regions_available:
|
|
690
|
+
self.cloud_event_producer.produce(
|
|
691
|
+
CloudAnalyticsEventName.PREPROCESS_COMPLETE,
|
|
692
|
+
succeeded=False,
|
|
693
|
+
internal_error=f"Region '{region}' is not available.",
|
|
694
|
+
)
|
|
695
|
+
raise ClickException(
|
|
696
|
+
f"Region '{region}' is not available. Regions available are: "
|
|
697
|
+
f"{', '.join(map(repr, regions_available))}"
|
|
698
|
+
)
|
|
699
|
+
|
|
700
|
+
for _ in range(5):
|
|
701
|
+
anyscale_iam_role_name = "{}-{}".format(
|
|
702
|
+
ANYSCALE_IAM_ROLE_NAME, secrets.token_hex(4)
|
|
703
|
+
)
|
|
704
|
+
try:
|
|
705
|
+
role = _get_role(anyscale_iam_role_name, region, boto3_session)
|
|
706
|
+
except Exception as e: # noqa: BLE001
|
|
707
|
+
if isinstance(e, NoCredentialsError):
|
|
708
|
+
# Rewrite the error message to be more user friendly
|
|
709
|
+
self.cloud_event_producer.produce(
|
|
710
|
+
CloudAnalyticsEventName.PREPROCESS_COMPLETE,
|
|
711
|
+
succeeded=False,
|
|
712
|
+
internal_error="Unable to locate AWS credentials.",
|
|
713
|
+
)
|
|
714
|
+
raise ClickException(
|
|
715
|
+
"Unable to locate AWS credentials. Please make sure you have AWS credentials configured."
|
|
716
|
+
)
|
|
717
|
+
self.cloud_event_producer.produce(
|
|
718
|
+
CloudAnalyticsEventName.PREPROCESS_COMPLETE,
|
|
719
|
+
succeeded=False,
|
|
720
|
+
internal_error=str(e),
|
|
721
|
+
)
|
|
722
|
+
raise ClickException(f"Failed to get IAM role: {e}")
|
|
723
|
+
if role is None:
|
|
724
|
+
break
|
|
725
|
+
else:
|
|
726
|
+
self.cloud_event_producer.produce(
|
|
727
|
+
CloudAnalyticsEventName.PREPROCESS_COMPLETE,
|
|
728
|
+
succeeded=False,
|
|
729
|
+
internal_error="Unable to find an available AWS IAM Role name",
|
|
730
|
+
)
|
|
731
|
+
raise ClickException(
|
|
732
|
+
"We weren't able to connect your account with the Anyscale because we weren't able to find an available IAM Role name in your account. Please reach out to support or your SA for assistance."
|
|
733
|
+
)
|
|
734
|
+
|
|
735
|
+
user_aws_account_id = get_user_env_aws_account(region)
|
|
736
|
+
self.cloud_event_producer.produce(
|
|
737
|
+
CloudAnalyticsEventName.PREPROCESS_COMPLETE, succeeded=True,
|
|
738
|
+
)
|
|
739
|
+
try:
|
|
740
|
+
created_cloud = self.api_client.create_cloud_api_v2_clouds_post(
|
|
741
|
+
write_cloud=WriteCloud(
|
|
742
|
+
provider="AWS",
|
|
743
|
+
region=region,
|
|
744
|
+
credentials=AwsRoleArn.from_role_name(
|
|
745
|
+
user_aws_account_id, anyscale_iam_role_name
|
|
746
|
+
).to_string(),
|
|
747
|
+
name=cloud_name,
|
|
748
|
+
is_bring_your_own_resource=False,
|
|
749
|
+
cluster_management_stack_version=cluster_management_stack_version,
|
|
750
|
+
auto_add_user=auto_add_user,
|
|
751
|
+
is_aioa=is_aioa,
|
|
752
|
+
)
|
|
753
|
+
).result
|
|
754
|
+
self.cloud_event_producer.set_cloud_id(created_cloud.id)
|
|
755
|
+
self.cloud_event_producer.produce(
|
|
756
|
+
CloudAnalyticsEventName.CLOUD_RECORD_INSERTED, succeeded=True,
|
|
757
|
+
)
|
|
758
|
+
except ClickException as e:
|
|
759
|
+
self.cloud_event_producer.produce(
|
|
760
|
+
CloudAnalyticsEventName.CLOUD_RECORD_INSERTED,
|
|
761
|
+
succeeded=False,
|
|
762
|
+
internal_error=str(e),
|
|
763
|
+
)
|
|
764
|
+
raise
|
|
765
|
+
|
|
766
|
+
return anyscale_iam_role_name, created_cloud.id
|
|
767
|
+
|
|
768
|
+
def prepare_for_managed_cloud_setup_gcp( # noqa: PLR0913
|
|
769
|
+
self,
|
|
770
|
+
project_id: str,
|
|
771
|
+
region: str,
|
|
772
|
+
cloud_name: str,
|
|
773
|
+
factory: Any,
|
|
774
|
+
cluster_management_stack_version: ClusterManagementStackVersions,
|
|
775
|
+
auto_add_user: bool,
|
|
776
|
+
is_aioa: bool = False,
|
|
777
|
+
) -> Tuple[str, str, str]:
|
|
778
|
+
setup_utils = try_import_gcp_managed_setup_utils()
|
|
779
|
+
|
|
780
|
+
# choose an service account name and create a provider pool
|
|
781
|
+
for _ in range(5):
|
|
782
|
+
token = secrets.token_hex(4)
|
|
783
|
+
anyscale_access_service_account = (
|
|
784
|
+
f"anyscale-access-{token}@{project_id}.iam.gserviceaccount.com"
|
|
785
|
+
)
|
|
786
|
+
service_account = setup_utils.get_anyscale_gcp_access_service_acount(
|
|
787
|
+
factory, anyscale_access_service_account
|
|
788
|
+
)
|
|
789
|
+
if service_account is not None:
|
|
790
|
+
continue
|
|
791
|
+
pool_id = f"anyscale-provider-pool-{token}"
|
|
792
|
+
wordload_identity_pool = setup_utils.get_workload_identity_pool(
|
|
793
|
+
factory, project_id, pool_id
|
|
794
|
+
)
|
|
795
|
+
if wordload_identity_pool is None:
|
|
796
|
+
break
|
|
797
|
+
else:
|
|
798
|
+
self.cloud_event_producer.produce(
|
|
799
|
+
CloudAnalyticsEventName.PREPROCESS_COMPLETE,
|
|
800
|
+
succeeded=False,
|
|
801
|
+
internal_error="Unable to find an available GCP service account name and create a provider pool.",
|
|
802
|
+
)
|
|
803
|
+
raise ClickException(
|
|
804
|
+
"We weren't able to connect your account with the Anyscale because we weren't able to find an available service account name and create a provider pool in your GCP project. Please reach out to support or your SA for assistance."
|
|
805
|
+
)
|
|
806
|
+
|
|
807
|
+
# build credentials
|
|
808
|
+
project_number = setup_utils.get_project_number(factory, project_id)
|
|
809
|
+
provider_id = "anyscale-access"
|
|
810
|
+
pool_name = f"{project_number}/locations/global/workloadIdentityPools/{pool_id}"
|
|
811
|
+
provider_name = f"{pool_name}/providers/{provider_id}"
|
|
812
|
+
credentials = json.dumps(
|
|
813
|
+
{
|
|
814
|
+
"project_id": project_id,
|
|
815
|
+
"provider_id": provider_name,
|
|
816
|
+
"service_account_email": anyscale_access_service_account,
|
|
817
|
+
}
|
|
818
|
+
)
|
|
819
|
+
self.cloud_event_producer.produce(
|
|
820
|
+
CloudAnalyticsEventName.PREPROCESS_COMPLETE, succeeded=True,
|
|
821
|
+
)
|
|
822
|
+
|
|
823
|
+
# create a cloud
|
|
824
|
+
try:
|
|
825
|
+
created_cloud = self.api_client.create_cloud_api_v2_clouds_post(
|
|
826
|
+
write_cloud=WriteCloud(
|
|
827
|
+
provider="GCP",
|
|
828
|
+
region=region,
|
|
829
|
+
credentials=credentials,
|
|
830
|
+
name=cloud_name,
|
|
831
|
+
is_bring_your_own_resource=False,
|
|
832
|
+
cluster_management_stack_version=cluster_management_stack_version,
|
|
833
|
+
auto_add_user=auto_add_user,
|
|
834
|
+
is_aioa=is_aioa,
|
|
835
|
+
)
|
|
836
|
+
).result
|
|
837
|
+
self.cloud_event_producer.set_cloud_id(created_cloud.id)
|
|
838
|
+
self.cloud_event_producer.produce(
|
|
839
|
+
CloudAnalyticsEventName.CLOUD_RECORD_INSERTED, succeeded=True,
|
|
840
|
+
)
|
|
841
|
+
except ClickException as e:
|
|
842
|
+
self.cloud_event_producer.produce(
|
|
843
|
+
CloudAnalyticsEventName.CLOUD_RECORD_INSERTED,
|
|
844
|
+
succeeded=False,
|
|
845
|
+
internal_error=str(e),
|
|
846
|
+
)
|
|
847
|
+
raise
|
|
848
|
+
return anyscale_access_service_account, pool_name, created_cloud.id
|
|
849
|
+
|
|
850
|
+
def create_workload_identity_federation_provider(
|
|
851
|
+
self,
|
|
852
|
+
factory: Any,
|
|
853
|
+
project_id: str,
|
|
854
|
+
pool_id: str,
|
|
855
|
+
anyscale_access_service_account: str,
|
|
856
|
+
):
|
|
857
|
+
setup_utils = try_import_gcp_managed_setup_utils()
|
|
858
|
+
# create provider pool
|
|
859
|
+
pool_display_name = "Anyscale provider pool"
|
|
860
|
+
pool_description = f"Workload Identity Provider Pool for Anyscale access service account {anyscale_access_service_account}"
|
|
861
|
+
|
|
862
|
+
wordload_identity_pool = setup_utils.create_workload_identity_pool(
|
|
863
|
+
factory, project_id, pool_id, self.log, pool_display_name, pool_description,
|
|
864
|
+
)
|
|
865
|
+
try:
|
|
866
|
+
# create provider
|
|
867
|
+
provider_display_name = "Anyscale Access"
|
|
868
|
+
provider_id = "anyscale-access"
|
|
869
|
+
anyscale_aws_account = (
|
|
870
|
+
self.api_client.get_anyscale_aws_account_api_v2_clouds_anyscale_aws_account_get().result.anyscale_aws_account
|
|
871
|
+
)
|
|
872
|
+
organization_id = get_organization_id(self.api_client)
|
|
873
|
+
setup_utils.create_anyscale_aws_provider(
|
|
874
|
+
factory,
|
|
875
|
+
organization_id,
|
|
876
|
+
wordload_identity_pool,
|
|
877
|
+
provider_id,
|
|
878
|
+
anyscale_aws_account,
|
|
879
|
+
provider_display_name,
|
|
880
|
+
self.log,
|
|
881
|
+
)
|
|
882
|
+
except ClickException as e:
|
|
883
|
+
# delete provider pool if there's an exception
|
|
884
|
+
setup_utils.delete_workload_identity_pool(
|
|
885
|
+
factory, wordload_identity_pool, self.log
|
|
886
|
+
)
|
|
887
|
+
raise ClickException(
|
|
888
|
+
f"Error occurred when trying to set up workload identity federation: {e}"
|
|
889
|
+
)
|
|
890
|
+
|
|
891
|
+
def wait_for_cloud_to_be_active(
|
|
892
|
+
self, cloud_id: str, cloud_provider: CloudProviders
|
|
893
|
+
) -> None:
|
|
894
|
+
"""
|
|
895
|
+
Waits for the cloud to be active
|
|
896
|
+
"""
|
|
897
|
+
with self.log.spinner("Setting up resources on Anyscale for your new cloud..."):
|
|
898
|
+
try:
|
|
899
|
+
# The ingress for cloud admin zone may not be ready yet, so we need to wait for it to be active.
|
|
900
|
+
# We use the cloud functional verification controller to create a cluster compute config to wait
|
|
901
|
+
# for the cloud admin zone to be active.
|
|
902
|
+
if self.initialize_auth_api_client:
|
|
903
|
+
CloudFunctionalVerificationController(
|
|
904
|
+
self.cloud_event_producer, self.log
|
|
905
|
+
).get_or_create_cluster_compute(cloud_id, cloud_provider)
|
|
906
|
+
except Exception: # noqa: BLE001
|
|
907
|
+
self.log.error(
|
|
908
|
+
"Timed out waiting for cloud admin zone to be active. Your cloud may not be set up properly. Please reach out to Anyscale support for assistance."
|
|
909
|
+
)
|
|
910
|
+
|
|
911
|
+
def setup_managed_cloud( # noqa: PLR0912
|
|
912
|
+
self,
|
|
913
|
+
*,
|
|
914
|
+
provider: str,
|
|
915
|
+
region: str,
|
|
916
|
+
name: str,
|
|
917
|
+
functional_verify: Optional[str],
|
|
918
|
+
cluster_management_stack_version: ClusterManagementStackVersions,
|
|
919
|
+
enable_head_node_fault_tolerance: bool,
|
|
920
|
+
project_id: Optional[str] = None,
|
|
921
|
+
is_aioa: bool = False,
|
|
922
|
+
yes: bool = False,
|
|
923
|
+
boto3_session: Optional[
|
|
924
|
+
boto3.Session
|
|
925
|
+
] = None, # This is used by AIOA cloud setup
|
|
926
|
+
_use_strict_iam_permissions: bool = False, # This should only be used in testing.
|
|
927
|
+
auto_add_user: bool = True,
|
|
928
|
+
) -> None:
|
|
929
|
+
"""
|
|
930
|
+
Sets up a cloud provider
|
|
931
|
+
"""
|
|
932
|
+
# TODO (congding): split this function into smaller functions per cloud provider
|
|
933
|
+
functions_to_verify = self._validate_functional_verification_args(
|
|
934
|
+
functional_verify
|
|
935
|
+
)
|
|
936
|
+
if provider == "aws":
|
|
937
|
+
if boto3_session is None:
|
|
938
|
+
# If boto3_session is not provided, we will create a new session with the given region.
|
|
939
|
+
boto3_session = boto3.Session(region_name=region)
|
|
940
|
+
if not validate_aws_credentials(self.log, boto3_session):
|
|
941
|
+
raise ClickException(
|
|
942
|
+
"Cloud setup requires valid AWS credentials to be set locally. Learn more: https://boto3.amazonaws.com/v1/documentation/api/latest/guide/credentials.html"
|
|
943
|
+
)
|
|
944
|
+
self.cloud_event_producer.init_trace_context(
|
|
945
|
+
CloudAnalyticsEventCommandName.SETUP, CloudProviders.AWS
|
|
946
|
+
)
|
|
947
|
+
self.cloud_event_producer.produce(
|
|
948
|
+
CloudAnalyticsEventName.COMMAND_START, succeeded=True,
|
|
949
|
+
)
|
|
950
|
+
with self.log.spinner("Preparing environment for cloud setup..."):
|
|
951
|
+
(
|
|
952
|
+
anyscale_iam_role_name,
|
|
953
|
+
cloud_id,
|
|
954
|
+
) = self.prepare_for_managed_cloud_setup(
|
|
955
|
+
region,
|
|
956
|
+
name,
|
|
957
|
+
cluster_management_stack_version,
|
|
958
|
+
auto_add_user,
|
|
959
|
+
is_aioa,
|
|
960
|
+
boto3_session,
|
|
961
|
+
)
|
|
962
|
+
|
|
963
|
+
try:
|
|
964
|
+
anyscale_aws_account = (
|
|
965
|
+
self.api_client.get_anyscale_aws_account_api_v2_clouds_anyscale_aws_account_get().result.anyscale_aws_account
|
|
966
|
+
)
|
|
967
|
+
cfn_stack = self.run_cloudformation(
|
|
968
|
+
region,
|
|
969
|
+
cloud_id,
|
|
970
|
+
anyscale_iam_role_name,
|
|
971
|
+
f"{cloud_id}-cluster_node_role",
|
|
972
|
+
enable_head_node_fault_tolerance,
|
|
973
|
+
anyscale_aws_account,
|
|
974
|
+
_use_strict_iam_permissions=_use_strict_iam_permissions,
|
|
975
|
+
boto3_session=boto3_session,
|
|
976
|
+
)
|
|
977
|
+
self.cloud_event_producer.produce(
|
|
978
|
+
CloudAnalyticsEventName.RESOURCES_CREATED, succeeded=True,
|
|
979
|
+
)
|
|
980
|
+
except Exception as e: # noqa: BLE001
|
|
981
|
+
self.log.error(str(e))
|
|
982
|
+
self.cloud_event_producer.produce(
|
|
983
|
+
CloudAnalyticsEventName.RESOURCES_CREATED,
|
|
984
|
+
succeeded=False,
|
|
985
|
+
logger=self.log,
|
|
986
|
+
internal_error=str(e),
|
|
987
|
+
)
|
|
988
|
+
self.api_client.delete_cloud_api_v2_clouds_cloud_id_delete(
|
|
989
|
+
cloud_id=cloud_id
|
|
990
|
+
)
|
|
991
|
+
raise ClickException("Cloud setup failed!")
|
|
992
|
+
|
|
993
|
+
try:
|
|
994
|
+
self.update_cloud_with_resources(
|
|
995
|
+
cfn_stack, cloud_id, enable_head_node_fault_tolerance
|
|
996
|
+
)
|
|
997
|
+
self.wait_for_cloud_to_be_active(cloud_id, CloudProviders.AWS)
|
|
998
|
+
self.cloud_event_producer.produce(
|
|
999
|
+
CloudAnalyticsEventName.INFRA_SETUP_COMPLETE, succeeded=True,
|
|
1000
|
+
)
|
|
1001
|
+
except Exception as e: # noqa: BLE001
|
|
1002
|
+
self.log.error(str(e))
|
|
1003
|
+
self.cloud_event_producer.produce(
|
|
1004
|
+
CloudAnalyticsEventName.INFRA_SETUP_COMPLETE,
|
|
1005
|
+
succeeded=False,
|
|
1006
|
+
internal_error=str(e),
|
|
1007
|
+
)
|
|
1008
|
+
self.api_client.delete_cloud_api_v2_clouds_cloud_id_delete(
|
|
1009
|
+
cloud_id=cloud_id
|
|
1010
|
+
)
|
|
1011
|
+
raise ClickException("Cloud setup failed!")
|
|
1012
|
+
|
|
1013
|
+
self.verify_aws_cloud_quotas(region=region, boto3_session=boto3_session)
|
|
1014
|
+
|
|
1015
|
+
self.log.info(f"Successfully created cloud {name}, and it's ready to use.")
|
|
1016
|
+
elif provider == "gcp":
|
|
1017
|
+
if project_id is None:
|
|
1018
|
+
raise click.ClickException("Please provide a value for --project-id")
|
|
1019
|
+
gcp_utils = try_import_gcp_utils()
|
|
1020
|
+
setup_utils = try_import_gcp_managed_setup_utils()
|
|
1021
|
+
factory = gcp_utils.get_google_cloud_client_factory(self.log, project_id)
|
|
1022
|
+
self.cloud_event_producer.init_trace_context(
|
|
1023
|
+
CloudAnalyticsEventCommandName.SETUP, CloudProviders.GCP
|
|
1024
|
+
)
|
|
1025
|
+
self.cloud_event_producer.produce(
|
|
1026
|
+
CloudAnalyticsEventName.COMMAND_START, succeeded=True,
|
|
1027
|
+
)
|
|
1028
|
+
with self.log.spinner("Preparing environment for cloud setup..."):
|
|
1029
|
+
try:
|
|
1030
|
+
organization_id = get_organization_id(self.api_client)
|
|
1031
|
+
anyscale_aws_account = (
|
|
1032
|
+
self.api_client.get_anyscale_aws_account_api_v2_clouds_anyscale_aws_account_get().result.anyscale_aws_account
|
|
1033
|
+
)
|
|
1034
|
+
# Enable APIs in the given GCP project
|
|
1035
|
+
setup_utils.enable_project_apis(
|
|
1036
|
+
factory, project_id, self.log, enable_head_node_fault_tolerance
|
|
1037
|
+
)
|
|
1038
|
+
# We need the Google APIs Service Agent to have security admin permissions on the project
|
|
1039
|
+
# so that we can set IAM policy on Anyscale access service account
|
|
1040
|
+
project_number = setup_utils.get_project_number(
|
|
1041
|
+
factory, project_id
|
|
1042
|
+
).split("/")[-1]
|
|
1043
|
+
google_api_service_agent = f"serviceAccount:{project_number}@cloudservices.gserviceaccount.com"
|
|
1044
|
+
setup_utils.append_project_iam_policy(
|
|
1045
|
+
factory,
|
|
1046
|
+
project_id,
|
|
1047
|
+
"roles/iam.securityAdmin",
|
|
1048
|
+
google_api_service_agent,
|
|
1049
|
+
)
|
|
1050
|
+
|
|
1051
|
+
except ClickException as e:
|
|
1052
|
+
self.cloud_event_producer.produce(
|
|
1053
|
+
CloudAnalyticsEventName.PREPROCESS_COMPLETE,
|
|
1054
|
+
succeeded=False,
|
|
1055
|
+
logger=self.log,
|
|
1056
|
+
internal_error=str(e),
|
|
1057
|
+
)
|
|
1058
|
+
self.log.error(str(e))
|
|
1059
|
+
raise ClickException("Cloud setup failed!")
|
|
1060
|
+
|
|
1061
|
+
(
|
|
1062
|
+
anyscale_access_service_account,
|
|
1063
|
+
pool_name,
|
|
1064
|
+
cloud_id,
|
|
1065
|
+
) = self.prepare_for_managed_cloud_setup_gcp(
|
|
1066
|
+
project_id,
|
|
1067
|
+
region,
|
|
1068
|
+
name,
|
|
1069
|
+
factory,
|
|
1070
|
+
cluster_management_stack_version,
|
|
1071
|
+
auto_add_user,
|
|
1072
|
+
is_aioa,
|
|
1073
|
+
)
|
|
1074
|
+
|
|
1075
|
+
pool_id = pool_name.split("/")[-1]
|
|
1076
|
+
deployment_name = cloud_id.replace("_", "-").lower()
|
|
1077
|
+
deployment_succeed = False
|
|
1078
|
+
try:
|
|
1079
|
+
with self.log.spinner(
|
|
1080
|
+
"Creating workload identity federation provider for Anyscale access..."
|
|
1081
|
+
):
|
|
1082
|
+
self.create_workload_identity_federation_provider(
|
|
1083
|
+
factory, project_id, pool_id, anyscale_access_service_account
|
|
1084
|
+
)
|
|
1085
|
+
deployment_succeed = self.run_deployment_manager(
|
|
1086
|
+
factory,
|
|
1087
|
+
deployment_name,
|
|
1088
|
+
cloud_id,
|
|
1089
|
+
project_id,
|
|
1090
|
+
region,
|
|
1091
|
+
anyscale_access_service_account,
|
|
1092
|
+
pool_name,
|
|
1093
|
+
anyscale_aws_account,
|
|
1094
|
+
organization_id,
|
|
1095
|
+
enable_head_node_fault_tolerance,
|
|
1096
|
+
)
|
|
1097
|
+
self.cloud_event_producer.produce(
|
|
1098
|
+
CloudAnalyticsEventName.RESOURCES_CREATED, succeeded=True,
|
|
1099
|
+
)
|
|
1100
|
+
except Exception as e: # noqa: BLE001
|
|
1101
|
+
self.log.error(str(e))
|
|
1102
|
+
self.cloud_event_producer.produce(
|
|
1103
|
+
CloudAnalyticsEventName.RESOURCES_CREATED,
|
|
1104
|
+
succeeded=False,
|
|
1105
|
+
internal_error=str(e),
|
|
1106
|
+
logger=self.log,
|
|
1107
|
+
)
|
|
1108
|
+
self.api_client.delete_cloud_api_v2_clouds_cloud_id_delete(
|
|
1109
|
+
cloud_id=cloud_id
|
|
1110
|
+
)
|
|
1111
|
+
setup_utils.delete_workload_identity_pool(factory, pool_name, self.log)
|
|
1112
|
+
raise ClickException("Cloud setup failed!")
|
|
1113
|
+
|
|
1114
|
+
try:
|
|
1115
|
+
with self.log.spinner(
|
|
1116
|
+
"Updating Anyscale cloud with cloud resources..."
|
|
1117
|
+
):
|
|
1118
|
+
self.update_cloud_with_resources_gcp(
|
|
1119
|
+
factory,
|
|
1120
|
+
deployment_name,
|
|
1121
|
+
cloud_id,
|
|
1122
|
+
project_id,
|
|
1123
|
+
anyscale_access_service_account,
|
|
1124
|
+
)
|
|
1125
|
+
self.wait_for_cloud_to_be_active(cloud_id, CloudProviders.GCP)
|
|
1126
|
+
self.cloud_event_producer.produce(
|
|
1127
|
+
CloudAnalyticsEventName.INFRA_SETUP_COMPLETE, succeeded=True,
|
|
1128
|
+
)
|
|
1129
|
+
except Exception as e: # noqa: BLE001
|
|
1130
|
+
self.log.error(str(e))
|
|
1131
|
+
self.cloud_event_producer.produce(
|
|
1132
|
+
CloudAnalyticsEventName.INFRA_SETUP_COMPLETE,
|
|
1133
|
+
succeeded=False,
|
|
1134
|
+
internal_error=str(e),
|
|
1135
|
+
)
|
|
1136
|
+
self.api_client.delete_cloud_api_v2_clouds_cloud_id_delete(
|
|
1137
|
+
cloud_id=cloud_id
|
|
1138
|
+
)
|
|
1139
|
+
if deployment_succeed:
|
|
1140
|
+
# only clean up deployment if it's created successfully
|
|
1141
|
+
# otherwise keep the deployment for customers to check the errors
|
|
1142
|
+
setup_utils.delete_gcp_deployment(
|
|
1143
|
+
factory, project_id, deployment_name
|
|
1144
|
+
)
|
|
1145
|
+
setup_utils.delete_workload_identity_pool(factory, pool_name, self.log)
|
|
1146
|
+
raise ClickException("Cloud setup failed!")
|
|
1147
|
+
|
|
1148
|
+
self.log.info(f"Successfully created cloud {name}, and it's ready to use.")
|
|
1149
|
+
else:
|
|
1150
|
+
raise ClickException(
|
|
1151
|
+
f"Invalid Cloud provider: {provider}. Available providers are [aws, gcp]."
|
|
1152
|
+
)
|
|
1153
|
+
|
|
1154
|
+
if len(functions_to_verify) > 0:
|
|
1155
|
+
CloudFunctionalVerificationController(
|
|
1156
|
+
self.cloud_event_producer, self.log
|
|
1157
|
+
).start_verification(
|
|
1158
|
+
cloud_id,
|
|
1159
|
+
self._get_cloud_provider_from_str(provider),
|
|
1160
|
+
functions_to_verify,
|
|
1161
|
+
yes,
|
|
1162
|
+
)
|
|
1163
|
+
|
|
1164
|
+
def _add_redis_cluster_aws(
|
|
1165
|
+
self, cloud: Any, cloud_resource: CreateCloudResource, yes: bool = False
|
|
1166
|
+
):
|
|
1167
|
+
# Check if memorydb cluster already exists
|
|
1168
|
+
if cloud_resource.memorydb_cluster_config is not None:
|
|
1169
|
+
memorydb_name = cloud_resource.memorydb_cluster_config.id
|
|
1170
|
+
self.log.info(f"AWS memorydb {memorydb_name} already exists. ")
|
|
1171
|
+
return
|
|
1172
|
+
|
|
1173
|
+
# Get or create memorydb cluster
|
|
1174
|
+
try:
|
|
1175
|
+
memorydb_cluster_id = get_or_create_memorydb(
|
|
1176
|
+
cloud.region, cloud_resource.aws_cloudformation_stack_id, self.log, yes,
|
|
1177
|
+
)
|
|
1178
|
+
memorydb_cluster_config = _get_memorydb_cluster_config(
|
|
1179
|
+
memorydb_cluster_id, cloud.region, self.log
|
|
1180
|
+
)
|
|
1181
|
+
except Exception as e: # noqa: BLE001
|
|
1182
|
+
raise ClickException(f"Failed to create memorydb cluster: {e}")
|
|
1183
|
+
|
|
1184
|
+
# Edit cloud resource record
|
|
1185
|
+
try:
|
|
1186
|
+
self.api_client.edit_cloud_resource_api_v2_clouds_with_cloud_resource_router_cloud_id_patch(
|
|
1187
|
+
cloud_id=cloud.id,
|
|
1188
|
+
editable_cloud_resource=EditableCloudResource(
|
|
1189
|
+
memorydb_cluster_config=memorydb_cluster_config,
|
|
1190
|
+
),
|
|
1191
|
+
)
|
|
1192
|
+
except Exception as e: # noqa: BLE001
|
|
1193
|
+
self.log.error(str(e))
|
|
1194
|
+
raise ClickException(
|
|
1195
|
+
"Failed to update resources on Anyscale. Please reach out to Anyscale support or your SA for assistance."
|
|
1196
|
+
)
|
|
1197
|
+
|
|
1198
|
+
def _add_redis_cluster_gcp(
|
|
1199
|
+
self, cloud: Any, cloud_resource: CreateCloudResourceGCP, yes: bool = False
|
|
1200
|
+
):
|
|
1201
|
+
if cloud_resource.memorystore_instance_config is not None:
|
|
1202
|
+
memorystore_name = cloud_resource.memorystore_instance_config.name
|
|
1203
|
+
self.log.info(f"GCP memorystore {memorystore_name} already exists. ")
|
|
1204
|
+
return
|
|
1205
|
+
|
|
1206
|
+
gcp_utils = try_import_gcp_utils()
|
|
1207
|
+
managed_setup_utils = try_import_gcp_managed_setup_utils()
|
|
1208
|
+
project_id = self._get_project_id(cloud, cloud.name, cloud.id)
|
|
1209
|
+
factory = gcp_utils.get_google_cloud_client_factory(self.log, project_id)
|
|
1210
|
+
deployment_name = cloud_resource.gcp_deployment_manager_id
|
|
1211
|
+
try:
|
|
1212
|
+
memorystore_instance_name = managed_setup_utils.get_or_create_memorystore_gcp(
|
|
1213
|
+
factory,
|
|
1214
|
+
cloud.id,
|
|
1215
|
+
deployment_name,
|
|
1216
|
+
project_id,
|
|
1217
|
+
cloud.region,
|
|
1218
|
+
self.log,
|
|
1219
|
+
yes,
|
|
1220
|
+
)
|
|
1221
|
+
memorystore_instance_config = gcp_utils.get_gcp_memorystore_config(
|
|
1222
|
+
factory, memorystore_instance_name
|
|
1223
|
+
)
|
|
1224
|
+
except Exception as e: # noqa: BLE001
|
|
1225
|
+
raise ClickException(f"Failed to create GCP memorystore. Error: {e}")
|
|
1226
|
+
try:
|
|
1227
|
+
self.api_client.edit_cloud_resource_api_v2_clouds_with_cloud_resource_gcp_router_cloud_id_patch(
|
|
1228
|
+
cloud_id=cloud.id,
|
|
1229
|
+
editable_cloud_resource_gcp=EditableCloudResourceGCP(
|
|
1230
|
+
memorystore_instance_config=memorystore_instance_config,
|
|
1231
|
+
),
|
|
1232
|
+
)
|
|
1233
|
+
except Exception as e: # noqa: BLE001
|
|
1234
|
+
self.log.error(str(e))
|
|
1235
|
+
raise ClickException(
|
|
1236
|
+
"Cloud update failed! Please reach out to Anyscale support or your SA for assistance."
|
|
1237
|
+
)
|
|
1238
|
+
|
|
1239
|
+
def _update_auto_add_user_field(self, auto_add_user: bool, cloud) -> None:
|
|
1240
|
+
if cloud.auto_add_user == auto_add_user:
|
|
1241
|
+
self.log.info(
|
|
1242
|
+
f"No updated required to the auto add user value. Cloud {cloud.name}({cloud.id}) "
|
|
1243
|
+
f"has auto add user {'enabled' if auto_add_user else 'disabled'}."
|
|
1244
|
+
)
|
|
1245
|
+
else:
|
|
1246
|
+
self.api_client.update_cloud_auto_add_user_api_v2_clouds_cloud_id_auto_add_user_put(
|
|
1247
|
+
cloud.id, auto_add_user=auto_add_user
|
|
1248
|
+
)
|
|
1249
|
+
if auto_add_user:
|
|
1250
|
+
self.log.info(
|
|
1251
|
+
f"Auto add user for cloud {cloud.name}({cloud.id}) has been successfully enabled. Note: There may be up to 30 "
|
|
1252
|
+
"sec delay for all users to be granted permissions after this feature is enabled."
|
|
1253
|
+
)
|
|
1254
|
+
else:
|
|
1255
|
+
self.log.info(
|
|
1256
|
+
f"Auto add user for cloud {cloud.name}({cloud.id}) has been successfully disabled. No existing "
|
|
1257
|
+
"cloud permissions were altered by this flag. Users added to the organization in the future will not "
|
|
1258
|
+
"automatically be added to this cloud."
|
|
1259
|
+
)
|
|
1260
|
+
|
|
1261
|
+
def _update_customer_aggregated_logs_config(self, cloud_id: str, is_enabled: bool):
|
|
1262
|
+
self.api_client.update_customer_aggregated_logs_config_api_v2_clouds_cloud_id_update_customer_aggregated_logs_config_put(
|
|
1263
|
+
cloud_id=cloud_id, is_enabled=is_enabled,
|
|
1264
|
+
)
|
|
1265
|
+
|
|
1266
|
+
def update_managed_cloud( # noqa: PLR0912, C901
|
|
1267
|
+
self,
|
|
1268
|
+
cloud_name: Optional[str],
|
|
1269
|
+
cloud_id: Optional[str],
|
|
1270
|
+
enable_head_node_fault_tolerance: bool,
|
|
1271
|
+
functional_verify: Optional[str],
|
|
1272
|
+
yes: bool = False,
|
|
1273
|
+
auto_add_user: Optional[bool] = None,
|
|
1274
|
+
) -> None:
|
|
1275
|
+
"""
|
|
1276
|
+
Updates managed cloud.
|
|
1277
|
+
|
|
1278
|
+
If `enable_head_node_fault_tolerance` is set to True, we will add redis clusters to the cloud.
|
|
1279
|
+
Otherwise it only updates the inline IAM policies associated with the cross account IAM role for AWS clouds.
|
|
1280
|
+
"""
|
|
1281
|
+
functions_to_verify = self._validate_functional_verification_args(
|
|
1282
|
+
functional_verify
|
|
1283
|
+
)
|
|
1284
|
+
cloud_id, cloud_name = get_cloud_id_and_name(
|
|
1285
|
+
self.api_client, cloud_id, cloud_name
|
|
1286
|
+
)
|
|
1287
|
+
cloud = self.api_client.get_cloud_api_v2_clouds_cloud_id_get(cloud_id).result
|
|
1288
|
+
if cloud.is_bring_your_own_resource or cloud.is_bring_your_own_resource is None:
|
|
1289
|
+
# If cloud.is_bring_your_own_resource is None, we couldn't tell it's a registered cloud or a managed cloud.
|
|
1290
|
+
# But it should be an old cloud so that we could just abort.
|
|
1291
|
+
raise ClickException(
|
|
1292
|
+
"Cannot update cloud with customer defined resources. Please modify the resources by `anyscale cloud edit`"
|
|
1293
|
+
)
|
|
1294
|
+
cloud_resource = get_cloud_resource_by_cloud_id(
|
|
1295
|
+
cloud_id, cloud.provider, self.api_client
|
|
1296
|
+
)
|
|
1297
|
+
if cloud_resource is None:
|
|
1298
|
+
raise ClickException(
|
|
1299
|
+
f"This cloud {cloud_name}({cloud_id}) does not contain resource records. Please delete this cloud and create a new one."
|
|
1300
|
+
)
|
|
1301
|
+
if anyscale_version == "0.0.0-dev":
|
|
1302
|
+
confirm(
|
|
1303
|
+
"You are using a development version of Anyscale CLI. Still want to update the cloud?",
|
|
1304
|
+
yes,
|
|
1305
|
+
)
|
|
1306
|
+
if auto_add_user is not None:
|
|
1307
|
+
self._update_auto_add_user_field(auto_add_user, cloud)
|
|
1308
|
+
msg = ""
|
|
1309
|
+
if cloud.provider == CloudProviders.AWS:
|
|
1310
|
+
msg = (
|
|
1311
|
+
" Note: The inline IAM policies associated with the cross account IAM role of this AWS cloud "
|
|
1312
|
+
f"were not updated because {'--enable-auto-add-user' if auto_add_user else '--disable-auto-add-user'} "
|
|
1313
|
+
"was specified. Please re-run `anyscale cloud update` if you want to update these IAM policies."
|
|
1314
|
+
)
|
|
1315
|
+
self.log.info("Cloud update completed." + msg)
|
|
1316
|
+
return
|
|
1317
|
+
|
|
1318
|
+
self.cloud_event_producer.init_trace_context(
|
|
1319
|
+
CloudAnalyticsEventCommandName.UPDATE, cloud.provider, cloud_id
|
|
1320
|
+
)
|
|
1321
|
+
if enable_head_node_fault_tolerance:
|
|
1322
|
+
self.cloud_event_producer.produce(
|
|
1323
|
+
CloudAnalyticsEventName.COMMAND_START, succeeded=True,
|
|
1324
|
+
)
|
|
1325
|
+
try:
|
|
1326
|
+
if cloud.provider == CloudProviders.AWS:
|
|
1327
|
+
self._add_redis_cluster_aws(cloud, cloud_resource, yes)
|
|
1328
|
+
elif cloud.provider == CloudProviders.GCP:
|
|
1329
|
+
self._add_redis_cluster_gcp(cloud, cloud_resource, yes)
|
|
1330
|
+
else:
|
|
1331
|
+
raise ClickException(
|
|
1332
|
+
f"Unsupported cloud provider {cloud.provider}. Only AWS and GCP are supported for fault tolerance."
|
|
1333
|
+
)
|
|
1334
|
+
except Exception as e: # noqa: BLE001
|
|
1335
|
+
self.cloud_event_producer.produce(
|
|
1336
|
+
CloudAnalyticsEventName.REDIS_CLUSTER_ADDED,
|
|
1337
|
+
succeeded=False,
|
|
1338
|
+
internal_error=str(e),
|
|
1339
|
+
)
|
|
1340
|
+
raise ClickException(f"Cloud update failed! {e}")
|
|
1341
|
+
self.cloud_event_producer.produce(
|
|
1342
|
+
CloudAnalyticsEventName.REDIS_CLUSTER_ADDED, succeeded=True
|
|
1343
|
+
)
|
|
1344
|
+
else:
|
|
1345
|
+
# Update IAM permissions
|
|
1346
|
+
if cloud.provider != CloudProviders.AWS:
|
|
1347
|
+
raise ClickException(
|
|
1348
|
+
f"Unsupported cloud provider {cloud.provider}. Only AWS is supported for updating."
|
|
1349
|
+
)
|
|
1350
|
+
aws_cloudformation_stack_id = cloud_resource.aws_cloudformation_stack_id
|
|
1351
|
+
if aws_cloudformation_stack_id is None:
|
|
1352
|
+
raise ClickException(
|
|
1353
|
+
f"This cloud {cloud.name}({cloud.id}) does not have an associated cloudformation stack. Please contact Anyscale support."
|
|
1354
|
+
)
|
|
1355
|
+
|
|
1356
|
+
self.cloud_event_producer.produce(
|
|
1357
|
+
CloudAnalyticsEventName.COMMAND_START, succeeded=True,
|
|
1358
|
+
)
|
|
1359
|
+
try:
|
|
1360
|
+
update_iam_role(
|
|
1361
|
+
cloud.region, aws_cloudformation_stack_id, self.log, yes
|
|
1362
|
+
)
|
|
1363
|
+
except Exception as e: # noqa: BLE001
|
|
1364
|
+
self.cloud_event_producer.produce(
|
|
1365
|
+
CloudAnalyticsEventName.IAM_ROLE_UPDATED,
|
|
1366
|
+
succeeded=False,
|
|
1367
|
+
internal_error=str(e),
|
|
1368
|
+
)
|
|
1369
|
+
raise ClickException(f"Cloud update failed! {e}")
|
|
1370
|
+
self.cloud_event_producer.produce(
|
|
1371
|
+
CloudAnalyticsEventName.IAM_ROLE_UPDATED, succeeded=True,
|
|
1372
|
+
)
|
|
1373
|
+
|
|
1374
|
+
self.log.info("Cloud update completed.")
|
|
1375
|
+
|
|
1376
|
+
if len(functions_to_verify) > 0:
|
|
1377
|
+
CloudFunctionalVerificationController(
|
|
1378
|
+
self.cloud_event_producer, self.log
|
|
1379
|
+
).start_verification(
|
|
1380
|
+
cloud_id, CloudProviders.AWS, functions_to_verify, yes,
|
|
1381
|
+
)
|
|
1382
|
+
|
|
1383
|
+
def get_cloud_config(
|
|
1384
|
+
self, cloud_name: Optional[str] = None, cloud_id: Optional[str] = None,
|
|
1385
|
+
) -> str:
|
|
1386
|
+
"""Get a cloud's current JSON configuration."""
|
|
1387
|
+
|
|
1388
|
+
cloud_id, cloud_name = get_cloud_id_and_name(
|
|
1389
|
+
self.api_client, cloud_id, cloud_name
|
|
1390
|
+
)
|
|
1391
|
+
|
|
1392
|
+
return str(get_cloud_json_from_id(cloud_id, self.api_client)["config"])
|
|
1393
|
+
|
|
1394
|
+
def update_cloud_config(
|
|
1395
|
+
self,
|
|
1396
|
+
cloud_name: Optional[str] = None,
|
|
1397
|
+
cloud_id: Optional[str] = None,
|
|
1398
|
+
enable_log_ingestion: Optional[bool] = None,
|
|
1399
|
+
):
|
|
1400
|
+
"""Update a cloud's configuration."""
|
|
1401
|
+
|
|
1402
|
+
cloud_id, cloud_name = get_cloud_id_and_name(
|
|
1403
|
+
self.api_client, cloud_id, cloud_name
|
|
1404
|
+
)
|
|
1405
|
+
if enable_log_ingestion is not None:
|
|
1406
|
+
self._update_customer_aggregated_logs_config(
|
|
1407
|
+
cloud_id=cloud_id, is_enabled=enable_log_ingestion,
|
|
1408
|
+
)
|
|
1409
|
+
self.log.info(
|
|
1410
|
+
f"Successfully updated log ingestion configuration for cloud, "
|
|
1411
|
+
f"{cloud_id} to {enable_log_ingestion}"
|
|
1412
|
+
)
|
|
1413
|
+
|
|
1414
|
+
def set_default_cloud(
|
|
1415
|
+
self, cloud_name: Optional[str], cloud_id: Optional[str],
|
|
1416
|
+
) -> None:
|
|
1417
|
+
"""
|
|
1418
|
+
Sets default cloud for caller's organization. This operation can only be performed
|
|
1419
|
+
by organization admins, and the default cloud must have organization level
|
|
1420
|
+
permissions.
|
|
1421
|
+
"""
|
|
1422
|
+
|
|
1423
|
+
cloud_id, cloud_name = get_cloud_id_and_name(
|
|
1424
|
+
self.api_client, cloud_id, cloud_name
|
|
1425
|
+
)
|
|
1426
|
+
|
|
1427
|
+
self.api_client.update_default_cloud_api_v2_organizations_update_default_cloud_post(
|
|
1428
|
+
cloud_id=cloud_id
|
|
1429
|
+
)
|
|
1430
|
+
|
|
1431
|
+
self.log.info(f"Updated default cloud to {cloud_name}")
|
|
1432
|
+
|
|
1433
|
+
def _passed_or_failed_str_from_bool(self, is_passing: bool) -> str:
|
|
1434
|
+
return "PASSED" if is_passing else "FAILED"
|
|
1435
|
+
|
|
1436
|
+
@staticmethod
|
|
1437
|
+
def _get_cloud_provider_from_str(provider: str) -> CloudProviders:
|
|
1438
|
+
if provider.lower() == "aws":
|
|
1439
|
+
return CloudProviders.AWS
|
|
1440
|
+
elif provider.lower() == "gcp":
|
|
1441
|
+
return CloudProviders.GCP
|
|
1442
|
+
else:
|
|
1443
|
+
raise ClickException(
|
|
1444
|
+
f"Unsupported provider {provider}. Supported providers are [aws, gcp]."
|
|
1445
|
+
)
|
|
1446
|
+
|
|
1447
|
+
def _validate_functional_verification_args(
|
|
1448
|
+
self, functional_verify: Optional[str]
|
|
1449
|
+
) -> List[CloudFunctionalVerificationType]:
|
|
1450
|
+
if functional_verify is None:
|
|
1451
|
+
return []
|
|
1452
|
+
# validate functional_verify
|
|
1453
|
+
functions_to_verify = set()
|
|
1454
|
+
for function in functional_verify.split(","):
|
|
1455
|
+
fn = getattr(CloudFunctionalVerificationType, function.upper(), None)
|
|
1456
|
+
if fn is None:
|
|
1457
|
+
raise ClickException(
|
|
1458
|
+
f"Unsupported function {function} for --functional-verify. "
|
|
1459
|
+
f"Supported functions: {[function.lower() for function in CloudFunctionalVerificationType]}"
|
|
1460
|
+
)
|
|
1461
|
+
functions_to_verify.add(fn)
|
|
1462
|
+
return list(functions_to_verify)
|
|
1463
|
+
|
|
1464
|
+
def verify_cloud( # noqa: PLR0911
|
|
1465
|
+
self,
|
|
1466
|
+
*,
|
|
1467
|
+
cloud_name: Optional[str],
|
|
1468
|
+
cloud_id: Optional[str],
|
|
1469
|
+
functional_verify: Optional[str],
|
|
1470
|
+
boto3_session: Optional[Any] = None,
|
|
1471
|
+
strict: bool = False,
|
|
1472
|
+
_use_strict_iam_permissions: bool = False, # This should only be used in testing.
|
|
1473
|
+
yes: bool = False,
|
|
1474
|
+
) -> bool:
|
|
1475
|
+
"""
|
|
1476
|
+
Verifies a cloud by name or id.
|
|
1477
|
+
|
|
1478
|
+
Note: If your changes involve operations that may require additional permissions
|
|
1479
|
+
(for example, `boto3_session.client("efs").describe_backup_policy`), it's important
|
|
1480
|
+
to run the end-to-end test `bk_e2e/test_cloud.py` locally before pushing the changes.
|
|
1481
|
+
This way, you can ensure that your changes will not break the tests.
|
|
1482
|
+
"""
|
|
1483
|
+
functions_to_verify = self._validate_functional_verification_args(
|
|
1484
|
+
functional_verify
|
|
1485
|
+
)
|
|
1486
|
+
|
|
1487
|
+
cloud_id, cloud_name = get_cloud_id_and_name(
|
|
1488
|
+
self.api_client, cloud_id, cloud_name
|
|
1489
|
+
)
|
|
1490
|
+
|
|
1491
|
+
cloud = self.api_client.get_cloud_api_v2_clouds_cloud_id_get(cloud_id).result
|
|
1492
|
+
|
|
1493
|
+
if cloud.compute_stack == ComputeStack.K8S:
|
|
1494
|
+
self.log.error(
|
|
1495
|
+
"The cloud verify command is not supported for Kubernetes clouds."
|
|
1496
|
+
)
|
|
1497
|
+
return False
|
|
1498
|
+
|
|
1499
|
+
if cloud.state in (CloudState.DELETING, CloudState.DELETED):
|
|
1500
|
+
self.log.info(
|
|
1501
|
+
f"This cloud {cloud_name}({cloud_id}) is either during deletion or deleted. Skipping verification."
|
|
1502
|
+
)
|
|
1503
|
+
return False
|
|
1504
|
+
|
|
1505
|
+
cloud_resource = get_cloud_resource_by_cloud_id(
|
|
1506
|
+
cloud_id, cloud.provider, self.api_client
|
|
1507
|
+
)
|
|
1508
|
+
if cloud_resource is None:
|
|
1509
|
+
self.log.error(
|
|
1510
|
+
f"This cloud {cloud_name}({cloud_id}) does not contain resource records. Please delete this cloud and create a new one."
|
|
1511
|
+
)
|
|
1512
|
+
return False
|
|
1513
|
+
|
|
1514
|
+
self.cloud_event_producer.init_trace_context(
|
|
1515
|
+
CloudAnalyticsEventCommandName.VERIFY,
|
|
1516
|
+
cloud_provider=cloud.provider,
|
|
1517
|
+
cloud_id=cloud_id,
|
|
1518
|
+
)
|
|
1519
|
+
self.cloud_event_producer.produce(
|
|
1520
|
+
CloudAnalyticsEventName.COMMAND_START, succeeded=True
|
|
1521
|
+
)
|
|
1522
|
+
|
|
1523
|
+
if cloud.provider == "AWS":
|
|
1524
|
+
if boto3_session is None:
|
|
1525
|
+
boto3_session = boto3.Session(region_name=cloud.region)
|
|
1526
|
+
if not self.verify_aws_cloud_resources(
|
|
1527
|
+
cloud_resource=cloud_resource,
|
|
1528
|
+
boto3_session=boto3_session,
|
|
1529
|
+
region=cloud.region,
|
|
1530
|
+
cloud_id=cloud_id,
|
|
1531
|
+
is_bring_your_own_resource=cloud.is_bring_your_own_resource,
|
|
1532
|
+
is_private_network=cloud.is_private_cloud
|
|
1533
|
+
if cloud.is_private_cloud
|
|
1534
|
+
else False,
|
|
1535
|
+
strict=strict,
|
|
1536
|
+
_use_strict_iam_permissions=_use_strict_iam_permissions,
|
|
1537
|
+
):
|
|
1538
|
+
self.cloud_event_producer.produce(
|
|
1539
|
+
CloudAnalyticsEventName.RESOURCES_VERIFIED,
|
|
1540
|
+
succeeded=False,
|
|
1541
|
+
logger=self.log,
|
|
1542
|
+
)
|
|
1543
|
+
return False
|
|
1544
|
+
elif cloud.provider == "GCP":
|
|
1545
|
+
credentials_dict = json.loads(cloud.credentials)
|
|
1546
|
+
project_id = credentials_dict["project_id"]
|
|
1547
|
+
host_project_id = credentials_dict.get("host_project_id")
|
|
1548
|
+
if not self.verify_gcp_cloud_resources(
|
|
1549
|
+
cloud_resource=cloud_resource,
|
|
1550
|
+
project_id=project_id,
|
|
1551
|
+
host_project_id=host_project_id,
|
|
1552
|
+
region=cloud.region,
|
|
1553
|
+
cloud_id=cloud_id,
|
|
1554
|
+
yes=False,
|
|
1555
|
+
strict=strict,
|
|
1556
|
+
is_private_service_cloud=cloud.is_private_service_cloud,
|
|
1557
|
+
):
|
|
1558
|
+
self.cloud_event_producer.produce(
|
|
1559
|
+
CloudAnalyticsEventName.RESOURCES_VERIFIED,
|
|
1560
|
+
succeeded=False,
|
|
1561
|
+
logger=self.log,
|
|
1562
|
+
)
|
|
1563
|
+
return False
|
|
1564
|
+
else:
|
|
1565
|
+
self.log.error(
|
|
1566
|
+
f"This cloud {cloud_name}({cloud_id}) does not have a valid cloud provider."
|
|
1567
|
+
)
|
|
1568
|
+
self.cloud_event_producer.produce(
|
|
1569
|
+
CloudAnalyticsEventName.RESOURCES_VERIFIED,
|
|
1570
|
+
succeeded=False,
|
|
1571
|
+
internal_error="invalid cloud provider",
|
|
1572
|
+
)
|
|
1573
|
+
return False
|
|
1574
|
+
|
|
1575
|
+
self.cloud_event_producer.produce(
|
|
1576
|
+
CloudAnalyticsEventName.RESOURCES_VERIFIED, succeeded=True
|
|
1577
|
+
)
|
|
1578
|
+
|
|
1579
|
+
if len(functions_to_verify) == 0:
|
|
1580
|
+
return True
|
|
1581
|
+
|
|
1582
|
+
return CloudFunctionalVerificationController(
|
|
1583
|
+
self.cloud_event_producer, self.log
|
|
1584
|
+
).start_verification(cloud_id, cloud.provider, functions_to_verify, yes=yes)
|
|
1585
|
+
|
|
1586
|
+
def verify_aws_cloud_resources(
|
|
1587
|
+
self,
|
|
1588
|
+
*,
|
|
1589
|
+
cloud_resource: CreateCloudResource,
|
|
1590
|
+
boto3_session: boto3.Session,
|
|
1591
|
+
region: str,
|
|
1592
|
+
is_private_network: bool,
|
|
1593
|
+
cloud_id: str,
|
|
1594
|
+
is_bring_your_own_resource: bool = False,
|
|
1595
|
+
ignore_capacity_errors: bool = IGNORE_CAPACITY_ERRORS,
|
|
1596
|
+
logger: CloudSetupLogger = None,
|
|
1597
|
+
strict: bool = False,
|
|
1598
|
+
_use_strict_iam_permissions: bool = False, # This should only be used in testing.
|
|
1599
|
+
):
|
|
1600
|
+
if not logger:
|
|
1601
|
+
logger = self.log
|
|
1602
|
+
|
|
1603
|
+
verify_aws_vpc_result = verify_aws_vpc(
|
|
1604
|
+
cloud_resource=cloud_resource,
|
|
1605
|
+
boto3_session=boto3_session,
|
|
1606
|
+
logger=logger,
|
|
1607
|
+
ignore_capacity_errors=ignore_capacity_errors,
|
|
1608
|
+
strict=strict,
|
|
1609
|
+
)
|
|
1610
|
+
verify_aws_subnets_result = verify_aws_subnets(
|
|
1611
|
+
cloud_resource=cloud_resource,
|
|
1612
|
+
region=region,
|
|
1613
|
+
logger=logger,
|
|
1614
|
+
ignore_capacity_errors=ignore_capacity_errors,
|
|
1615
|
+
is_private_network=is_private_network,
|
|
1616
|
+
strict=strict,
|
|
1617
|
+
)
|
|
1618
|
+
|
|
1619
|
+
anyscale_aws_account = (
|
|
1620
|
+
self.api_client.get_anyscale_aws_account_api_v2_clouds_anyscale_aws_account_get().result.anyscale_aws_account
|
|
1621
|
+
)
|
|
1622
|
+
verify_aws_iam_roles_result = verify_aws_iam_roles(
|
|
1623
|
+
cloud_resource=cloud_resource,
|
|
1624
|
+
boto3_session=boto3_session,
|
|
1625
|
+
anyscale_aws_account=anyscale_aws_account,
|
|
1626
|
+
logger=logger,
|
|
1627
|
+
strict=strict,
|
|
1628
|
+
cloud_id=cloud_id,
|
|
1629
|
+
_use_strict_iam_permissions=_use_strict_iam_permissions,
|
|
1630
|
+
)
|
|
1631
|
+
verify_aws_security_groups_result = verify_aws_security_groups(
|
|
1632
|
+
cloud_resource=cloud_resource,
|
|
1633
|
+
boto3_session=boto3_session,
|
|
1634
|
+
logger=logger,
|
|
1635
|
+
strict=strict,
|
|
1636
|
+
)
|
|
1637
|
+
verify_aws_s3_result = verify_aws_s3(
|
|
1638
|
+
cloud_resource=cloud_resource,
|
|
1639
|
+
boto3_session=boto3_session,
|
|
1640
|
+
region=region,
|
|
1641
|
+
logger=logger,
|
|
1642
|
+
strict=strict,
|
|
1643
|
+
)
|
|
1644
|
+
verify_aws_efs_result = verify_aws_efs(
|
|
1645
|
+
cloud_resource=cloud_resource,
|
|
1646
|
+
boto3_session=boto3_session,
|
|
1647
|
+
logger=logger,
|
|
1648
|
+
strict=strict,
|
|
1649
|
+
)
|
|
1650
|
+
# Cloudformation is only used in managed cloud setup. Set to True in BYOR case because it's not used.
|
|
1651
|
+
verify_aws_cloudformation_stack_result = True
|
|
1652
|
+
if not is_bring_your_own_resource:
|
|
1653
|
+
verify_aws_cloudformation_stack_result = verify_aws_cloudformation_stack(
|
|
1654
|
+
cloud_resource=cloud_resource,
|
|
1655
|
+
boto3_session=boto3_session,
|
|
1656
|
+
logger=logger,
|
|
1657
|
+
strict=strict,
|
|
1658
|
+
)
|
|
1659
|
+
if cloud_resource.memorydb_cluster_config is not None:
|
|
1660
|
+
verify_aws_memorydb_cluster_result = verify_aws_memorydb_cluster(
|
|
1661
|
+
cloud_resource=cloud_resource,
|
|
1662
|
+
boto3_session=boto3_session,
|
|
1663
|
+
logger=logger,
|
|
1664
|
+
strict=strict,
|
|
1665
|
+
)
|
|
1666
|
+
|
|
1667
|
+
verify_anyscale_access_result = verify_anyscale_access(
|
|
1668
|
+
self.api_client, cloud_id, CloudProviders.AWS, logger
|
|
1669
|
+
)
|
|
1670
|
+
|
|
1671
|
+
verification_result_summary = [
|
|
1672
|
+
"Verification result:",
|
|
1673
|
+
f"anyscale access: {self._passed_or_failed_str_from_bool(verify_anyscale_access_result)}",
|
|
1674
|
+
f"vpc: {self._passed_or_failed_str_from_bool(verify_aws_vpc_result)}",
|
|
1675
|
+
f"subnets: {self._passed_or_failed_str_from_bool(verify_aws_subnets_result)}",
|
|
1676
|
+
f"iam roles: {self._passed_or_failed_str_from_bool(verify_aws_iam_roles_result)}",
|
|
1677
|
+
f"security groups: {self._passed_or_failed_str_from_bool(verify_aws_security_groups_result)}",
|
|
1678
|
+
f"s3: {self._passed_or_failed_str_from_bool(verify_aws_s3_result)}",
|
|
1679
|
+
f"efs: {self._passed_or_failed_str_from_bool(verify_aws_efs_result)}",
|
|
1680
|
+
f"cloudformation stack: {self._passed_or_failed_str_from_bool(verify_aws_cloudformation_stack_result) if not is_bring_your_own_resource else 'N/A'}",
|
|
1681
|
+
]
|
|
1682
|
+
if cloud_resource.memorydb_cluster_config is not None:
|
|
1683
|
+
verification_result_summary.append(
|
|
1684
|
+
f"memorydb cluster: {self._passed_or_failed_str_from_bool(verify_aws_memorydb_cluster_result)}"
|
|
1685
|
+
)
|
|
1686
|
+
|
|
1687
|
+
logger.info("\n".join(verification_result_summary))
|
|
1688
|
+
|
|
1689
|
+
self.verify_aws_cloud_quotas(region=region, boto3_session=boto3_session)
|
|
1690
|
+
|
|
1691
|
+
return all(
|
|
1692
|
+
[
|
|
1693
|
+
verify_anyscale_access_result,
|
|
1694
|
+
verify_aws_vpc_result,
|
|
1695
|
+
verify_aws_subnets_result,
|
|
1696
|
+
verify_aws_iam_roles_result,
|
|
1697
|
+
verify_aws_security_groups_result,
|
|
1698
|
+
verify_aws_s3_result,
|
|
1699
|
+
verify_aws_efs_result,
|
|
1700
|
+
verify_aws_cloudformation_stack_result
|
|
1701
|
+
if not is_bring_your_own_resource
|
|
1702
|
+
else True,
|
|
1703
|
+
]
|
|
1704
|
+
)
|
|
1705
|
+
|
|
1706
|
+
def verify_aws_cloud_quotas(
|
|
1707
|
+
self, *, region: str, boto3_session: Optional[Any] = None
|
|
1708
|
+
):
|
|
1709
|
+
"""
|
|
1710
|
+
Checks the AWS EC2 instance quotas and warns users if they are not good enough
|
|
1711
|
+
to support LLM workloads
|
|
1712
|
+
"""
|
|
1713
|
+
if boto3_session is None:
|
|
1714
|
+
boto3_session = boto3.Session(region_name=region)
|
|
1715
|
+
|
|
1716
|
+
QUOTAS_CONFIG = {
|
|
1717
|
+
"L-3819A6DF": {
|
|
1718
|
+
"description": "All G and VT Spot Instance Requests",
|
|
1719
|
+
"min": 512,
|
|
1720
|
+
},
|
|
1721
|
+
"L-34B43A08": {
|
|
1722
|
+
"description": "All Standard (A, C, D, H, I, M, R, T, Z) Spot Instance Requests",
|
|
1723
|
+
"min": 512,
|
|
1724
|
+
},
|
|
1725
|
+
"L-7212CCBC": {
|
|
1726
|
+
"description": "All P4, P3 and P2 Spot Instance Requests",
|
|
1727
|
+
"min": 224,
|
|
1728
|
+
},
|
|
1729
|
+
"L-DB2E81BA": {
|
|
1730
|
+
"description": "Running On-Demand G and VT instances",
|
|
1731
|
+
"min": 512,
|
|
1732
|
+
},
|
|
1733
|
+
"L-1216C47A": {
|
|
1734
|
+
"description": "Running On-Demand Standard (A, C, D, H, I, M, R, T, Z) instances",
|
|
1735
|
+
"min": 544,
|
|
1736
|
+
},
|
|
1737
|
+
"L-417A185B": {"description": "Running On-Demand P instances", "min": 224,},
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
quota_client = boto3_session.client("service-quotas", region_name=region)
|
|
1741
|
+
|
|
1742
|
+
self.log.info("Checking quota values...")
|
|
1743
|
+
# List of tuples of quota code, current quota value
|
|
1744
|
+
invalid_quotas = []
|
|
1745
|
+
for quota_code, config in QUOTAS_CONFIG.items():
|
|
1746
|
+
quota = quota_client.get_service_quota(
|
|
1747
|
+
ServiceCode="ec2", QuotaCode=quota_code
|
|
1748
|
+
)
|
|
1749
|
+
if quota["Quota"]["Value"] < config["min"]:
|
|
1750
|
+
invalid_quotas.append((quota_code, quota["Quota"]["Value"]))
|
|
1751
|
+
|
|
1752
|
+
if len(invalid_quotas):
|
|
1753
|
+
quota_errors = [
|
|
1754
|
+
f"- \"{QUOTAS_CONFIG[quota_code]['description']}\" should be at least {QUOTAS_CONFIG[quota_code]['min']} (curr: {value})"
|
|
1755
|
+
for quota_code, value in invalid_quotas
|
|
1756
|
+
]
|
|
1757
|
+
quota_error_str = "\n".join(quota_errors)
|
|
1758
|
+
self.log.warning(
|
|
1759
|
+
"Your AWS account does not have enough quota to support LLM workloads. "
|
|
1760
|
+
"Please request quota increases for the following quotas:\n"
|
|
1761
|
+
f"{quota_error_str}\n\nFor instructions on how to increase quotas, visit this link: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-resource-limits.html#request-increase"
|
|
1762
|
+
)
|
|
1763
|
+
|
|
1764
|
+
def register_generic_cloud( # noqa: PLR0913
|
|
1765
|
+
self,
|
|
1766
|
+
name: str,
|
|
1767
|
+
auto_add_user: bool = False,
|
|
1768
|
+
# Optional cloud-resource-scoped parameters.
|
|
1769
|
+
# Some of these are conditionally required.
|
|
1770
|
+
region: Optional[str] = "default",
|
|
1771
|
+
cloud_storage_bucket_name: Optional[str] = None,
|
|
1772
|
+
cloud_storage_bucket_endpoint: Optional[str] = None,
|
|
1773
|
+
nfs_mount_targets: Optional[List[str]] = None,
|
|
1774
|
+
nfs_mount_path: Optional[str] = None,
|
|
1775
|
+
kubernetes_zones: Optional[List[str]] = None,
|
|
1776
|
+
) -> None:
|
|
1777
|
+
self.cloud_event_producer.init_trace_context(
|
|
1778
|
+
CloudAnalyticsEventCommandName.REGISTER, CloudProviders.GENERIC
|
|
1779
|
+
)
|
|
1780
|
+
self.cloud_event_producer.produce(
|
|
1781
|
+
CloudAnalyticsEventName.COMMAND_START, succeeded=True
|
|
1782
|
+
)
|
|
1783
|
+
|
|
1784
|
+
# Handle parsing / conversion of nfs_mount_targets.
|
|
1785
|
+
mount_targets: List[NFSMountTarget] = []
|
|
1786
|
+
for target in nfs_mount_targets or []:
|
|
1787
|
+
parts = [part.strip() for part in target.split(",")]
|
|
1788
|
+
if len(parts) == 1:
|
|
1789
|
+
mount_targets.append(NFSMountTarget(address=parts[0]))
|
|
1790
|
+
elif len(parts) == 2:
|
|
1791
|
+
mount_targets.append(NFSMountTarget(address=parts[1], zone=parts[0]))
|
|
1792
|
+
else:
|
|
1793
|
+
raise ClickException(
|
|
1794
|
+
f"Invalid mount target {target}; expected (zone,address) tuple or a singular address."
|
|
1795
|
+
)
|
|
1796
|
+
|
|
1797
|
+
# Handle defaulting of region.
|
|
1798
|
+
if not region:
|
|
1799
|
+
region = "default"
|
|
1800
|
+
|
|
1801
|
+
# Attempt to create the cloud.
|
|
1802
|
+
try:
|
|
1803
|
+
created_cloud = self.api_client.create_cloud_api_v2_clouds_post(
|
|
1804
|
+
write_cloud=WriteCloud(
|
|
1805
|
+
name=name,
|
|
1806
|
+
region=region,
|
|
1807
|
+
provider="GENERIC",
|
|
1808
|
+
is_bring_your_own_resource=True,
|
|
1809
|
+
cluster_management_stack_version=ClusterManagementStackVersions.V2,
|
|
1810
|
+
auto_add_user=auto_add_user,
|
|
1811
|
+
credentials="",
|
|
1812
|
+
)
|
|
1813
|
+
)
|
|
1814
|
+
cloud_id = created_cloud.result.id
|
|
1815
|
+
self.cloud_event_producer.set_cloud_id(cloud_id)
|
|
1816
|
+
self.cloud_event_producer.produce(
|
|
1817
|
+
CloudAnalyticsEventName.CLOUD_RECORD_INSERTED, succeeded=True
|
|
1818
|
+
)
|
|
1819
|
+
except ClickException as e:
|
|
1820
|
+
self.cloud_event_producer.produce(
|
|
1821
|
+
CloudAnalyticsEventName.CLOUD_RECORD_INSERTED,
|
|
1822
|
+
succeeded=False,
|
|
1823
|
+
internal_error=str(e),
|
|
1824
|
+
)
|
|
1825
|
+
raise
|
|
1826
|
+
|
|
1827
|
+
# Attempt to create the cloud resource.
|
|
1828
|
+
try:
|
|
1829
|
+
with self.log.spinner("Registering Anyscale cloud resources..."):
|
|
1830
|
+
cloud_resource = self.api_client.update_cloud_with_cloud_resource_api_v2_clouds_with_cloud_resource_router_cloud_id_put(
|
|
1831
|
+
cloud_id=cloud_id,
|
|
1832
|
+
update_cloud_with_cloud_resource=UpdateCloudWithCloudResource(
|
|
1833
|
+
cloud_resource_to_update=CreateCloudResource(
|
|
1834
|
+
compute_stack=ComputeStack.K8S,
|
|
1835
|
+
kubernetes_zones=kubernetes_zones,
|
|
1836
|
+
cloud_storage_bucket_name=cloud_storage_bucket_name,
|
|
1837
|
+
cloud_storage_bucket_endpoint=cloud_storage_bucket_endpoint,
|
|
1838
|
+
nfs_mount_targets=mount_targets,
|
|
1839
|
+
nfs_mount_path=nfs_mount_path,
|
|
1840
|
+
),
|
|
1841
|
+
),
|
|
1842
|
+
)
|
|
1843
|
+
|
|
1844
|
+
self.cloud_event_producer.produce(
|
|
1845
|
+
CloudAnalyticsEventName.INFRA_SETUP_COMPLETE, succeeded=True
|
|
1846
|
+
)
|
|
1847
|
+
|
|
1848
|
+
except Exception as e: # noqa: BLE001
|
|
1849
|
+
self.log.error(str(e))
|
|
1850
|
+
self.cloud_event_producer.produce(
|
|
1851
|
+
CloudAnalyticsEventName.INFRA_SETUP_COMPLETE,
|
|
1852
|
+
succeeded=False,
|
|
1853
|
+
internal_error=str(e),
|
|
1854
|
+
)
|
|
1855
|
+
|
|
1856
|
+
# Delete the cloud if registering the cloud fails
|
|
1857
|
+
self.api_client.delete_cloud_api_v2_clouds_cloud_id_delete(
|
|
1858
|
+
cloud_id=cloud_id
|
|
1859
|
+
)
|
|
1860
|
+
raise ClickException(f"Cloud registration failed! {e}")
|
|
1861
|
+
|
|
1862
|
+
# TODO (shomilj): Fetch & optionally run the Helm installation here.
|
|
1863
|
+
cloud_resource_id = cloud_resource.result.cloud_resource.id
|
|
1864
|
+
self.log.info(
|
|
1865
|
+
f"Cloud registration complete! When installing this cloud's Kubernetes Manager, use cloud deployment ID '{cloud_resource_id}'."
|
|
1866
|
+
)
|
|
1867
|
+
|
|
1868
|
+
def register_aws_cloud( # noqa: PLR0913, PLR0912, C901
|
|
1869
|
+
self,
|
|
1870
|
+
*,
|
|
1871
|
+
region: str,
|
|
1872
|
+
name: str,
|
|
1873
|
+
vpc_id: str,
|
|
1874
|
+
subnet_ids: List[str],
|
|
1875
|
+
efs_id: str,
|
|
1876
|
+
anyscale_iam_role_id: str,
|
|
1877
|
+
instance_iam_role_id: str,
|
|
1878
|
+
security_group_ids: List[str],
|
|
1879
|
+
cloud_storage_bucket_name: str,
|
|
1880
|
+
memorydb_cluster_id: Optional[str],
|
|
1881
|
+
functional_verify: Optional[str],
|
|
1882
|
+
private_network: bool,
|
|
1883
|
+
cluster_management_stack_version: ClusterManagementStackVersions,
|
|
1884
|
+
yes: bool = False,
|
|
1885
|
+
skip_verifications: bool = False,
|
|
1886
|
+
auto_add_user: bool = False,
|
|
1887
|
+
external_id: Optional[str] = None,
|
|
1888
|
+
# Default to ComputeStack.VM for backwards compatibility
|
|
1889
|
+
# for SDK users who do not specify a compute stack here.
|
|
1890
|
+
compute_stack: ComputeStack = ComputeStack.VM,
|
|
1891
|
+
kubernetes_zones: Optional[List[str]] = None,
|
|
1892
|
+
anyscale_operator_iam_identity: Optional[str] = None,
|
|
1893
|
+
):
|
|
1894
|
+
functions_to_verify = self._validate_functional_verification_args(
|
|
1895
|
+
functional_verify
|
|
1896
|
+
)
|
|
1897
|
+
if not validate_aws_credentials(self.log):
|
|
1898
|
+
raise ClickException(
|
|
1899
|
+
"Cloud registration requires valid AWS credentials to be set locally. Learn more: https://boto3.amazonaws.com/v1/documentation/api/latest/guide/credentials.html"
|
|
1900
|
+
)
|
|
1901
|
+
|
|
1902
|
+
# We accept both the full ARN and the bucket name as input
|
|
1903
|
+
# but we unify it to the bucket name here
|
|
1904
|
+
if cloud_storage_bucket_name.startswith(S3_ARN_PREFIX):
|
|
1905
|
+
cloud_storage_bucket_name = cloud_storage_bucket_name[len(S3_ARN_PREFIX) :]
|
|
1906
|
+
if not cloud_storage_bucket_name.startswith(S3_STORAGE_PREFIX):
|
|
1907
|
+
cloud_storage_bucket_name = S3_STORAGE_PREFIX + cloud_storage_bucket_name
|
|
1908
|
+
|
|
1909
|
+
organization_id = get_organization_id(self.api_client)
|
|
1910
|
+
if external_id and not external_id.startswith(organization_id):
|
|
1911
|
+
raise ClickException(
|
|
1912
|
+
f"Cloud registration failed! `--external-id` must start with the organization ID: {organization_id}"
|
|
1913
|
+
)
|
|
1914
|
+
|
|
1915
|
+
self.cloud_event_producer.init_trace_context(
|
|
1916
|
+
CloudAnalyticsEventCommandName.REGISTER, CloudProviders.AWS
|
|
1917
|
+
)
|
|
1918
|
+
self.cloud_event_producer.produce(
|
|
1919
|
+
CloudAnalyticsEventName.COMMAND_START, succeeded=True
|
|
1920
|
+
)
|
|
1921
|
+
|
|
1922
|
+
credentials = anyscale_iam_role_id
|
|
1923
|
+
if compute_stack == ComputeStack.K8S:
|
|
1924
|
+
# On K8S, we don't need to collect credentials;
|
|
1925
|
+
# instead, write a random value into this field
|
|
1926
|
+
# to maintain the property that each cloud's
|
|
1927
|
+
# credentials are unique.
|
|
1928
|
+
credentials = uuid.uuid4().hex
|
|
1929
|
+
|
|
1930
|
+
# Create a cloud without cloud resources first
|
|
1931
|
+
try:
|
|
1932
|
+
created_cloud = self.api_client.create_cloud_api_v2_clouds_post(
|
|
1933
|
+
write_cloud=WriteCloud(
|
|
1934
|
+
provider="AWS",
|
|
1935
|
+
region=region,
|
|
1936
|
+
credentials=credentials,
|
|
1937
|
+
name=name,
|
|
1938
|
+
is_bring_your_own_resource=True,
|
|
1939
|
+
is_private_cloud=private_network,
|
|
1940
|
+
cluster_management_stack_version=cluster_management_stack_version,
|
|
1941
|
+
auto_add_user=auto_add_user,
|
|
1942
|
+
external_id=external_id,
|
|
1943
|
+
)
|
|
1944
|
+
)
|
|
1945
|
+
cloud_id = created_cloud.result.id
|
|
1946
|
+
self.cloud_event_producer.set_cloud_id(cloud_id)
|
|
1947
|
+
self.cloud_event_producer.produce(
|
|
1948
|
+
CloudAnalyticsEventName.CLOUD_RECORD_INSERTED, succeeded=True
|
|
1949
|
+
)
|
|
1950
|
+
except ClickException as e:
|
|
1951
|
+
self.cloud_event_producer.produce(
|
|
1952
|
+
CloudAnalyticsEventName.CLOUD_RECORD_INSERTED,
|
|
1953
|
+
succeeded=False,
|
|
1954
|
+
internal_error=str(e),
|
|
1955
|
+
)
|
|
1956
|
+
raise
|
|
1957
|
+
|
|
1958
|
+
try:
|
|
1959
|
+
# The Anyscale IAM role is optional for the K8s stack.
|
|
1960
|
+
has_anyscale_iam_role = compute_stack == ComputeStack.VM or (
|
|
1961
|
+
compute_stack == ComputeStack.K8S and anyscale_iam_role_id
|
|
1962
|
+
)
|
|
1963
|
+
|
|
1964
|
+
iam_role_original_policy = None
|
|
1965
|
+
if has_anyscale_iam_role:
|
|
1966
|
+
# Update anyscale IAM role's assume policy to include the cloud id as the external ID
|
|
1967
|
+
role = _get_role(
|
|
1968
|
+
AwsRoleArn.from_string(anyscale_iam_role_id).to_role_name(), region
|
|
1969
|
+
)
|
|
1970
|
+
if role is None:
|
|
1971
|
+
self.log.log_resource_error(
|
|
1972
|
+
CloudAnalyticsEventCloudResource.AWS_IAM_ROLE,
|
|
1973
|
+
CloudSetupError.RESOURCE_NOT_FOUND,
|
|
1974
|
+
)
|
|
1975
|
+
raise ClickException(
|
|
1976
|
+
f"Failed to access IAM role {anyscale_iam_role_id}."
|
|
1977
|
+
)
|
|
1978
|
+
|
|
1979
|
+
iam_role_original_policy = role.assume_role_policy_document # type: ignore
|
|
1980
|
+
if external_id is None:
|
|
1981
|
+
try:
|
|
1982
|
+
new_policy = _update_external_ids_for_policy(
|
|
1983
|
+
iam_role_original_policy, cloud_id
|
|
1984
|
+
)
|
|
1985
|
+
role.AssumeRolePolicy().update(PolicyDocument=json.dumps(new_policy)) # type: ignore
|
|
1986
|
+
except ClientError as e:
|
|
1987
|
+
self.log.log_resource_exception(
|
|
1988
|
+
CloudAnalyticsEventCloudResource.AWS_IAM_ROLE, e
|
|
1989
|
+
)
|
|
1990
|
+
raise e
|
|
1991
|
+
else:
|
|
1992
|
+
fetched_external_ids = [
|
|
1993
|
+
statement.setdefault("Condition", {})
|
|
1994
|
+
.setdefault("StringEquals", {})
|
|
1995
|
+
.setdefault("sts:ExternalId", [])
|
|
1996
|
+
for statement in iam_role_original_policy.get("Statement", []) # type: ignore
|
|
1997
|
+
]
|
|
1998
|
+
external_id_in_policy = all(
|
|
1999
|
+
external_id == fetched_external_id
|
|
2000
|
+
if isinstance(fetched_external_id, str)
|
|
2001
|
+
else external_id in fetched_external_id
|
|
2002
|
+
for fetched_external_id in fetched_external_ids
|
|
2003
|
+
)
|
|
2004
|
+
if not external_id_in_policy:
|
|
2005
|
+
raise ClickException(
|
|
2006
|
+
f"External ID {external_id} is not in the assume role policy of {anyscale_iam_role_id}."
|
|
2007
|
+
)
|
|
2008
|
+
|
|
2009
|
+
# When running on the VM compute stack, validate and retrieve the EFS mount target IP.
|
|
2010
|
+
# When running on the K8S compute stack, EFS is optional; if efs_id is provided, then
|
|
2011
|
+
# validate and retrieve the EFS mount target IP.
|
|
2012
|
+
enable_efs = compute_stack == ComputeStack.VM or (
|
|
2013
|
+
compute_stack == ComputeStack.K8S and efs_id
|
|
2014
|
+
)
|
|
2015
|
+
if enable_efs:
|
|
2016
|
+
try:
|
|
2017
|
+
boto3_session = boto3.Session(region_name=region)
|
|
2018
|
+
aws_efs_mount_target_ip = _get_aws_efs_mount_target_ip(
|
|
2019
|
+
boto3_session, efs_id
|
|
2020
|
+
)
|
|
2021
|
+
except ClientError as e:
|
|
2022
|
+
self.log.log_resource_exception(
|
|
2023
|
+
CloudAnalyticsEventCloudResource.AWS_EFS, e
|
|
2024
|
+
)
|
|
2025
|
+
raise e
|
|
2026
|
+
else:
|
|
2027
|
+
boto3_session = None
|
|
2028
|
+
aws_efs_mount_target_ip = None
|
|
2029
|
+
|
|
2030
|
+
# When running on the VM compute stack, associate the AWS subnets with their availability zones.
|
|
2031
|
+
if compute_stack == ComputeStack.VM:
|
|
2032
|
+
aws_subnet_ids_with_availability_zones = associate_aws_subnets_with_azs(
|
|
2033
|
+
subnet_ids, region, self.log
|
|
2034
|
+
)
|
|
2035
|
+
else:
|
|
2036
|
+
aws_subnet_ids_with_availability_zones = None
|
|
2037
|
+
|
|
2038
|
+
# If memorydb cluster is provided, get the memorydb cluster config.
|
|
2039
|
+
if memorydb_cluster_id is not None:
|
|
2040
|
+
memorydb_cluster_config = _get_memorydb_cluster_config(
|
|
2041
|
+
memorydb_cluster_id, region, self.log
|
|
2042
|
+
)
|
|
2043
|
+
else:
|
|
2044
|
+
memorydb_cluster_config = None
|
|
2045
|
+
|
|
2046
|
+
self.cloud_event_producer.produce(
|
|
2047
|
+
CloudAnalyticsEventName.PREPROCESS_COMPLETE, succeeded=True
|
|
2048
|
+
)
|
|
2049
|
+
except Exception as e: # noqa: BLE001
|
|
2050
|
+
error = str(e)
|
|
2051
|
+
self.log.error(error)
|
|
2052
|
+
error_msg_for_event = str(e)
|
|
2053
|
+
if isinstance(e, NoCredentialsError):
|
|
2054
|
+
# If it is a credentials error, rewrite the error to be more clear
|
|
2055
|
+
error = "Unable to locate AWS credentials. Cloud registration requires valid AWS credentials to be set locally. Learn more: https://boto3.amazonaws.com/v1/documentation/api/latest/guide/credentials.html"
|
|
2056
|
+
error_msg_for_event = "Unable to locate AWS credentials."
|
|
2057
|
+
self.cloud_event_producer.produce(
|
|
2058
|
+
CloudAnalyticsEventName.PREPROCESS_COMPLETE,
|
|
2059
|
+
succeeded=False,
|
|
2060
|
+
logger=self.log,
|
|
2061
|
+
internal_error=error_msg_for_event,
|
|
2062
|
+
)
|
|
2063
|
+
# Delete the cloud if registering the cloud fails
|
|
2064
|
+
self.api_client.delete_cloud_api_v2_clouds_cloud_id_delete(
|
|
2065
|
+
cloud_id=cloud_id
|
|
2066
|
+
)
|
|
2067
|
+
try:
|
|
2068
|
+
if iam_role_original_policy is not None and external_id is None:
|
|
2069
|
+
# Revert the assume policy back to the original policy
|
|
2070
|
+
role.AssumeRolePolicy().update( # type: ignore
|
|
2071
|
+
PolicyDocument=json.dumps(iam_role_original_policy)
|
|
2072
|
+
)
|
|
2073
|
+
except Exception as revert_error: # noqa: BLE001
|
|
2074
|
+
raise ClickException(
|
|
2075
|
+
f"Cloud registration failed! {error}. Failed to revert the trust policy back to the original policy. {revert_error}"
|
|
2076
|
+
)
|
|
2077
|
+
raise ClickException(f"Cloud registration failed! {error}")
|
|
2078
|
+
|
|
2079
|
+
aws_iam_role_arns = None
|
|
2080
|
+
if compute_stack == ComputeStack.VM:
|
|
2081
|
+
aws_iam_role_arns = [anyscale_iam_role_id, instance_iam_role_id]
|
|
2082
|
+
elif compute_stack == ComputeStack.K8S and anyscale_iam_role_id:
|
|
2083
|
+
aws_iam_role_arns = [anyscale_iam_role_id]
|
|
2084
|
+
|
|
2085
|
+
try:
|
|
2086
|
+
# Verify cloud resources meet our requirement
|
|
2087
|
+
create_cloud_resource = CreateCloudResource(
|
|
2088
|
+
aws_vpc_id=vpc_id,
|
|
2089
|
+
aws_subnet_ids_with_availability_zones=aws_subnet_ids_with_availability_zones,
|
|
2090
|
+
aws_iam_role_arns=aws_iam_role_arns,
|
|
2091
|
+
aws_security_groups=security_group_ids,
|
|
2092
|
+
aws_s3_id=cloud_storage_bucket_name[len(S3_STORAGE_PREFIX) :],
|
|
2093
|
+
aws_efs_id=efs_id,
|
|
2094
|
+
aws_efs_mount_target_ip=aws_efs_mount_target_ip,
|
|
2095
|
+
memorydb_cluster_config=memorydb_cluster_config,
|
|
2096
|
+
compute_stack=compute_stack,
|
|
2097
|
+
kubernetes_zones=kubernetes_zones,
|
|
2098
|
+
kubernetes_dataplane_identity=anyscale_operator_iam_identity,
|
|
2099
|
+
cloud_storage_bucket_name=cloud_storage_bucket_name,
|
|
2100
|
+
)
|
|
2101
|
+
|
|
2102
|
+
# Verification is only performed for VM compute stack.
|
|
2103
|
+
# TODO (shomilj): Add verification to the K8S compute stack as well.
|
|
2104
|
+
if compute_stack == ComputeStack.VM:
|
|
2105
|
+
with self.log.spinner("Verifying cloud resources...") as spinner:
|
|
2106
|
+
assert boto3_session is not None
|
|
2107
|
+
if not skip_verifications and not self.verify_aws_cloud_resources(
|
|
2108
|
+
cloud_resource=create_cloud_resource,
|
|
2109
|
+
boto3_session=boto3_session,
|
|
2110
|
+
region=region,
|
|
2111
|
+
is_bring_your_own_resource=True,
|
|
2112
|
+
is_private_network=private_network,
|
|
2113
|
+
cloud_id=cloud_id,
|
|
2114
|
+
logger=CloudSetupLogger(spinner_manager=spinner),
|
|
2115
|
+
):
|
|
2116
|
+
raise ClickException(
|
|
2117
|
+
"Please make sure all the resources provided meet the requirements and try again."
|
|
2118
|
+
)
|
|
2119
|
+
|
|
2120
|
+
confirm(
|
|
2121
|
+
"Please review the output from verification for any warnings. Would you like to proceed with cloud creation?",
|
|
2122
|
+
yes,
|
|
2123
|
+
)
|
|
2124
|
+
self.cloud_event_producer.produce(
|
|
2125
|
+
CloudAnalyticsEventName.RESOURCES_VERIFIED, succeeded=True
|
|
2126
|
+
)
|
|
2127
|
+
# Since the verify runs for a while, it's possible the user aborts the process, which throws a KeyboardInterrupt.
|
|
2128
|
+
except (Exception, KeyboardInterrupt) as e: # noqa: BLE001
|
|
2129
|
+
self.log.error(str(e))
|
|
2130
|
+
internal_error = str(e)
|
|
2131
|
+
if isinstance(e, Abort):
|
|
2132
|
+
internal_error = "User aborted."
|
|
2133
|
+
self.cloud_event_producer.produce(
|
|
2134
|
+
CloudAnalyticsEventName.RESOURCES_VERIFIED,
|
|
2135
|
+
succeeded=False,
|
|
2136
|
+
logger=self.log,
|
|
2137
|
+
internal_error=internal_error,
|
|
2138
|
+
)
|
|
2139
|
+
# Delete the cloud if registering the cloud fails
|
|
2140
|
+
self.api_client.delete_cloud_api_v2_clouds_cloud_id_delete(
|
|
2141
|
+
cloud_id=cloud_id
|
|
2142
|
+
)
|
|
2143
|
+
try:
|
|
2144
|
+
if iam_role_original_policy is not None and external_id is None:
|
|
2145
|
+
# Revert the assume policy back to the original policy
|
|
2146
|
+
role.AssumeRolePolicy().update( # type: ignore
|
|
2147
|
+
PolicyDocument=json.dumps(iam_role_original_policy)
|
|
2148
|
+
)
|
|
2149
|
+
except Exception as revert_error: # noqa: BLE001
|
|
2150
|
+
raise ClickException(
|
|
2151
|
+
f"Cloud registration failed! {e}. Failed to revert the trust policy back to the original policy. {revert_error}"
|
|
2152
|
+
)
|
|
2153
|
+
|
|
2154
|
+
raise ClickException(f"Cloud registration failed! {e}")
|
|
2155
|
+
|
|
2156
|
+
try:
|
|
2157
|
+
with self.log.spinner(
|
|
2158
|
+
"Updating Anyscale cloud with cloud resource..."
|
|
2159
|
+
) as spinner:
|
|
2160
|
+
# update cloud with verified cloud resources
|
|
2161
|
+
cloud_resource = self.api_client.update_cloud_with_cloud_resource_api_v2_clouds_with_cloud_resource_router_cloud_id_put(
|
|
2162
|
+
cloud_id=cloud_id,
|
|
2163
|
+
update_cloud_with_cloud_resource=UpdateCloudWithCloudResource(
|
|
2164
|
+
cloud_resource_to_update=create_cloud_resource,
|
|
2165
|
+
),
|
|
2166
|
+
)
|
|
2167
|
+
# For now, only wait for the cloud to be active if the compute stack is VM.
|
|
2168
|
+
# TODO (shomilj): support this fully for Kubernetes after provider metadata
|
|
2169
|
+
# checks are removed.
|
|
2170
|
+
if compute_stack == ComputeStack.VM:
|
|
2171
|
+
self.wait_for_cloud_to_be_active(cloud_id, CloudProviders.AWS)
|
|
2172
|
+
if compute_stack == ComputeStack.K8S:
|
|
2173
|
+
cloud_resource_id = cloud_resource.result.cloud_resource.id
|
|
2174
|
+
self.log.info(
|
|
2175
|
+
f"For registering this cloud's Kubernetes Manager, use cloud deployment ID '{cloud_resource_id}'."
|
|
2176
|
+
)
|
|
2177
|
+
self.cloud_event_producer.produce(
|
|
2178
|
+
CloudAnalyticsEventName.INFRA_SETUP_COMPLETE, succeeded=True
|
|
2179
|
+
)
|
|
2180
|
+
# Since the update runs for a while, it's possible the user aborts the process, which throws a KeyboardInterrupt.
|
|
2181
|
+
except (Exception, KeyboardInterrupt) as e: # noqa: BLE001
|
|
2182
|
+
# Delete the cloud if registering the cloud fails
|
|
2183
|
+
self.log.error(str(e))
|
|
2184
|
+
self.cloud_event_producer.produce(
|
|
2185
|
+
CloudAnalyticsEventName.INFRA_SETUP_COMPLETE,
|
|
2186
|
+
succeeded=False,
|
|
2187
|
+
internal_error=str(e),
|
|
2188
|
+
)
|
|
2189
|
+
self.api_client.delete_cloud_api_v2_clouds_cloud_id_delete(
|
|
2190
|
+
cloud_id=cloud_id
|
|
2191
|
+
)
|
|
2192
|
+
try:
|
|
2193
|
+
if iam_role_original_policy is not None and external_id is None:
|
|
2194
|
+
# Revert the assume policy back to the original policy
|
|
2195
|
+
role.AssumeRolePolicy().update( # type: ignore
|
|
2196
|
+
PolicyDocument=json.dumps(iam_role_original_policy)
|
|
2197
|
+
)
|
|
2198
|
+
except Exception as revert_error: # noqa: BLE001
|
|
2199
|
+
raise ClickException(
|
|
2200
|
+
f"Cloud registration failed! {e}. Failed to revert the trust policy back to the original policy. {revert_error}"
|
|
2201
|
+
)
|
|
2202
|
+
|
|
2203
|
+
raise ClickException(f"Cloud registration failed! {e}")
|
|
2204
|
+
|
|
2205
|
+
self.log.info(f"Successfully created cloud {name}, and it's ready to use.")
|
|
2206
|
+
|
|
2207
|
+
if len(functions_to_verify) > 0:
|
|
2208
|
+
CloudFunctionalVerificationController(
|
|
2209
|
+
self.cloud_event_producer, self.log
|
|
2210
|
+
).start_verification(cloud_id, CloudProviders.AWS, functions_to_verify, yes)
|
|
2211
|
+
|
|
2212
|
+
def verify_gcp_cloud_resources(
|
|
2213
|
+
self,
|
|
2214
|
+
*,
|
|
2215
|
+
cloud_resource: CreateCloudResourceGCP,
|
|
2216
|
+
project_id: str,
|
|
2217
|
+
region: str,
|
|
2218
|
+
cloud_id: str,
|
|
2219
|
+
yes: bool,
|
|
2220
|
+
host_project_id: Optional[str] = None,
|
|
2221
|
+
factory: Any = None,
|
|
2222
|
+
strict: bool = False,
|
|
2223
|
+
is_private_service_cloud: bool = False,
|
|
2224
|
+
) -> bool:
|
|
2225
|
+
gcp_utils = try_import_gcp_utils()
|
|
2226
|
+
if not factory:
|
|
2227
|
+
factory = gcp_utils.get_google_cloud_client_factory(self.log, project_id)
|
|
2228
|
+
GCPLogger = gcp_utils.GCPLogger
|
|
2229
|
+
verify_lib = try_import_gcp_verify_lib()
|
|
2230
|
+
|
|
2231
|
+
network_project_id = host_project_id if host_project_id else project_id
|
|
2232
|
+
use_shared_vpc = bool(host_project_id)
|
|
2233
|
+
|
|
2234
|
+
with self.log.spinner("Verifying cloud resources...") as spinner:
|
|
2235
|
+
gcp_logger = GCPLogger(self.log, project_id, spinner, yes)
|
|
2236
|
+
verify_gcp_project_result = verify_lib.verify_gcp_project(
|
|
2237
|
+
factory, cloud_resource, project_id, gcp_logger, strict=strict
|
|
2238
|
+
)
|
|
2239
|
+
verify_gcp_access_service_account_result = verify_lib.verify_gcp_access_service_account(
|
|
2240
|
+
factory, cloud_resource, project_id, gcp_logger
|
|
2241
|
+
)
|
|
2242
|
+
verify_gcp_dataplane_service_account_result = verify_lib.verify_gcp_dataplane_service_account(
|
|
2243
|
+
factory, cloud_resource, project_id, gcp_logger, strict=strict
|
|
2244
|
+
)
|
|
2245
|
+
verify_gcp_networking_result = verify_lib.verify_gcp_networking(
|
|
2246
|
+
factory,
|
|
2247
|
+
cloud_resource,
|
|
2248
|
+
network_project_id,
|
|
2249
|
+
region,
|
|
2250
|
+
gcp_logger,
|
|
2251
|
+
strict=strict,
|
|
2252
|
+
is_private_service_cloud=is_private_service_cloud,
|
|
2253
|
+
)
|
|
2254
|
+
verify_firewall_policy_result = verify_lib.verify_firewall_policy(
|
|
2255
|
+
factory,
|
|
2256
|
+
cloud_resource,
|
|
2257
|
+
network_project_id,
|
|
2258
|
+
region,
|
|
2259
|
+
use_shared_vpc,
|
|
2260
|
+
is_private_service_cloud,
|
|
2261
|
+
gcp_logger,
|
|
2262
|
+
strict=strict,
|
|
2263
|
+
)
|
|
2264
|
+
verify_filestore_result = verify_lib.verify_filestore(
|
|
2265
|
+
factory, cloud_resource, region, gcp_logger, strict=strict
|
|
2266
|
+
)
|
|
2267
|
+
verify_cloud_storage_result = verify_lib.verify_cloud_storage(
|
|
2268
|
+
factory, cloud_resource, project_id, region, gcp_logger, strict=strict,
|
|
2269
|
+
)
|
|
2270
|
+
verify_anyscale_access_result = verify_anyscale_access(
|
|
2271
|
+
self.api_client, cloud_id, CloudProviders.GCP, self.log
|
|
2272
|
+
)
|
|
2273
|
+
verify_memorystore_result = True
|
|
2274
|
+
if cloud_resource.memorystore_instance_config is not None:
|
|
2275
|
+
verify_memorystore_result = verify_lib.verify_memorystore(
|
|
2276
|
+
factory, cloud_resource, gcp_logger, strict=strict,
|
|
2277
|
+
)
|
|
2278
|
+
|
|
2279
|
+
verification_results = [
|
|
2280
|
+
"Verification result:",
|
|
2281
|
+
f"anyscale access: {self._passed_or_failed_str_from_bool(verify_anyscale_access_result)}",
|
|
2282
|
+
f"project: {self._passed_or_failed_str_from_bool(verify_gcp_project_result)}",
|
|
2283
|
+
f"vpc and subnet: {self._passed_or_failed_str_from_bool(verify_gcp_networking_result)}",
|
|
2284
|
+
f"anyscale iam service account: {self._passed_or_failed_str_from_bool(verify_gcp_access_service_account_result)}",
|
|
2285
|
+
f"cluster node service account: {self._passed_or_failed_str_from_bool(verify_gcp_dataplane_service_account_result)}",
|
|
2286
|
+
f"firewall policy: {self._passed_or_failed_str_from_bool(verify_firewall_policy_result)}",
|
|
2287
|
+
f"filestore: {self._passed_or_failed_str_from_bool(verify_filestore_result)}",
|
|
2288
|
+
f"cloud storage: {self._passed_or_failed_str_from_bool(verify_cloud_storage_result)}",
|
|
2289
|
+
]
|
|
2290
|
+
|
|
2291
|
+
if cloud_resource.memorystore_instance_config is not None:
|
|
2292
|
+
verification_results.append(
|
|
2293
|
+
f"memorystore: {self._passed_or_failed_str_from_bool(verify_memorystore_result)}"
|
|
2294
|
+
)
|
|
2295
|
+
|
|
2296
|
+
self.log.info("\n".join(verification_results))
|
|
2297
|
+
|
|
2298
|
+
return all(
|
|
2299
|
+
[
|
|
2300
|
+
verify_anyscale_access_result,
|
|
2301
|
+
verify_gcp_project_result,
|
|
2302
|
+
verify_gcp_access_service_account_result,
|
|
2303
|
+
verify_gcp_dataplane_service_account_result,
|
|
2304
|
+
verify_gcp_networking_result,
|
|
2305
|
+
verify_firewall_policy_result,
|
|
2306
|
+
verify_filestore_result,
|
|
2307
|
+
verify_cloud_storage_result,
|
|
2308
|
+
verify_memorystore_result,
|
|
2309
|
+
]
|
|
2310
|
+
)
|
|
2311
|
+
|
|
2312
|
+
def register_gcp_cloud( # noqa: PLR0913, PLR0912, C901
|
|
2313
|
+
self,
|
|
2314
|
+
*,
|
|
2315
|
+
region: str,
|
|
2316
|
+
name: str,
|
|
2317
|
+
project_id: str,
|
|
2318
|
+
vpc_name: str,
|
|
2319
|
+
subnet_names: List[str],
|
|
2320
|
+
filestore_instance_id: str,
|
|
2321
|
+
filestore_location: str,
|
|
2322
|
+
anyscale_service_account_email: str,
|
|
2323
|
+
instance_service_account_email: str,
|
|
2324
|
+
provider_id: str,
|
|
2325
|
+
firewall_policy_names: List[str],
|
|
2326
|
+
cloud_storage_bucket_name: str,
|
|
2327
|
+
memorystore_instance_name: Optional[str],
|
|
2328
|
+
functional_verify: Optional[str],
|
|
2329
|
+
private_network: bool,
|
|
2330
|
+
cluster_management_stack_version: ClusterManagementStackVersions,
|
|
2331
|
+
host_project_id: Optional[str] = None,
|
|
2332
|
+
yes: bool = False,
|
|
2333
|
+
skip_verifications: bool = False,
|
|
2334
|
+
auto_add_user: bool = False,
|
|
2335
|
+
# Default to ComputeStack.VM for backwards compatibility
|
|
2336
|
+
# for SDK users who do not specify a compute stack here.
|
|
2337
|
+
compute_stack: ComputeStack = ComputeStack.VM,
|
|
2338
|
+
kubernetes_zones: Optional[List[str]] = None,
|
|
2339
|
+
anyscale_operator_iam_identity: Optional[str] = None,
|
|
2340
|
+
):
|
|
2341
|
+
functions_to_verify = self._validate_functional_verification_args(
|
|
2342
|
+
functional_verify
|
|
2343
|
+
)
|
|
2344
|
+
gcp_utils = try_import_gcp_utils()
|
|
2345
|
+
|
|
2346
|
+
# Create a cloud without cloud resources first
|
|
2347
|
+
# Provider ID is optional for K8s clouds.
|
|
2348
|
+
if compute_stack != ComputeStack.K8S:
|
|
2349
|
+
provider_id_re_result = re.search(
|
|
2350
|
+
"projects\\/[0-9]*\\/locations\\/global\\/workloadIdentityPools\\/.+\\/providers\\/[a-z0-9-]*$",
|
|
2351
|
+
provider_id,
|
|
2352
|
+
)
|
|
2353
|
+
if provider_id_re_result is None:
|
|
2354
|
+
raise ClickException(
|
|
2355
|
+
f"Invalid provider_id {provider_id}. Only lowercase letters, numbers, and dashes are allowed."
|
|
2356
|
+
)
|
|
2357
|
+
|
|
2358
|
+
self.cloud_event_producer.init_trace_context(
|
|
2359
|
+
CloudAnalyticsEventCommandName.REGISTER, CloudProviders.GCP
|
|
2360
|
+
)
|
|
2361
|
+
self.cloud_event_producer.produce(
|
|
2362
|
+
CloudAnalyticsEventName.COMMAND_START, succeeded=True
|
|
2363
|
+
)
|
|
2364
|
+
|
|
2365
|
+
try:
|
|
2366
|
+
credentials_dict = {
|
|
2367
|
+
"project_id": project_id or "",
|
|
2368
|
+
"provider_id": provider_id or "",
|
|
2369
|
+
"service_account_email": anyscale_service_account_email or "",
|
|
2370
|
+
}
|
|
2371
|
+
if host_project_id:
|
|
2372
|
+
credentials_dict["host_project_id"] = host_project_id
|
|
2373
|
+
credentials = json.dumps(credentials_dict)
|
|
2374
|
+
|
|
2375
|
+
if compute_stack == ComputeStack.K8S:
|
|
2376
|
+
# On K8S, we don't need to collect credentials;
|
|
2377
|
+
# instead, write a random trace_id into this field
|
|
2378
|
+
# to maintain the property that each cloud's
|
|
2379
|
+
# credentials are unique.
|
|
2380
|
+
random_id = uuid.uuid4().hex
|
|
2381
|
+
credentials = json.dumps(
|
|
2382
|
+
{
|
|
2383
|
+
"provider_id": "",
|
|
2384
|
+
"project_id": random_id,
|
|
2385
|
+
"service_account_email": random_id,
|
|
2386
|
+
}
|
|
2387
|
+
)
|
|
2388
|
+
|
|
2389
|
+
# NOTE: For now we set the is_private_service_cloud to be the same as is_private_cloud
|
|
2390
|
+
# We don't expose this to the user yet since it's not recommended.
|
|
2391
|
+
is_private_service_cloud = private_network
|
|
2392
|
+
|
|
2393
|
+
created_cloud = self.api_client.create_cloud_api_v2_clouds_post(
|
|
2394
|
+
write_cloud=WriteCloud(
|
|
2395
|
+
provider="GCP",
|
|
2396
|
+
region=region,
|
|
2397
|
+
credentials=credentials,
|
|
2398
|
+
name=name,
|
|
2399
|
+
is_bring_your_own_resource=True,
|
|
2400
|
+
is_private_cloud=private_network,
|
|
2401
|
+
cluster_management_stack_version=cluster_management_stack_version,
|
|
2402
|
+
is_private_service_cloud=is_private_service_cloud,
|
|
2403
|
+
auto_add_user=auto_add_user,
|
|
2404
|
+
)
|
|
2405
|
+
)
|
|
2406
|
+
cloud_id = created_cloud.result.id
|
|
2407
|
+
self.cloud_event_producer.set_cloud_id(cloud_id)
|
|
2408
|
+
self.cloud_event_producer.produce(
|
|
2409
|
+
CloudAnalyticsEventName.CLOUD_RECORD_INSERTED, succeeded=True
|
|
2410
|
+
)
|
|
2411
|
+
except ClickException as e:
|
|
2412
|
+
self.cloud_event_producer.produce(
|
|
2413
|
+
CloudAnalyticsEventName.CLOUD_RECORD_INSERTED,
|
|
2414
|
+
succeeded=False,
|
|
2415
|
+
internal_error=str(e),
|
|
2416
|
+
)
|
|
2417
|
+
raise
|
|
2418
|
+
|
|
2419
|
+
try:
|
|
2420
|
+
# Set defaults for Kubernetes clouds.
|
|
2421
|
+
if compute_stack == ComputeStack.K8S:
|
|
2422
|
+
instance_service_account_email = ""
|
|
2423
|
+
subnet_names = []
|
|
2424
|
+
|
|
2425
|
+
enable_filestore = compute_stack == ComputeStack.VM or (
|
|
2426
|
+
filestore_location and filestore_instance_id
|
|
2427
|
+
)
|
|
2428
|
+
|
|
2429
|
+
# Normally, for Kubernetes clouds, we don't need a VPC name, since networking is managed by Kubernetes.
|
|
2430
|
+
# For Kubernetes clouds on GCP where Filestore is enabled, we require the VPC name, since it is needed
|
|
2431
|
+
# to look up the relevant Mount Target IP for Filestore in the VPC.
|
|
2432
|
+
if compute_stack == ComputeStack.K8S:
|
|
2433
|
+
if enable_filestore and not vpc_name:
|
|
2434
|
+
raise ClickException(
|
|
2435
|
+
"Please provide the name of the VPC that your Kubernetes cluster is running inside of."
|
|
2436
|
+
)
|
|
2437
|
+
if (enable_filestore or memorystore_instance_name) and not project_id:
|
|
2438
|
+
raise ClickException("Please provide a project ID.")
|
|
2439
|
+
|
|
2440
|
+
if project_id:
|
|
2441
|
+
factory = gcp_utils.get_google_cloud_client_factory(
|
|
2442
|
+
self.log, project_id
|
|
2443
|
+
)
|
|
2444
|
+
|
|
2445
|
+
if enable_filestore:
|
|
2446
|
+
filestore_config = gcp_utils.get_gcp_filestore_config(
|
|
2447
|
+
factory,
|
|
2448
|
+
project_id,
|
|
2449
|
+
vpc_name,
|
|
2450
|
+
filestore_location,
|
|
2451
|
+
filestore_instance_id,
|
|
2452
|
+
self.log,
|
|
2453
|
+
)
|
|
2454
|
+
else:
|
|
2455
|
+
filestore_config = GCPFileStoreConfig(
|
|
2456
|
+
instance_name="", mount_target_ip="", root_dir=""
|
|
2457
|
+
)
|
|
2458
|
+
vpc_name = ""
|
|
2459
|
+
|
|
2460
|
+
if memorystore_instance_name:
|
|
2461
|
+
memorystore_instance_config = gcp_utils.get_gcp_memorystore_config(
|
|
2462
|
+
factory, memorystore_instance_name
|
|
2463
|
+
)
|
|
2464
|
+
else:
|
|
2465
|
+
memorystore_instance_config = None
|
|
2466
|
+
|
|
2467
|
+
if not cloud_storage_bucket_name.startswith(GCS_STORAGE_PREFIX):
|
|
2468
|
+
cloud_storage_bucket_name = (
|
|
2469
|
+
GCS_STORAGE_PREFIX + cloud_storage_bucket_name
|
|
2470
|
+
)
|
|
2471
|
+
|
|
2472
|
+
# Verify cloud resources meet our requirement
|
|
2473
|
+
create_cloud_resource_gcp = CreateCloudResourceGCP(
|
|
2474
|
+
gcp_vpc_id=vpc_name,
|
|
2475
|
+
gcp_subnet_ids=subnet_names,
|
|
2476
|
+
gcp_cluster_node_service_account_email=instance_service_account_email,
|
|
2477
|
+
gcp_anyscale_iam_service_account_email=anyscale_service_account_email
|
|
2478
|
+
or "",
|
|
2479
|
+
gcp_filestore_config=filestore_config,
|
|
2480
|
+
gcp_firewall_policy_ids=firewall_policy_names,
|
|
2481
|
+
gcp_cloud_storage_bucket_id=cloud_storage_bucket_name[
|
|
2482
|
+
len(GCS_STORAGE_PREFIX) :
|
|
2483
|
+
],
|
|
2484
|
+
memorystore_instance_config=memorystore_instance_config,
|
|
2485
|
+
compute_stack=compute_stack,
|
|
2486
|
+
kubernetes_zones=kubernetes_zones,
|
|
2487
|
+
kubernetes_dataplane_identity=anyscale_operator_iam_identity,
|
|
2488
|
+
cloud_storage_bucket_name=cloud_storage_bucket_name,
|
|
2489
|
+
)
|
|
2490
|
+
|
|
2491
|
+
# Verification is only performed for VM compute stack.
|
|
2492
|
+
# TODO (shomilj): Add verification to the K8S compute stack as well.
|
|
2493
|
+
if compute_stack == ComputeStack.VM:
|
|
2494
|
+
if not skip_verifications and not self.verify_gcp_cloud_resources(
|
|
2495
|
+
cloud_resource=create_cloud_resource_gcp,
|
|
2496
|
+
project_id=project_id,
|
|
2497
|
+
host_project_id=host_project_id,
|
|
2498
|
+
region=region,
|
|
2499
|
+
cloud_id=cloud_id,
|
|
2500
|
+
yes=yes,
|
|
2501
|
+
factory=factory,
|
|
2502
|
+
is_private_service_cloud=is_private_service_cloud,
|
|
2503
|
+
):
|
|
2504
|
+
raise ClickException(
|
|
2505
|
+
"Please make sure all the resources provided meet the requirements and try again."
|
|
2506
|
+
)
|
|
2507
|
+
|
|
2508
|
+
confirm(
|
|
2509
|
+
"Please review the output from verification for any warnings. Would you like to proceed with cloud creation?",
|
|
2510
|
+
yes,
|
|
2511
|
+
)
|
|
2512
|
+
self.cloud_event_producer.produce(
|
|
2513
|
+
CloudAnalyticsEventName.RESOURCES_VERIFIED, succeeded=True
|
|
2514
|
+
)
|
|
2515
|
+
except Exception as e: # noqa: BLE001
|
|
2516
|
+
self.log.error(str(e))
|
|
2517
|
+
internal_error = str(e)
|
|
2518
|
+
if isinstance(e, Abort):
|
|
2519
|
+
internal_error = "User aborted."
|
|
2520
|
+
self.cloud_event_producer.produce(
|
|
2521
|
+
CloudAnalyticsEventName.RESOURCES_VERIFIED,
|
|
2522
|
+
succeeded=False,
|
|
2523
|
+
logger=self.log,
|
|
2524
|
+
internal_error=internal_error,
|
|
2525
|
+
)
|
|
2526
|
+
|
|
2527
|
+
# Delete the cloud if registering the cloud fails
|
|
2528
|
+
self.api_client.delete_cloud_api_v2_clouds_cloud_id_delete(
|
|
2529
|
+
cloud_id=cloud_id
|
|
2530
|
+
)
|
|
2531
|
+
raise ClickException(f"Cloud registration failed! {e}")
|
|
2532
|
+
|
|
2533
|
+
try:
|
|
2534
|
+
# update cloud with verified cloud resources
|
|
2535
|
+
with self.log.spinner("Updating Anyscale cloud with cloud resources..."):
|
|
2536
|
+
cloud_resource = self.api_client.update_cloud_with_cloud_resource_api_v2_clouds_with_cloud_resource_gcp_router_cloud_id_put(
|
|
2537
|
+
cloud_id=cloud_id,
|
|
2538
|
+
update_cloud_with_cloud_resource_gcp=UpdateCloudWithCloudResourceGCP(
|
|
2539
|
+
cloud_resource_to_update=create_cloud_resource_gcp,
|
|
2540
|
+
),
|
|
2541
|
+
)
|
|
2542
|
+
# For now, only wait for the cloud to be active if the compute stack is VM.
|
|
2543
|
+
# TODO (shomilj): support this fully for Kubernetes after provider metadata
|
|
2544
|
+
# checks are removed.
|
|
2545
|
+
if compute_stack == ComputeStack.VM:
|
|
2546
|
+
self.wait_for_cloud_to_be_active(cloud_id, CloudProviders.GCP)
|
|
2547
|
+
if compute_stack == ComputeStack.K8S:
|
|
2548
|
+
cloud_resource_id = cloud_resource.result.cloud_resource.id
|
|
2549
|
+
self.log.info(
|
|
2550
|
+
f"For registering this cloud's Kubernetes Manager, use cloud deployment ID '{cloud_resource_id}'."
|
|
2551
|
+
)
|
|
2552
|
+
self.cloud_event_producer.produce(
|
|
2553
|
+
CloudAnalyticsEventName.INFRA_SETUP_COMPLETE, succeeded=True
|
|
2554
|
+
)
|
|
2555
|
+
except Exception as e: # noqa: BLE001
|
|
2556
|
+
self.log.error(str(e))
|
|
2557
|
+
self.cloud_event_producer.produce(
|
|
2558
|
+
CloudAnalyticsEventName.INFRA_SETUP_COMPLETE,
|
|
2559
|
+
succeeded=False,
|
|
2560
|
+
internal_error=str(e),
|
|
2561
|
+
)
|
|
2562
|
+
# Delete the cloud if registering the cloud fails
|
|
2563
|
+
self.api_client.delete_cloud_api_v2_clouds_cloud_id_delete(
|
|
2564
|
+
cloud_id=cloud_id
|
|
2565
|
+
)
|
|
2566
|
+
raise ClickException(f"Cloud registration failed! {e}")
|
|
2567
|
+
|
|
2568
|
+
self.log.info(f"Successfully created cloud {name}, and it's ready to use.")
|
|
2569
|
+
|
|
2570
|
+
if len(functions_to_verify) > 0:
|
|
2571
|
+
CloudFunctionalVerificationController(
|
|
2572
|
+
self.cloud_event_producer, self.log
|
|
2573
|
+
).start_verification(cloud_id, CloudProviders.GCP, functions_to_verify, yes)
|
|
2574
|
+
|
|
2575
|
+
def delete_cloud( # noqa: PLR0912, C901
|
|
2576
|
+
self,
|
|
2577
|
+
cloud_name: Optional[str],
|
|
2578
|
+
cloud_id: Optional[str],
|
|
2579
|
+
skip_confirmation: bool,
|
|
2580
|
+
) -> bool:
|
|
2581
|
+
"""
|
|
2582
|
+
Deletes a cloud by name or id.
|
|
2583
|
+
TODO Delete all GCE resources on cloud delete
|
|
2584
|
+
Including: Anyscale maanged resources, ALB resources, and TLS certs
|
|
2585
|
+
"""
|
|
2586
|
+
cloud_id, cloud_name = get_cloud_id_and_name(
|
|
2587
|
+
self.api_client, cloud_id, cloud_name
|
|
2588
|
+
)
|
|
2589
|
+
|
|
2590
|
+
# get cloud
|
|
2591
|
+
cloud = self.api_client.get_cloud_api_v2_clouds_cloud_id_get(
|
|
2592
|
+
cloud_id=cloud_id
|
|
2593
|
+
).result
|
|
2594
|
+
|
|
2595
|
+
cloud_provider = cloud.provider
|
|
2596
|
+
assert cloud_provider in (
|
|
2597
|
+
CloudProviders.AWS,
|
|
2598
|
+
CloudProviders.GCP,
|
|
2599
|
+
CloudProviders.GENERIC,
|
|
2600
|
+
), f"Cloud provider {cloud_provider} not supported yet"
|
|
2601
|
+
|
|
2602
|
+
cloud_resource = get_cloud_resource_by_cloud_id(
|
|
2603
|
+
cloud_id, cloud.provider, self.api_client
|
|
2604
|
+
)
|
|
2605
|
+
if cloud_resource is None:
|
|
2606
|
+
# no cloud resource found, directly delete the cloud
|
|
2607
|
+
try:
|
|
2608
|
+
self.api_client.delete_cloud_api_v2_clouds_cloud_id_delete(
|
|
2609
|
+
cloud_id=cloud_id
|
|
2610
|
+
)
|
|
2611
|
+
except ClickException as e:
|
|
2612
|
+
self.log.error(e)
|
|
2613
|
+
raise ClickException(f"Failed to delete cloud with name {cloud_name}.")
|
|
2614
|
+
|
|
2615
|
+
self.log.info(f"Deleted cloud with name {cloud_name}.")
|
|
2616
|
+
return True
|
|
2617
|
+
|
|
2618
|
+
confirmation_msg = f"\nIf the cloud {cloud_id} is deleted, you will not be able to access existing clusters of this cloud.\n"
|
|
2619
|
+
if cloud.is_bring_your_own_resource:
|
|
2620
|
+
confirmation_msg += "Note that Anyscale does not delete any of the cloud provider resources created by you.\n"
|
|
2621
|
+
|
|
2622
|
+
confirmation_msg += "For more information, refer to the documentation "
|
|
2623
|
+
if cloud_provider == CloudProviders.AWS:
|
|
2624
|
+
confirmation_msg += "https://docs.anyscale.com/administration/cloud-deployment/manage-aws-cloud#delete-an-anyscale-cloud\n"
|
|
2625
|
+
elif cloud_provider == CloudProviders.GCP:
|
|
2626
|
+
confirmation_msg += "https://docs.anyscale.com/administration/cloud-deployment/manage-gcp-cloud#delete-an-anyscale-cloud\n"
|
|
2627
|
+
|
|
2628
|
+
confirmation_msg += "Continue?"
|
|
2629
|
+
confirm(confirmation_msg, skip_confirmation)
|
|
2630
|
+
|
|
2631
|
+
# set cloud state to DELETING
|
|
2632
|
+
try:
|
|
2633
|
+
if (
|
|
2634
|
+
cloud_provider == CloudProviders.GENERIC
|
|
2635
|
+
or cloud_provider == CloudProviders.AWS
|
|
2636
|
+
):
|
|
2637
|
+
with self.log.spinner("Preparing to delete Anyscale cloud..."):
|
|
2638
|
+
response = self.api_client.update_cloud_with_cloud_resource_api_v2_clouds_with_cloud_resource_router_cloud_id_put(
|
|
2639
|
+
cloud_id=cloud_id,
|
|
2640
|
+
update_cloud_with_cloud_resource=UpdateCloudWithCloudResource(
|
|
2641
|
+
state=CloudState.DELETING
|
|
2642
|
+
),
|
|
2643
|
+
)
|
|
2644
|
+
elif cloud_provider == CloudProviders.GCP:
|
|
2645
|
+
with self.log.spinner("Preparing to delete Anyscale cloud..."):
|
|
2646
|
+
response = self.api_client.update_cloud_with_cloud_resource_api_v2_clouds_with_cloud_resource_gcp_router_cloud_id_put(
|
|
2647
|
+
cloud_id=cloud_id,
|
|
2648
|
+
update_cloud_with_cloud_resource_gcp=UpdateCloudWithCloudResourceGCP(
|
|
2649
|
+
state=CloudState.DELETING
|
|
2650
|
+
),
|
|
2651
|
+
)
|
|
2652
|
+
|
|
2653
|
+
cloud = response.result
|
|
2654
|
+
except ClickException as e:
|
|
2655
|
+
raise ClickException(
|
|
2656
|
+
f"Failed to update cloud state to deleting for cloud {cloud_name}: {e}"
|
|
2657
|
+
)
|
|
2658
|
+
|
|
2659
|
+
# Clean up cloud resources
|
|
2660
|
+
try:
|
|
2661
|
+
if cloud_provider == CloudProviders.AWS:
|
|
2662
|
+
self.delete_all_aws_resources(cloud)
|
|
2663
|
+
elif cloud_provider == CloudProviders.GCP:
|
|
2664
|
+
with self.log.spinner("Deleting load balancing resources..."):
|
|
2665
|
+
wait_for_gcp_lb_resource_termination(
|
|
2666
|
+
api_client=self.api_client, cloud_id=cloud_id
|
|
2667
|
+
)
|
|
2668
|
+
self.delete_all_gcp_resources(cloud)
|
|
2669
|
+
except Exception as e: # noqa: BLE001
|
|
2670
|
+
confirm(
|
|
2671
|
+
f"Error while trying to clean up {cloud_provider} resources:\n{e}\n"
|
|
2672
|
+
f"Please check your {cloud_provider} account for relevant errors.\n"
|
|
2673
|
+
"Do you want to force delete this cloud? You will need to clean up any associated resources on your own.\n"
|
|
2674
|
+
"Continue with force deletion?",
|
|
2675
|
+
skip_confirmation,
|
|
2676
|
+
)
|
|
2677
|
+
|
|
2678
|
+
# Tear down admin zone and mark cloud as deleted
|
|
2679
|
+
with self.log.spinner("Deleting Anyscale cloud..."):
|
|
2680
|
+
try:
|
|
2681
|
+
self.api_client.delete_cloud_api_v2_clouds_cloud_id_delete(
|
|
2682
|
+
cloud_id=cloud_id
|
|
2683
|
+
)
|
|
2684
|
+
except ClickException as e:
|
|
2685
|
+
self.log.error(e)
|
|
2686
|
+
raise ClickException(f"Failed to delete cloud with name {cloud_name}.")
|
|
2687
|
+
|
|
2688
|
+
self.log.info(f"Deleted cloud with name {cloud_name}.")
|
|
2689
|
+
return True
|
|
2690
|
+
|
|
2691
|
+
def delete_all_gcp_resources(self, cloud: CloudWithCloudResource):
|
|
2692
|
+
if cloud.is_aioa or cloud.compute_stack == ComputeStack.K8S:
|
|
2693
|
+
# No resources to delete for hosted and k8s clouds
|
|
2694
|
+
return True
|
|
2695
|
+
|
|
2696
|
+
setup_utils = try_import_gcp_managed_setup_utils()
|
|
2697
|
+
gcp_utils = try_import_gcp_utils()
|
|
2698
|
+
credentials = json.loads(cloud.credentials)
|
|
2699
|
+
provider = credentials["gcp_workload_identity_pool_id"]
|
|
2700
|
+
service_account = credentials["service_account_email"]
|
|
2701
|
+
project_id = credentials["project_id"]
|
|
2702
|
+
factory = gcp_utils.get_google_cloud_client_factory(self.log, project_id)
|
|
2703
|
+
|
|
2704
|
+
# Delete services resources
|
|
2705
|
+
setup_utils.delete_gcp_tls_certificates(factory, project_id, cloud.id)
|
|
2706
|
+
|
|
2707
|
+
# Clean up cloud resources
|
|
2708
|
+
if cloud.is_bring_your_own_resource is False: # managed cloud
|
|
2709
|
+
self.delete_gcp_managed_cloud(cloud=cloud)
|
|
2710
|
+
else:
|
|
2711
|
+
self.log.warning(
|
|
2712
|
+
f"The workload identity federation provider pool {provider} and service account {service_account} that allows Anyscale to access your GCP account is still in place. Please delete it manually if you no longer wish anyscale to have access."
|
|
2713
|
+
)
|
|
2714
|
+
|
|
2715
|
+
return True
|
|
2716
|
+
|
|
2717
|
+
def delete_all_aws_resources(self, cloud: CloudWithCloudResource) -> bool:
|
|
2718
|
+
if cloud.is_aioa or cloud.compute_stack == ComputeStack.K8S:
|
|
2719
|
+
# No resources to delete for hosted and k8s clouds
|
|
2720
|
+
return True
|
|
2721
|
+
|
|
2722
|
+
# Delete services resources
|
|
2723
|
+
self.delete_aws_lb_cfn_stack(cloud=cloud)
|
|
2724
|
+
self.delete_aws_tls_certificates(cloud=cloud)
|
|
2725
|
+
|
|
2726
|
+
# Clean up cloud resources
|
|
2727
|
+
if cloud.is_bring_your_own_resource is False: # managed cloud
|
|
2728
|
+
# Delete AWS cloud resources by deleting the cfn stack
|
|
2729
|
+
self.delete_aws_managed_cloud(cloud=cloud)
|
|
2730
|
+
else: # registered cloud
|
|
2731
|
+
self.log.warning(
|
|
2732
|
+
f"The trust policy that allows Anyscale to assume {cloud.credentials} is still in place. Please delete it manually if you no longer wish anyscale to have access."
|
|
2733
|
+
)
|
|
2734
|
+
|
|
2735
|
+
return True
|
|
2736
|
+
|
|
2737
|
+
def delete_aws_lb_cfn_stack(self, cloud: CloudWithCloudResource) -> bool:
|
|
2738
|
+
|
|
2739
|
+
tag_name = "anyscale-cloud-id"
|
|
2740
|
+
key_name = cloud.id
|
|
2741
|
+
|
|
2742
|
+
cfn_client = _client("cloudformation", cloud.region)
|
|
2743
|
+
# Get a list of all the CloudFormation stacks with the specified tag
|
|
2744
|
+
stacks = cfn_client.list_stacks()
|
|
2745
|
+
|
|
2746
|
+
stacks = _unroll_resources_for_aws_list_call(
|
|
2747
|
+
cfn_client.list_stacks, "StackSummaries"
|
|
2748
|
+
)
|
|
2749
|
+
|
|
2750
|
+
resources_to_cleanup = []
|
|
2751
|
+
|
|
2752
|
+
for stack in stacks:
|
|
2753
|
+
if "StackName" in stack and "StackId" in stack:
|
|
2754
|
+
cfn_stack_arn = stack["StackId"]
|
|
2755
|
+
stack_description = cfn_client.describe_stacks(StackName=cfn_stack_arn)
|
|
2756
|
+
|
|
2757
|
+
# Extract the tags from the response
|
|
2758
|
+
if (
|
|
2759
|
+
"Stacks" in stack_description
|
|
2760
|
+
and stack_description["Stacks"]
|
|
2761
|
+
and "Tags" in stack_description["Stacks"][0]
|
|
2762
|
+
):
|
|
2763
|
+
tags = stack_description["Stacks"][0]["Tags"]
|
|
2764
|
+
for tag in tags:
|
|
2765
|
+
if (
|
|
2766
|
+
"Key" in tag
|
|
2767
|
+
and tag["Key"] == tag_name
|
|
2768
|
+
and "Value" in tag
|
|
2769
|
+
and tag["Value"] == key_name
|
|
2770
|
+
):
|
|
2771
|
+
resources_to_cleanup.append(cfn_stack_arn)
|
|
2772
|
+
|
|
2773
|
+
resource_delete_status = []
|
|
2774
|
+
for cfn_stack_arn in resources_to_cleanup:
|
|
2775
|
+
resource_delete_status.append(
|
|
2776
|
+
self.delete_aws_cloudformation_stack(
|
|
2777
|
+
cfn_stack_arn=cfn_stack_arn, cloud=cloud
|
|
2778
|
+
)
|
|
2779
|
+
)
|
|
2780
|
+
|
|
2781
|
+
return all(resource_delete_status)
|
|
2782
|
+
|
|
2783
|
+
def delete_aws_tls_certificates(self, cloud: CloudWithCloudResource) -> bool:
|
|
2784
|
+
tag_name = "anyscale-cloud-id"
|
|
2785
|
+
key_name = cloud.id
|
|
2786
|
+
|
|
2787
|
+
acm = boto3.client("acm", cloud.region)
|
|
2788
|
+
|
|
2789
|
+
certificates = _unroll_resources_for_aws_list_call(
|
|
2790
|
+
acm.list_certificates, "CertificateSummaryList"
|
|
2791
|
+
)
|
|
2792
|
+
|
|
2793
|
+
matching_certs = []
|
|
2794
|
+
|
|
2795
|
+
for certificate in certificates:
|
|
2796
|
+
if "CertificateArn" in certificate:
|
|
2797
|
+
|
|
2798
|
+
certificate_arn = certificate["CertificateArn"]
|
|
2799
|
+
response = acm.list_tags_for_certificate(CertificateArn=certificate_arn)
|
|
2800
|
+
|
|
2801
|
+
if "Tags" in response:
|
|
2802
|
+
tags = response["Tags"]
|
|
2803
|
+
for tag in tags:
|
|
2804
|
+
if (
|
|
2805
|
+
"Key" in tag
|
|
2806
|
+
and tag["Key"] == tag_name
|
|
2807
|
+
and "Value" in tag
|
|
2808
|
+
and tag["Value"] == key_name
|
|
2809
|
+
):
|
|
2810
|
+
matching_certs.append(certificate_arn)
|
|
2811
|
+
|
|
2812
|
+
resource_delete_status = []
|
|
2813
|
+
for certificate_arn in matching_certs:
|
|
2814
|
+
resource_delete_status.append(self.delete_tls_cert(certificate_arn, cloud))
|
|
2815
|
+
return all(resource_delete_status)
|
|
2816
|
+
|
|
2817
|
+
def delete_aws_managed_cloud(self, cloud: CloudWithCloudResource) -> bool:
|
|
2818
|
+
if (
|
|
2819
|
+
not cloud.cloud_resource
|
|
2820
|
+
or not cloud.cloud_resource.aws_cloudformation_stack_id
|
|
2821
|
+
):
|
|
2822
|
+
raise ClickException(
|
|
2823
|
+
f"This cloud {cloud.id} does not have a cloudformation stack."
|
|
2824
|
+
)
|
|
2825
|
+
|
|
2826
|
+
cfn_stack_arn = cloud.cloud_resource.aws_cloudformation_stack_id
|
|
2827
|
+
|
|
2828
|
+
# If the cloud is updated, the cross account IAM role might have an inline policy for customer drifts
|
|
2829
|
+
# We delete the inline policy first otherwise cfn stack deletion would fail
|
|
2830
|
+
try_delete_customer_drifts_policy(cloud=cloud)
|
|
2831
|
+
|
|
2832
|
+
self.log.info(
|
|
2833
|
+
f"\nThe S3 bucket ({cloud.cloud_resource.aws_s3_id}) associated with this cloud still exists."
|
|
2834
|
+
"\nIf you no longer need the data associated with this bucket, please delete it."
|
|
2835
|
+
)
|
|
2836
|
+
|
|
2837
|
+
return self.delete_aws_cloudformation_stack(
|
|
2838
|
+
cfn_stack_arn=cfn_stack_arn, cloud=cloud
|
|
2839
|
+
)
|
|
2840
|
+
|
|
2841
|
+
def delete_aws_cloudformation_stack(
|
|
2842
|
+
self, cfn_stack_arn: str, cloud: CloudWithCloudResource
|
|
2843
|
+
) -> bool:
|
|
2844
|
+
cfn_client = _client("cloudformation", cloud.region)
|
|
2845
|
+
|
|
2846
|
+
cfn_stack_url = f"https://{cloud.region}.console.aws.amazon.com/cloudformation/home?region={cloud.region}#/stacks/stackinfo?stackId={cfn_stack_arn}"
|
|
2847
|
+
|
|
2848
|
+
try:
|
|
2849
|
+
cfn_client.delete_stack(StackName=cfn_stack_arn)
|
|
2850
|
+
except ClientError:
|
|
2851
|
+
raise ClickException(
|
|
2852
|
+
f"Failed to delete cloudformation stack {cfn_stack_arn}.\nPlease view it at {cfn_stack_url}"
|
|
2853
|
+
) from None
|
|
2854
|
+
|
|
2855
|
+
self.log.info(f"\nTrack progress of cloudformation at {cfn_stack_url}")
|
|
2856
|
+
with self.log.spinner(
|
|
2857
|
+
f"Deleting cloud resource {cfn_stack_arn} through cloudformation..."
|
|
2858
|
+
):
|
|
2859
|
+
end_time = time.time() + CLOUDFORMATION_TIMEOUT_SECONDS_LONG
|
|
2860
|
+
while time.time() < end_time:
|
|
2861
|
+
try:
|
|
2862
|
+
cfn_stack = cfn_client.describe_stacks(StackName=cfn_stack_arn)[
|
|
2863
|
+
"Stacks"
|
|
2864
|
+
][0]
|
|
2865
|
+
except ClientError as e:
|
|
2866
|
+
raise ClickException(
|
|
2867
|
+
f"Failed to fetch the cloudformation stack {cfn_stack_arn}. Please check you have the right AWS credentials and the cloudformation stack still exists. Error details: {e}"
|
|
2868
|
+
) from None
|
|
2869
|
+
|
|
2870
|
+
if cfn_stack["StackStatus"] == "DELETE_COMPLETE":
|
|
2871
|
+
self.log.info(
|
|
2872
|
+
f"Cloudformation stack {cfn_stack['StackId']} is deleted."
|
|
2873
|
+
)
|
|
2874
|
+
break
|
|
2875
|
+
|
|
2876
|
+
if cfn_stack["StackStatus"] in ("DELETE_FAILED"):
|
|
2877
|
+
# Provide link to cloudformation
|
|
2878
|
+
raise ClickException(
|
|
2879
|
+
f"Failed to delete cloud resources. Please check your cloudformation stack for errors. {cfn_stack_url}"
|
|
2880
|
+
)
|
|
2881
|
+
time.sleep(1)
|
|
2882
|
+
|
|
2883
|
+
if time.time() > end_time:
|
|
2884
|
+
raise ClickException(
|
|
2885
|
+
f"Timed out deleting AWS resources. Please check your cloudformation stack for errors. {cfn_stack['StackId']}"
|
|
2886
|
+
)
|
|
2887
|
+
|
|
2888
|
+
return True
|
|
2889
|
+
|
|
2890
|
+
def delete_tls_cert(
|
|
2891
|
+
self, certificate_arn: str, cloud: CloudWithCloudResource
|
|
2892
|
+
) -> bool:
|
|
2893
|
+
acm = boto3.client("acm", cloud.region)
|
|
2894
|
+
|
|
2895
|
+
try:
|
|
2896
|
+
acm.delete_certificate(CertificateArn=certificate_arn)
|
|
2897
|
+
except ClientError as e:
|
|
2898
|
+
raise ClickException(
|
|
2899
|
+
f"Failed to delete TLS certificate {certificate_arn}: {e}"
|
|
2900
|
+
) from None
|
|
2901
|
+
|
|
2902
|
+
return True
|
|
2903
|
+
|
|
2904
|
+
def delete_gcp_managed_cloud(self, cloud: CloudWithCloudResourceGCP) -> bool:
|
|
2905
|
+
if (
|
|
2906
|
+
not cloud.cloud_resource
|
|
2907
|
+
or not cloud.cloud_resource.gcp_deployment_manager_id
|
|
2908
|
+
):
|
|
2909
|
+
raise ClickException(
|
|
2910
|
+
f"This cloud {cloud.id} does not have a deployment in GCP deployment manager."
|
|
2911
|
+
)
|
|
2912
|
+
setup_utils = try_import_gcp_managed_setup_utils()
|
|
2913
|
+
gcp_utils = try_import_gcp_utils()
|
|
2914
|
+
|
|
2915
|
+
project_id = json.loads(cloud.credentials)["project_id"]
|
|
2916
|
+
factory = gcp_utils.get_google_cloud_client_factory(self.log, project_id)
|
|
2917
|
+
deployment_name = cloud.cloud_resource.gcp_deployment_manager_id
|
|
2918
|
+
deployment_url = f"https://console.cloud.google.com/dm/deployments/details/{deployment_name}?project={project_id}"
|
|
2919
|
+
|
|
2920
|
+
self.log.info(f"\nTrack progress of Deployment Manager at {deployment_url}")
|
|
2921
|
+
|
|
2922
|
+
with self.log.spinner("Deleting cloud resources through Deployment Manager..."):
|
|
2923
|
+
|
|
2924
|
+
# Remove firewall policy ids
|
|
2925
|
+
if cloud.cloud_resource.gcp_firewall_policy_ids:
|
|
2926
|
+
for firewall_policy in cloud.cloud_resource.gcp_firewall_policy_ids:
|
|
2927
|
+
# try delete the associations
|
|
2928
|
+
setup_utils.remove_firewall_policy_associations(
|
|
2929
|
+
factory, project_id, firewall_policy
|
|
2930
|
+
)
|
|
2931
|
+
|
|
2932
|
+
# Delete the deployment
|
|
2933
|
+
setup_utils.update_deployment_with_bucket_only(
|
|
2934
|
+
factory, project_id, deployment_name
|
|
2935
|
+
)
|
|
2936
|
+
|
|
2937
|
+
self.log.info(
|
|
2938
|
+
f"\nThe cloud bucket ({cloud.cloud_resource.gcp_cloud_storage_bucket_id}) associated with this cloud still exists."
|
|
2939
|
+
"\nIf you no longer need the data associated with this bucket, please delete it."
|
|
2940
|
+
)
|
|
2941
|
+
return True
|
|
2942
|
+
|
|
2943
|
+
### Edit cloud ###
|
|
2944
|
+
def _get_cloud_resource_value(self, cloud_resource: Any, resource_type: str) -> Any:
|
|
2945
|
+
# Special case -- memorydb_cluster_id
|
|
2946
|
+
if resource_type == "memorydb_cluster_id":
|
|
2947
|
+
memorydb_cluster_config = getattr(
|
|
2948
|
+
cloud_resource, "memorydb_cluster_config", None
|
|
2949
|
+
)
|
|
2950
|
+
if memorydb_cluster_config is None:
|
|
2951
|
+
return None
|
|
2952
|
+
else:
|
|
2953
|
+
# memorydb_cluster_config.id has format "arn:aws:memorydb:us-east-2:815664363732:cluster/cloud-edit-test".
|
|
2954
|
+
value = memorydb_cluster_config.id.split("/")[-1]
|
|
2955
|
+
return value
|
|
2956
|
+
|
|
2957
|
+
# Special case -- memorystore_instance_name
|
|
2958
|
+
if resource_type == "memorystore_instance_name":
|
|
2959
|
+
memorystore_instance_config = getattr(
|
|
2960
|
+
cloud_resource, "memorystore_instance_config", None
|
|
2961
|
+
)
|
|
2962
|
+
if memorystore_instance_config is None:
|
|
2963
|
+
return None
|
|
2964
|
+
else:
|
|
2965
|
+
value = memorystore_instance_config.name
|
|
2966
|
+
return value
|
|
2967
|
+
|
|
2968
|
+
# Normal cases.
|
|
2969
|
+
value = getattr(cloud_resource, resource_type, None)
|
|
2970
|
+
if value is None:
|
|
2971
|
+
self.log.warning(f"Old value of {resource_type} is None.")
|
|
2972
|
+
return None
|
|
2973
|
+
return value
|
|
2974
|
+
|
|
2975
|
+
def _generate_edit_details_logs(
|
|
2976
|
+
self, cloud_resource: Any, edit_details: Dict[str, Optional[str]]
|
|
2977
|
+
) -> List[str]:
|
|
2978
|
+
details_logs = []
|
|
2979
|
+
for resource_type, value in edit_details.items():
|
|
2980
|
+
if value:
|
|
2981
|
+
old_value = self._get_cloud_resource_value(
|
|
2982
|
+
cloud_resource, resource_type
|
|
2983
|
+
)
|
|
2984
|
+
if old_value == value:
|
|
2985
|
+
raise ClickException(
|
|
2986
|
+
f"Specified resource is the same as existed resource -- {resource_type}: {value}"
|
|
2987
|
+
)
|
|
2988
|
+
details_logs.append(f"{resource_type}: from {old_value} -> {value}")
|
|
2989
|
+
return details_logs
|
|
2990
|
+
|
|
2991
|
+
def _generate_rollback_command(
|
|
2992
|
+
self, cloud_id: str, cloud_resource: Any, edit_details: Dict[str, Optional[str]]
|
|
2993
|
+
) -> str:
|
|
2994
|
+
rollback_command = BASE_ROLLBACK_COMMAND.format(cloud_id=cloud_id)
|
|
2995
|
+
for resource_type, value in edit_details.items():
|
|
2996
|
+
if value:
|
|
2997
|
+
old_value = self._get_cloud_resource_value(
|
|
2998
|
+
cloud_resource, resource_type
|
|
2999
|
+
)
|
|
3000
|
+
if old_value is not None:
|
|
3001
|
+
# The resource type names are in CreateCloudResource (backend/server/api/product/models/clouds.py).
|
|
3002
|
+
# The cli command names are in cloud_edit (frontend/cli/anyscale/commands:cloud_commands).
|
|
3003
|
+
# The relationship between their names are cli_command_name = resource_type_name.replace("_", "-").
|
|
3004
|
+
# e.g. cli_command_name: aws-s3-id & resource_type_name: aws_s3_id.
|
|
3005
|
+
formatted_resource_type = resource_type.replace("_", "-")
|
|
3006
|
+
rollback_command += f" --{formatted_resource_type}={old_value}"
|
|
3007
|
+
# If the only edit field is redis and it was None originally, rollback_command didn't appened any args, reset the rollback command to be empty.
|
|
3008
|
+
if rollback_command == BASE_ROLLBACK_COMMAND.format(cloud_id=cloud_id):
|
|
3009
|
+
rollback_command = ""
|
|
3010
|
+
return rollback_command
|
|
3011
|
+
|
|
3012
|
+
def _edit_aws_cloud( # noqa: PLR0912
|
|
3013
|
+
self,
|
|
3014
|
+
*,
|
|
3015
|
+
cloud_id: str,
|
|
3016
|
+
cloud_name: str,
|
|
3017
|
+
cloud: Any,
|
|
3018
|
+
cloud_resource: Any,
|
|
3019
|
+
aws_s3_id: Optional[str],
|
|
3020
|
+
aws_efs_id: Optional[str],
|
|
3021
|
+
aws_efs_mount_target_ip: Optional[str],
|
|
3022
|
+
memorydb_cluster_id: Optional[str],
|
|
3023
|
+
yes: bool = False,
|
|
3024
|
+
):
|
|
3025
|
+
# Log edit details.
|
|
3026
|
+
self.log.open_block("EditDetail", "\nEdit details...")
|
|
3027
|
+
edit_details = {
|
|
3028
|
+
"aws_s3_id": aws_s3_id,
|
|
3029
|
+
"aws_efs_id": aws_efs_id,
|
|
3030
|
+
"aws_efs_mount_target_ip": aws_efs_mount_target_ip,
|
|
3031
|
+
"memorydb_cluster_id": memorydb_cluster_id,
|
|
3032
|
+
}
|
|
3033
|
+
details_logs = self._generate_edit_details_logs(cloud_resource, edit_details)
|
|
3034
|
+
self.log.info(
|
|
3035
|
+
self.log.highlight(
|
|
3036
|
+
"Cloud {} ({}) edit details: \n{}".format(
|
|
3037
|
+
cloud_name, cloud_id, "; \n".join(details_logs)
|
|
3038
|
+
)
|
|
3039
|
+
)
|
|
3040
|
+
)
|
|
3041
|
+
self.log.close_block("EditDetail")
|
|
3042
|
+
|
|
3043
|
+
try:
|
|
3044
|
+
boto3_session = boto3.Session(region_name=cloud.region)
|
|
3045
|
+
if aws_efs_id and not aws_efs_mount_target_ip:
|
|
3046
|
+
# Get the mount target IP for new aws_efs_ip (consistent with cloud register).
|
|
3047
|
+
aws_efs_mount_target_ip = _get_aws_efs_mount_target_ip(
|
|
3048
|
+
boto3_session, aws_efs_id
|
|
3049
|
+
)
|
|
3050
|
+
if not aws_efs_mount_target_ip:
|
|
3051
|
+
raise ClickException(
|
|
3052
|
+
f"Failed to get the mount target IP for new aws_efs_ip {aws_efs_id}, please make sure the aws_efs_ip exists and it has mount targets."
|
|
3053
|
+
)
|
|
3054
|
+
except Exception as e: # noqa: BLE001
|
|
3055
|
+
self.log.error(str(e))
|
|
3056
|
+
raise ClickException(
|
|
3057
|
+
f"Failed to get the mount target IP for new aws_efs_ip {aws_efs_id}, please make sure it has mount targets."
|
|
3058
|
+
)
|
|
3059
|
+
try:
|
|
3060
|
+
memorydb_cluster_config = None
|
|
3061
|
+
if memorydb_cluster_id:
|
|
3062
|
+
memorydb_cluster_config = _get_memorydb_cluster_config(
|
|
3063
|
+
memorydb_cluster_id, cloud.region, self.log
|
|
3064
|
+
)
|
|
3065
|
+
except Exception as e: # noqa: BLE001
|
|
3066
|
+
self.log.error(str(e))
|
|
3067
|
+
raise ClickException(
|
|
3068
|
+
f"Failed to get the memorydb cluster config for new memorydb_cluster_id {memorydb_cluster_id}, please make sure it's created."
|
|
3069
|
+
)
|
|
3070
|
+
|
|
3071
|
+
# Static Verify.
|
|
3072
|
+
self.log.open_block(
|
|
3073
|
+
"Verify", "Start cloud static verification on the specified resources..."
|
|
3074
|
+
)
|
|
3075
|
+
new_cloud_resource = copy.deepcopy(cloud_resource)
|
|
3076
|
+
if aws_s3_id:
|
|
3077
|
+
new_cloud_resource.aws_s3_id = aws_s3_id
|
|
3078
|
+
if aws_efs_id:
|
|
3079
|
+
new_cloud_resource.aws_efs_id = aws_efs_id
|
|
3080
|
+
if aws_efs_mount_target_ip:
|
|
3081
|
+
new_cloud_resource.aws_efs_mount_target_ip = aws_efs_mount_target_ip
|
|
3082
|
+
if memorydb_cluster_id:
|
|
3083
|
+
new_cloud_resource.memorydb_cluster_config = memorydb_cluster_config
|
|
3084
|
+
|
|
3085
|
+
if not self.verify_aws_cloud_resources(
|
|
3086
|
+
cloud_resource=new_cloud_resource,
|
|
3087
|
+
boto3_session=boto3_session,
|
|
3088
|
+
region=cloud.region,
|
|
3089
|
+
cloud_id=cloud_id,
|
|
3090
|
+
is_bring_your_own_resource=cloud.is_bring_your_own_resource,
|
|
3091
|
+
is_private_network=cloud.is_private_cloud
|
|
3092
|
+
if cloud.is_private_cloud
|
|
3093
|
+
else False,
|
|
3094
|
+
):
|
|
3095
|
+
raise ClickException(
|
|
3096
|
+
"Cloud edit failed because resource failed verification. Please check the verification results above, fix them, and try again."
|
|
3097
|
+
)
|
|
3098
|
+
|
|
3099
|
+
self.log.info(
|
|
3100
|
+
self.log.highlight(
|
|
3101
|
+
"Please make sure you checked the warnings from above verification results."
|
|
3102
|
+
)
|
|
3103
|
+
)
|
|
3104
|
+
self.log.close_block("Verify")
|
|
3105
|
+
|
|
3106
|
+
self.log.open_block(
|
|
3107
|
+
"Reminder", "Please read the following reminder carefully..."
|
|
3108
|
+
)
|
|
3109
|
+
self.log.info(
|
|
3110
|
+
self.log.highlight(
|
|
3111
|
+
"If there are running workloads utilizing the old resources, you may want to retain them. Please note that this edit will not automatically remove any old resources. If you wish to delete them, you'll need to handle it."
|
|
3112
|
+
)
|
|
3113
|
+
)
|
|
3114
|
+
self.log.info(
|
|
3115
|
+
self.log.highlight(
|
|
3116
|
+
"The cloud resources we are going to edit {} ({}): \n{}".format(
|
|
3117
|
+
cloud_name, cloud_id, "; \n".join(details_logs)
|
|
3118
|
+
)
|
|
3119
|
+
)
|
|
3120
|
+
)
|
|
3121
|
+
self.log.close_block("Reminder")
|
|
3122
|
+
|
|
3123
|
+
confirm(
|
|
3124
|
+
"Are you sure you want to edit these cloud resource? ", yes,
|
|
3125
|
+
)
|
|
3126
|
+
|
|
3127
|
+
# Execute edit cloud.
|
|
3128
|
+
self.log.open_block("Edit", "Start editing cloud...")
|
|
3129
|
+
try:
|
|
3130
|
+
self.api_client.edit_cloud_resource_api_v2_clouds_with_cloud_resource_router_cloud_id_patch(
|
|
3131
|
+
cloud_id=cloud_id,
|
|
3132
|
+
editable_cloud_resource=EditableCloudResource(
|
|
3133
|
+
aws_s3_id=aws_s3_id,
|
|
3134
|
+
aws_efs_id=aws_efs_id,
|
|
3135
|
+
aws_efs_mount_target_ip=aws_efs_mount_target_ip,
|
|
3136
|
+
memorydb_cluster_config=memorydb_cluster_config,
|
|
3137
|
+
),
|
|
3138
|
+
)
|
|
3139
|
+
except Exception as e: # noqa: BLE001
|
|
3140
|
+
self.log.error(str(e))
|
|
3141
|
+
raise ClickException(
|
|
3142
|
+
"Edit cloud resource failed! The backend server might be under maintainance right now, please reach out to support or your SA for assistance."
|
|
3143
|
+
)
|
|
3144
|
+
|
|
3145
|
+
# Hint customer rollback command.
|
|
3146
|
+
rollback_command = self._generate_rollback_command(
|
|
3147
|
+
cloud_id, cloud_resource, edit_details
|
|
3148
|
+
)
|
|
3149
|
+
self.log.info(
|
|
3150
|
+
self.log.highlight(
|
|
3151
|
+
f"Cloud {cloud_name}({cloud_id}) is successfully edited."
|
|
3152
|
+
)
|
|
3153
|
+
)
|
|
3154
|
+
if rollback_command:
|
|
3155
|
+
self.log.info(
|
|
3156
|
+
self.log.highlight(
|
|
3157
|
+
f"If you want to revert the edit, you can edit it back to the original cloud with: `{rollback_command}`"
|
|
3158
|
+
)
|
|
3159
|
+
)
|
|
3160
|
+
self.log.close_block("Edit")
|
|
3161
|
+
|
|
3162
|
+
def _get_project_id(self, cloud: Any, cloud_name: str, cloud_id: str) -> str:
|
|
3163
|
+
try:
|
|
3164
|
+
return json.loads(cloud.credentials)["project_id"]
|
|
3165
|
+
except Exception: # noqa: BLE001
|
|
3166
|
+
raise ClickException(
|
|
3167
|
+
f"Failed to get project id for cloud {cloud_name}({cloud_id}). Please ensure the provided cloud_name/cloud_id exists."
|
|
3168
|
+
)
|
|
3169
|
+
|
|
3170
|
+
def _get_host_project_id(
|
|
3171
|
+
self, cloud: Any, cloud_name: str, cloud_id: str
|
|
3172
|
+
) -> Optional[str]:
|
|
3173
|
+
try:
|
|
3174
|
+
credentials = json.loads(cloud.credentials)
|
|
3175
|
+
return credentials.get("host_project_id")
|
|
3176
|
+
except Exception: # noqa: BLE001
|
|
3177
|
+
raise ClickException(
|
|
3178
|
+
f"Failed to get host project id for cloud {cloud_name}({cloud_id}). Please ensure the provided cloud_name/cloud_id exists."
|
|
3179
|
+
)
|
|
3180
|
+
|
|
3181
|
+
def _get_gcp_filestore_config(
|
|
3182
|
+
self,
|
|
3183
|
+
gcp_filestore_instance_id: str,
|
|
3184
|
+
gcp_filestore_location: str,
|
|
3185
|
+
project_id: str,
|
|
3186
|
+
cloud_resource: Any,
|
|
3187
|
+
gcp_utils,
|
|
3188
|
+
) -> GCPFileStoreConfig:
|
|
3189
|
+
try:
|
|
3190
|
+
factory = gcp_utils.get_google_cloud_client_factory(self.log, project_id)
|
|
3191
|
+
return gcp_utils.get_gcp_filestore_config(
|
|
3192
|
+
factory,
|
|
3193
|
+
project_id,
|
|
3194
|
+
cloud_resource.gcp_vpc_id,
|
|
3195
|
+
gcp_filestore_location,
|
|
3196
|
+
gcp_filestore_instance_id,
|
|
3197
|
+
self.log,
|
|
3198
|
+
)
|
|
3199
|
+
except Exception as e: # noqa: BLE001
|
|
3200
|
+
self.log.error(str(e))
|
|
3201
|
+
raise ClickException(
|
|
3202
|
+
f"Failed to construct the gcp_filestore_config from project {project_id}, please double check the provided filestore_location {gcp_filestore_location} and filestore_instance_id {gcp_filestore_instance_id}."
|
|
3203
|
+
)
|
|
3204
|
+
|
|
3205
|
+
def _get_gcp_edit_details(
|
|
3206
|
+
self,
|
|
3207
|
+
*,
|
|
3208
|
+
cloud_resource: Any,
|
|
3209
|
+
edit_details: Dict[str, Optional[str]],
|
|
3210
|
+
gcp_filestore_config: GCPFileStoreConfig,
|
|
3211
|
+
gcp_filestore_instance_id: Optional[str],
|
|
3212
|
+
gcp_filestore_location: Optional[str],
|
|
3213
|
+
gcp_utils,
|
|
3214
|
+
) -> List[str]:
|
|
3215
|
+
details_logs = self._generate_edit_details_logs(cloud_resource, edit_details)
|
|
3216
|
+
if gcp_filestore_config:
|
|
3217
|
+
(
|
|
3218
|
+
old_filestore_location,
|
|
3219
|
+
old_filestore_instance_id,
|
|
3220
|
+
) = gcp_utils.get_filestore_location_and_instance_id(
|
|
3221
|
+
cloud_resource.gcp_filestore_config
|
|
3222
|
+
)
|
|
3223
|
+
if (
|
|
3224
|
+
old_filestore_instance_id == gcp_filestore_instance_id
|
|
3225
|
+
and old_filestore_location == gcp_filestore_location
|
|
3226
|
+
):
|
|
3227
|
+
raise ClickException(
|
|
3228
|
+
f"Specified resource is the same as existed resource -- gcp_filestore_instance_id: {gcp_filestore_instance_id}; gcp_filestore_location: {gcp_filestore_location}"
|
|
3229
|
+
)
|
|
3230
|
+
details_logs.append(
|
|
3231
|
+
f"filestore_instance_id: from {old_filestore_instance_id} -> {gcp_filestore_instance_id}"
|
|
3232
|
+
)
|
|
3233
|
+
details_logs.append(
|
|
3234
|
+
f"filestore_location: from {old_filestore_location} -> {gcp_filestore_location}"
|
|
3235
|
+
)
|
|
3236
|
+
return details_logs
|
|
3237
|
+
|
|
3238
|
+
def _generate_rollback_command_for_gcp(
|
|
3239
|
+
self,
|
|
3240
|
+
cloud_id: str,
|
|
3241
|
+
cloud_resource: Any,
|
|
3242
|
+
edit_details: Dict[str, Optional[str]],
|
|
3243
|
+
gcp_filestore_config: GCPFileStoreConfig,
|
|
3244
|
+
gcp_utils,
|
|
3245
|
+
):
|
|
3246
|
+
rollback_cmd = self._generate_rollback_command(
|
|
3247
|
+
cloud_id, cloud_resource, edit_details
|
|
3248
|
+
)
|
|
3249
|
+
if gcp_filestore_config:
|
|
3250
|
+
(
|
|
3251
|
+
old_filestore_location,
|
|
3252
|
+
old_filestore_instance_id,
|
|
3253
|
+
) = gcp_utils.get_filestore_location_and_instance_id(
|
|
3254
|
+
cloud_resource.gcp_filestore_config
|
|
3255
|
+
)
|
|
3256
|
+
rollback_cmd += f" --gcp-filestore-instance-id={old_filestore_instance_id}"
|
|
3257
|
+
rollback_cmd += f" --gcp-filestore-location={old_filestore_location}"
|
|
3258
|
+
rollback_cmd = rollback_cmd.replace(
|
|
3259
|
+
"gcp-cloud-storage-bucket-id", "gcp-cloud-storage-bucket-name"
|
|
3260
|
+
)
|
|
3261
|
+
return rollback_cmd
|
|
3262
|
+
|
|
3263
|
+
def _edit_gcp_cloud( # noqa: PLR0912
|
|
3264
|
+
self,
|
|
3265
|
+
*,
|
|
3266
|
+
cloud_id: str,
|
|
3267
|
+
cloud_name: str,
|
|
3268
|
+
cloud: Any,
|
|
3269
|
+
cloud_resource: Any,
|
|
3270
|
+
gcp_filestore_instance_id: Optional[str],
|
|
3271
|
+
gcp_filestore_location: Optional[str],
|
|
3272
|
+
gcp_cloud_storage_bucket_name: Optional[str],
|
|
3273
|
+
memorystore_instance_name: Optional[str],
|
|
3274
|
+
yes: bool = False,
|
|
3275
|
+
):
|
|
3276
|
+
project_id = self._get_project_id(cloud, cloud_name, cloud_id)
|
|
3277
|
+
host_project_id = self._get_host_project_id(cloud, cloud_name, cloud_id)
|
|
3278
|
+
gcp_utils = try_import_gcp_utils()
|
|
3279
|
+
|
|
3280
|
+
gcp_filestore_config = None
|
|
3281
|
+
if gcp_filestore_instance_id and gcp_filestore_location:
|
|
3282
|
+
gcp_filestore_config = self._get_gcp_filestore_config(
|
|
3283
|
+
gcp_filestore_instance_id,
|
|
3284
|
+
gcp_filestore_location,
|
|
3285
|
+
project_id,
|
|
3286
|
+
cloud_resource,
|
|
3287
|
+
gcp_utils,
|
|
3288
|
+
)
|
|
3289
|
+
|
|
3290
|
+
memorystore_instance_config = None
|
|
3291
|
+
if memorystore_instance_name:
|
|
3292
|
+
factory = gcp_utils.get_google_cloud_client_factory(self.log, project_id)
|
|
3293
|
+
memorystore_instance_config = gcp_utils.get_gcp_memorystore_config(
|
|
3294
|
+
factory, memorystore_instance_name
|
|
3295
|
+
)
|
|
3296
|
+
|
|
3297
|
+
# Log edit details.
|
|
3298
|
+
self.log.open_block("EditDetail", "\nEdit details...")
|
|
3299
|
+
edit_details = {
|
|
3300
|
+
"gcp_cloud_storage_bucket_id": gcp_cloud_storage_bucket_name,
|
|
3301
|
+
"memorystore_instance_name": memorystore_instance_name,
|
|
3302
|
+
}
|
|
3303
|
+
details_logs = self._get_gcp_edit_details(
|
|
3304
|
+
cloud_resource=cloud_resource,
|
|
3305
|
+
edit_details=edit_details,
|
|
3306
|
+
gcp_filestore_config=gcp_filestore_config,
|
|
3307
|
+
gcp_filestore_instance_id=gcp_filestore_instance_id,
|
|
3308
|
+
gcp_filestore_location=gcp_filestore_location,
|
|
3309
|
+
gcp_utils=gcp_utils,
|
|
3310
|
+
)
|
|
3311
|
+
self.log.info(
|
|
3312
|
+
self.log.highlight(
|
|
3313
|
+
"Cloud edit details {} ({}): \n{}".format(
|
|
3314
|
+
cloud_name, cloud_id, "; \n".join(details_logs)
|
|
3315
|
+
)
|
|
3316
|
+
)
|
|
3317
|
+
)
|
|
3318
|
+
self.log.close_block("EditDetail")
|
|
3319
|
+
|
|
3320
|
+
# Static Verify.
|
|
3321
|
+
self.log.open_block(
|
|
3322
|
+
"Verify", "Start cloud static verification on the specified resources..."
|
|
3323
|
+
)
|
|
3324
|
+
new_cloud_resource = copy.deepcopy(cloud_resource)
|
|
3325
|
+
if gcp_filestore_config:
|
|
3326
|
+
new_cloud_resource.gcp_filestore_config = gcp_filestore_config
|
|
3327
|
+
if gcp_cloud_storage_bucket_name:
|
|
3328
|
+
new_cloud_resource.gcp_cloud_storage_bucket_id = (
|
|
3329
|
+
gcp_cloud_storage_bucket_name
|
|
3330
|
+
)
|
|
3331
|
+
if memorystore_instance_config:
|
|
3332
|
+
new_cloud_resource.memorystore_instance_config = memorystore_instance_config
|
|
3333
|
+
if not self.verify_gcp_cloud_resources(
|
|
3334
|
+
cloud_resource=new_cloud_resource,
|
|
3335
|
+
project_id=project_id,
|
|
3336
|
+
host_project_id=host_project_id,
|
|
3337
|
+
region=cloud.region,
|
|
3338
|
+
cloud_id=cloud_id,
|
|
3339
|
+
yes=False,
|
|
3340
|
+
):
|
|
3341
|
+
raise ClickException(
|
|
3342
|
+
"Cloud edit failed because resource failed verification. Please check the verification results above, fix them, and try again."
|
|
3343
|
+
)
|
|
3344
|
+
|
|
3345
|
+
self.log.info(
|
|
3346
|
+
self.log.highlight(
|
|
3347
|
+
"Please make sure you checked the warnings from above verification results."
|
|
3348
|
+
)
|
|
3349
|
+
)
|
|
3350
|
+
self.log.close_block("Verify")
|
|
3351
|
+
|
|
3352
|
+
self.log.open_block(
|
|
3353
|
+
"Reminder", "Please read the following reminder carefully..."
|
|
3354
|
+
)
|
|
3355
|
+
self.log.info(
|
|
3356
|
+
self.log.highlight(
|
|
3357
|
+
"If there are running workloads utilizing the old resources, you may want to retain them. Please note that this edit will not automatically remove any old resources. If you wish to delete them, you'll need to handle it."
|
|
3358
|
+
)
|
|
3359
|
+
)
|
|
3360
|
+
self.log.info(
|
|
3361
|
+
self.log.highlight(
|
|
3362
|
+
"The cloud resources we are going to edit {} ({}): \n{}".format(
|
|
3363
|
+
cloud_name, cloud_id, "; \n".join(details_logs)
|
|
3364
|
+
)
|
|
3365
|
+
)
|
|
3366
|
+
)
|
|
3367
|
+
self.log.close_block("Reminder")
|
|
3368
|
+
|
|
3369
|
+
confirm(
|
|
3370
|
+
"Are you sure you want to edit these cloud resource? ", yes,
|
|
3371
|
+
)
|
|
3372
|
+
|
|
3373
|
+
# Execute edit cloud.
|
|
3374
|
+
self.log.open_block("Edit", "Start editing cloud...")
|
|
3375
|
+
try:
|
|
3376
|
+
self.api_client.edit_cloud_resource_api_v2_clouds_with_cloud_resource_gcp_router_cloud_id_patch(
|
|
3377
|
+
cloud_id=cloud_id,
|
|
3378
|
+
editable_cloud_resource_gcp=EditableCloudResourceGCP(
|
|
3379
|
+
gcp_filestore_config=gcp_filestore_config,
|
|
3380
|
+
gcp_cloud_storage_bucket_id=gcp_cloud_storage_bucket_name,
|
|
3381
|
+
memorystore_instance_config=memorystore_instance_config,
|
|
3382
|
+
),
|
|
3383
|
+
)
|
|
3384
|
+
except Exception as e: # noqa: BLE001
|
|
3385
|
+
self.log.error(str(e))
|
|
3386
|
+
raise ClickException(
|
|
3387
|
+
"Edit cloud resource failed! The backend server might be under maintainance right now, please reach out to support or your SA for assistance."
|
|
3388
|
+
)
|
|
3389
|
+
|
|
3390
|
+
# Hint customer rollback command.
|
|
3391
|
+
rollback_command = self._generate_rollback_command_for_gcp(
|
|
3392
|
+
cloud_id, cloud_resource, edit_details, gcp_filestore_config, gcp_utils,
|
|
3393
|
+
)
|
|
3394
|
+
self.log.info(
|
|
3395
|
+
self.log.highlight(
|
|
3396
|
+
f"Cloud {cloud_name}({cloud_id}) is successfully edited."
|
|
3397
|
+
)
|
|
3398
|
+
)
|
|
3399
|
+
if rollback_command:
|
|
3400
|
+
self.log.info(
|
|
3401
|
+
self.log.highlight(
|
|
3402
|
+
f"If you want to revert the edit, you can edit it back to the original cloud with: `{rollback_command}`"
|
|
3403
|
+
)
|
|
3404
|
+
)
|
|
3405
|
+
self.log.close_block("Edit")
|
|
3406
|
+
|
|
3407
|
+
def edit_cloud( # noqa: PLR0912,PLR0913
|
|
3408
|
+
self,
|
|
3409
|
+
*,
|
|
3410
|
+
cloud_name: Optional[str],
|
|
3411
|
+
cloud_id: Optional[str],
|
|
3412
|
+
aws_s3_id: Optional[str],
|
|
3413
|
+
aws_efs_id: Optional[str],
|
|
3414
|
+
aws_efs_mount_target_ip: Optional[str],
|
|
3415
|
+
memorydb_cluster_id: Optional[str],
|
|
3416
|
+
gcp_filestore_instance_id: Optional[str],
|
|
3417
|
+
gcp_filestore_location: Optional[str],
|
|
3418
|
+
gcp_cloud_storage_bucket_name: Optional[str],
|
|
3419
|
+
memorystore_instance_name: Optional[str],
|
|
3420
|
+
functional_verify: Optional[str],
|
|
3421
|
+
yes: bool = False,
|
|
3422
|
+
auto_add_user: Optional[bool] = None,
|
|
3423
|
+
):
|
|
3424
|
+
"""Edit aws cloud.
|
|
3425
|
+
|
|
3426
|
+
The editable fields are in EditableCloudResource (AWS) /EditableCloudResourceGCP (GCP).
|
|
3427
|
+
Steps:
|
|
3428
|
+
1. Log the edits (from old to new values).
|
|
3429
|
+
2. Static verify cloud resources with updated values.
|
|
3430
|
+
3. Prompt the customer for confirmation based on verification results.
|
|
3431
|
+
4. Update the cloud resource (calls backend API to modify the database).
|
|
3432
|
+
5. Conduct a functional verification, if specified.
|
|
3433
|
+
"""
|
|
3434
|
+
functions_to_verify = self._validate_functional_verification_args(
|
|
3435
|
+
functional_verify
|
|
3436
|
+
)
|
|
3437
|
+
cloud_id, cloud_name = get_cloud_id_and_name(
|
|
3438
|
+
self.api_client, cloud_id, cloud_name
|
|
3439
|
+
)
|
|
3440
|
+
cloud = self.api_client.get_cloud_api_v2_clouds_cloud_id_get(cloud_id).result
|
|
3441
|
+
|
|
3442
|
+
if not cloud.is_bring_your_own_resource:
|
|
3443
|
+
raise ClickException(
|
|
3444
|
+
f"Cloud {cloud_name}({cloud_id}) is not a cloud with customer defined resources, currently we don't support editing cloud resource values of managed clouds."
|
|
3445
|
+
)
|
|
3446
|
+
|
|
3447
|
+
cloud_resource = get_cloud_resource_by_cloud_id(
|
|
3448
|
+
cloud_id, cloud.provider, self.api_client
|
|
3449
|
+
)
|
|
3450
|
+
if cloud_resource is None:
|
|
3451
|
+
raise ClickException(
|
|
3452
|
+
f"Cloud {cloud_name}({cloud_id}) does not contain resource records."
|
|
3453
|
+
)
|
|
3454
|
+
|
|
3455
|
+
if auto_add_user is not None:
|
|
3456
|
+
self._update_auto_add_user_field(auto_add_user, cloud)
|
|
3457
|
+
|
|
3458
|
+
if any(
|
|
3459
|
+
[
|
|
3460
|
+
aws_s3_id,
|
|
3461
|
+
aws_efs_id,
|
|
3462
|
+
aws_efs_mount_target_ip,
|
|
3463
|
+
memorydb_cluster_id,
|
|
3464
|
+
gcp_filestore_instance_id,
|
|
3465
|
+
gcp_filestore_location,
|
|
3466
|
+
gcp_cloud_storage_bucket_name,
|
|
3467
|
+
memorystore_instance_name,
|
|
3468
|
+
]
|
|
3469
|
+
):
|
|
3470
|
+
if cloud.provider == "AWS":
|
|
3471
|
+
self.cloud_event_producer.init_trace_context(
|
|
3472
|
+
CloudAnalyticsEventCommandName.EDIT,
|
|
3473
|
+
CloudProviders.AWS,
|
|
3474
|
+
cloud_id=cloud_id,
|
|
3475
|
+
)
|
|
3476
|
+
self.cloud_event_producer.produce(
|
|
3477
|
+
CloudAnalyticsEventName.COMMAND_START, succeeded=True
|
|
3478
|
+
)
|
|
3479
|
+
if (
|
|
3480
|
+
not any(
|
|
3481
|
+
[
|
|
3482
|
+
aws_s3_id,
|
|
3483
|
+
aws_efs_id,
|
|
3484
|
+
aws_efs_mount_target_ip,
|
|
3485
|
+
memorydb_cluster_id,
|
|
3486
|
+
]
|
|
3487
|
+
)
|
|
3488
|
+
) or any(
|
|
3489
|
+
[
|
|
3490
|
+
gcp_filestore_instance_id,
|
|
3491
|
+
gcp_filestore_location,
|
|
3492
|
+
gcp_cloud_storage_bucket_name,
|
|
3493
|
+
memorystore_instance_name,
|
|
3494
|
+
]
|
|
3495
|
+
):
|
|
3496
|
+
self.cloud_event_producer.produce(
|
|
3497
|
+
CloudAnalyticsEventName.RESOURCES_EDITED,
|
|
3498
|
+
succeeded=False,
|
|
3499
|
+
internal_error="specified resource and provider mismatch",
|
|
3500
|
+
)
|
|
3501
|
+
raise ClickException(
|
|
3502
|
+
"Specified resource and provider mismatch -- the cloud's provider is AWS, please make sure all the resource you want to edit is for AWS as well."
|
|
3503
|
+
)
|
|
3504
|
+
try:
|
|
3505
|
+
self._edit_aws_cloud(
|
|
3506
|
+
cloud_id=cloud_id,
|
|
3507
|
+
cloud_name=cloud_name,
|
|
3508
|
+
cloud=cloud,
|
|
3509
|
+
cloud_resource=cloud_resource,
|
|
3510
|
+
aws_s3_id=aws_s3_id,
|
|
3511
|
+
aws_efs_id=aws_efs_id,
|
|
3512
|
+
aws_efs_mount_target_ip=aws_efs_mount_target_ip,
|
|
3513
|
+
memorydb_cluster_id=memorydb_cluster_id,
|
|
3514
|
+
yes=yes,
|
|
3515
|
+
)
|
|
3516
|
+
except Exception as e: # noqa: BLE001
|
|
3517
|
+
self.cloud_event_producer.produce(
|
|
3518
|
+
CloudAnalyticsEventName.RESOURCES_EDITED,
|
|
3519
|
+
succeeded=False,
|
|
3520
|
+
logger=self.log,
|
|
3521
|
+
internal_error=str(e),
|
|
3522
|
+
)
|
|
3523
|
+
raise e
|
|
3524
|
+
elif cloud.provider == "GCP":
|
|
3525
|
+
self.cloud_event_producer.init_trace_context(
|
|
3526
|
+
CloudAnalyticsEventCommandName.EDIT,
|
|
3527
|
+
CloudProviders.GCP,
|
|
3528
|
+
cloud_id=cloud_id,
|
|
3529
|
+
)
|
|
3530
|
+
self.cloud_event_producer.produce(
|
|
3531
|
+
CloudAnalyticsEventName.COMMAND_START, succeeded=True
|
|
3532
|
+
)
|
|
3533
|
+
if (
|
|
3534
|
+
not any(
|
|
3535
|
+
[
|
|
3536
|
+
gcp_filestore_instance_id,
|
|
3537
|
+
gcp_filestore_location,
|
|
3538
|
+
gcp_cloud_storage_bucket_name,
|
|
3539
|
+
memorystore_instance_name,
|
|
3540
|
+
]
|
|
3541
|
+
)
|
|
3542
|
+
) or any(
|
|
3543
|
+
[
|
|
3544
|
+
aws_s3_id,
|
|
3545
|
+
aws_efs_id,
|
|
3546
|
+
aws_efs_mount_target_ip,
|
|
3547
|
+
memorydb_cluster_id,
|
|
3548
|
+
]
|
|
3549
|
+
):
|
|
3550
|
+
self.cloud_event_producer.produce(
|
|
3551
|
+
CloudAnalyticsEventName.RESOURCES_EDITED,
|
|
3552
|
+
succeeded=False,
|
|
3553
|
+
internal_error="specified resource and provider mismatch",
|
|
3554
|
+
)
|
|
3555
|
+
raise ClickException(
|
|
3556
|
+
"Specified resource and provider mismatch -- the cloud's provider is GCP, please make sure all the resource you want to edit is for GCP as well."
|
|
3557
|
+
)
|
|
3558
|
+
try:
|
|
3559
|
+
self._edit_gcp_cloud(
|
|
3560
|
+
cloud_id=cloud_id,
|
|
3561
|
+
cloud_name=cloud_name,
|
|
3562
|
+
cloud=cloud,
|
|
3563
|
+
cloud_resource=cloud_resource,
|
|
3564
|
+
gcp_filestore_instance_id=gcp_filestore_instance_id,
|
|
3565
|
+
gcp_filestore_location=gcp_filestore_location,
|
|
3566
|
+
gcp_cloud_storage_bucket_name=gcp_cloud_storage_bucket_name,
|
|
3567
|
+
memorystore_instance_name=memorystore_instance_name,
|
|
3568
|
+
yes=yes,
|
|
3569
|
+
)
|
|
3570
|
+
except Exception as e: # noqa: BLE001
|
|
3571
|
+
self.cloud_event_producer.produce(
|
|
3572
|
+
CloudAnalyticsEventName.RESOURCES_EDITED,
|
|
3573
|
+
succeeded=False,
|
|
3574
|
+
logger=self.log,
|
|
3575
|
+
internal_error=str(e),
|
|
3576
|
+
)
|
|
3577
|
+
raise e
|
|
3578
|
+
else:
|
|
3579
|
+
self.cloud_event_producer.produce(
|
|
3580
|
+
CloudAnalyticsEventName.RESOURCES_EDITED,
|
|
3581
|
+
succeeded=False,
|
|
3582
|
+
internal_error="invalid cloud provider",
|
|
3583
|
+
)
|
|
3584
|
+
raise ClickException(
|
|
3585
|
+
f"Unsupported cloud provider {cloud.provider} for cloud edit!"
|
|
3586
|
+
)
|
|
3587
|
+
|
|
3588
|
+
self.cloud_event_producer.produce(
|
|
3589
|
+
CloudAnalyticsEventName.RESOURCES_EDITED, succeeded=True
|
|
3590
|
+
)
|
|
3591
|
+
# Functional verify.
|
|
3592
|
+
if len(functions_to_verify) > 0:
|
|
3593
|
+
functional_verify_succeed = CloudFunctionalVerificationController(
|
|
3594
|
+
self.cloud_event_producer, self.log
|
|
3595
|
+
).start_verification(
|
|
3596
|
+
cloud_id,
|
|
3597
|
+
self._get_cloud_provider_from_str(cloud.provider),
|
|
3598
|
+
functions_to_verify,
|
|
3599
|
+
yes,
|
|
3600
|
+
)
|
|
3601
|
+
if not functional_verify_succeed:
|
|
3602
|
+
raise ClickException(
|
|
3603
|
+
"Cloud functional verification failed. Please consider the following options:\n"
|
|
3604
|
+
"1. Create a new cloud (we recommend)\n"
|
|
3605
|
+
"2. Double-check the resources specified in the edit details, and the verification results, modify the resource if necessary, run `anyscale cloud verify (optional with functional-verify)` to verify again.\n"
|
|
3606
|
+
"3. Edit the resource back to original if you still want to use this cloud.\n"
|
|
3607
|
+
)
|
|
3608
|
+
|
|
3609
|
+
### End of edit cloud ###
|