├── .dockerignore ├── .gitbook.yaml ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE │ └── pull_request_template.md ├── actions │ ├── build-cluster-init │ │ └── action.yaml │ ├── release-rules │ │ └── action.yaml │ ├── run-cluster-init │ │ └── action.yaml │ └── setup-test-cluster │ │ └── action.yaml └── workflows │ ├── cluster-init.yaml │ ├── codecov-config │ └── codecov.yml │ ├── fluentd-plugin.yaml │ ├── pyfunc-ensembler-job.yaml │ ├── pyfunc-ensembler-service.yaml │ ├── sdk.yaml │ ├── trigger-caraml-doc-sync.yml │ ├── turing-publish.yaml │ └── turing.yaml ├── .gitignore ├── .golangci.yml ├── .pre-commit-config.yaml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── api ├── .env.development ├── .gitignore ├── .golangci.yml ├── Dockerfile ├── Makefile ├── README.md ├── api │ ├── .gitignore │ ├── Makefile │ ├── openapi-sdk.yaml │ ├── openapi.bundle.yaml │ ├── openapi.yaml │ ├── override-sample.yaml │ └── specs │ │ ├── alerts.yaml │ │ ├── common.yaml │ │ ├── ensembler-images.yaml │ │ ├── ensemblers.yaml │ │ ├── experiment-engines.yaml │ │ ├── jobs.yaml │ │ ├── logs.yaml │ │ ├── projects.yaml │ │ └── routers.yaml ├── config-dev-exp-engine.yaml ├── config-dev.yaml ├── db-migrations │ ├── 000001_schema_creation.down.sql │ ├── 000001_schema_creation.up.sql │ ├── 000002_create_alerts_table.down.sql │ ├── 000002_create_alerts_table.up.sql │ ├── 000003_create_events_table.down.sql │ ├── 000003_create_events_table.up.sql │ ├── 000004_enricher_ensembler_service_account.down.sql │ ├── 000004_enricher_ensembler_service_account.up.sql │ ├── 000005_ensembler_type_config.down.sql │ ├── 000005_ensembler_type_config.up.sql │ ├── 000006_traffic_rules.down.sql │ ├── 000006_traffic_rules.up.sql │ ├── 000007_log_config_kafka_serialization_format.down.sql │ ├── 000007_log_config_kafka_serialization_format.up.sql │ ├── 000008_pyfunc_ensemblers.down.sql │ ├── 000008_pyfunc_ensemblers.up.sql │ ├── 000009_ensembling_job.down.sql │ ├── 000009_ensembling_job.up.sql │ ├── 000010_add_pyfunc_config.down.sql │ ├── 000010_add_pyfunc_config.up.sql │ ├── 000011_add_pyfunc_ensemblers_python_version.down.sql │ ├── 000011_add_pyfunc_ensemblers_python_version.up.sql │ ├── 000012_add_traffic_rule_name.down.sql │ ├── 000012_add_traffic_rule_name.up.sql │ ├── 000013_add_default_traffic_rule.down.sql │ ├── 000013_add_default_traffic_rule.up.sql │ ├── 000014_autoscaling_policy.down.sql │ ├── 000014_autoscaling_policy.up.sql │ ├── 000015_add_router_protocol.down.sql │ ├── 000015_add_router_protocol.up.sql │ ├── 000016_add_secrets_columns.down.sql │ └── 000016_add_secrets_columns.up.sql ├── docker-compose.yaml ├── e2e │ └── test │ │ ├── api │ │ └── router.go │ │ ├── cluster │ │ └── cluster.go │ │ ├── config-local.yaml │ │ ├── config.yaml │ │ ├── config │ │ └── config.go │ │ ├── matcher │ │ └── proto.go │ │ ├── router_e2e_test.go │ │ ├── router_test.go │ │ ├── routers_suite_test.go │ │ └── testdata │ │ ├── create_router_nop_logger_proprietary_exp.json.tmpl │ │ ├── create_router_std_ensembler_proprietary_exp.json.tmpl │ │ ├── create_router_upi_simple.json.tmpl │ │ ├── create_router_upi_with_std_ensembler.json.tmpl │ │ ├── create_router_upi_with_traffic_rules.json.tmpl │ │ ├── create_router_with_traffic_rules.json.tmpl │ │ ├── responses │ │ ├── router_proprietary_exp_batch_predict.json │ │ ├── router_proprietary_exp_predict.json │ │ └── traffic_rules │ │ │ ├── no-rules.json │ │ │ ├── traffic-rule-1.json │ │ │ └── traffic-rule-2.json │ │ ├── update_router_high_cpu.json.tmpl │ │ └── update_router_nop_logger_proprietary_exp.json.tmpl ├── environments-dev.yaml ├── go.mod ├── go.sum ├── keto │ └── policies │ │ └── example_policy.json ├── openapi-codegen.yaml └── turing │ ├── api │ ├── alerts_api.go │ ├── alerts_api_test.go │ ├── appcontext.go │ ├── appcontext_test.go │ ├── base_controller.go │ ├── base_controller_test.go │ ├── deployment_controller.go │ ├── deployment_controller_test.go │ ├── ensembler_images_api.go │ ├── ensembler_images_api_test.go │ ├── ensemblers_api.go │ ├── ensemblers_api_it_test.go │ ├── ensemblers_api_test.go │ ├── ensembling_job_api.go │ ├── ensembling_job_api_it_test.go │ ├── ensembling_job_api_test.go │ ├── experiments_api.go │ ├── experiments_api_test.go │ ├── pod_log_api.go │ ├── pod_log_api_test.go │ ├── projects_api.go │ ├── request │ │ ├── ensembler_images.go │ │ ├── ensemblers.go │ │ ├── request.go │ │ └── request_test.go │ ├── request_utils.go │ ├── request_utils_test.go │ ├── response.go │ ├── response_test.go │ ├── route.go │ ├── route_test.go │ ├── router_versions_api.go │ ├── router_versions_api_test.go │ ├── routers_api.go │ └── routers_api_test.go │ ├── batch │ ├── const.go │ ├── ensembling │ │ ├── controller.go │ │ ├── controller_test.go │ │ ├── mock_ensembling_controller.go │ │ ├── runner.go │ │ └── runner_test.go │ └── runner │ │ └── base.go │ ├── cluster │ ├── common.go │ ├── common_test.go │ ├── controller.go │ ├── controller_test.go │ ├── job.go │ ├── job_test.go │ ├── knative_service.go │ ├── knative_service_test.go │ ├── kubernetes_service.go │ ├── kubernetes_service_test.go │ ├── labeller │ │ ├── labeller.go │ │ └── labeller_test.go │ ├── mocks │ │ └── controller.go │ ├── models.go │ ├── pdb.go │ ├── pdb_test.go │ ├── pvc.go │ ├── pvc_test.go │ ├── role.go │ ├── role_binding.go │ ├── role_binding_test.go │ ├── role_test.go │ ├── secret.go │ ├── secret_test.go │ ├── service_account.go │ ├── service_account_test.go │ ├── servicebuilder │ │ ├── fluentd.go │ │ ├── fluentd_test.go │ │ ├── plugins_server.go │ │ ├── router.go │ │ ├── router_test.go │ │ ├── service_builder.go │ │ └── service_builder_test.go │ ├── spark.go │ ├── spark_test.go │ ├── virtual_service.go │ └── virtual_service_test.go │ ├── cmd │ └── main.go │ ├── config │ ├── config.go │ ├── config_test.go │ ├── example.yaml │ └── testdata │ │ ├── config-1.yaml │ │ ├── config-2.yaml │ │ ├── env-config-1.yaml │ │ ├── env-config-nil.yaml │ │ ├── env-err.yaml │ │ ├── invalid-duration-format.yaml │ │ ├── invalid-quantity-format.yaml │ │ └── invalid-type.yaml │ ├── database │ ├── database.go │ ├── integration_config.go │ └── integration_database.go │ ├── generated │ ├── model_big_query_dataset.go │ ├── model_big_query_dataset_config.go │ ├── model_big_query_sink.go │ ├── model_big_query_sink_config.go │ ├── model_dataset.go │ ├── model_ensembler_config.go │ ├── model_ensembler_config_kind.go │ ├── model_ensembler_infra_config.go │ ├── model_ensembling_job_ensembler_spec.go │ ├── model_ensembling_job_ensembler_spec_result.go │ ├── model_ensembling_job_meta.go │ ├── model_ensembling_job_prediction_source.go │ ├── model_ensembling_job_result_type.go │ ├── model_ensembling_job_sink.go │ ├── model_ensembling_job_source.go │ ├── model_ensembling_job_spec.go │ ├── model_ensembling_resources.go │ ├── model_env_var.go │ ├── model_mounted_mlp_secret.go │ └── model_save_mode.go │ ├── imagebuilder │ ├── ensembler.go │ ├── errors.go │ ├── imagebuilder.go │ ├── imagebuilder_test.go │ └── mocks │ │ └── image_builder.go │ ├── internal │ ├── alertschema │ │ └── alertschema.go │ ├── ref │ │ └── types.go │ └── testutils │ │ ├── utils.go │ │ └── validation.go │ ├── log │ └── log.go │ ├── middleware │ ├── openapi_validation.go │ └── openapi_validation_test.go │ ├── models │ ├── alert.go │ ├── alert_test.go │ ├── autoscaling_policy.go │ ├── autoscaling_policy_test.go │ ├── default_traffic_rule.go │ ├── default_traffic_rule_test.go │ ├── enricher.go │ ├── ensembler.go │ ├── ensembling_job.go │ ├── env_var.go │ ├── env_var_test.go │ ├── events.go │ ├── events_test.go │ ├── experiment_engine.go │ ├── experiment_engine_test.go │ ├── log_config.go │ ├── log_config_test.go │ ├── model.go │ ├── pyfunc_ensembler.go │ ├── resource_request.go │ ├── resource_request_test.go │ ├── router.go │ ├── router_test.go │ ├── router_version.go │ ├── router_version_test.go │ ├── routes.go │ ├── routes_test.go │ ├── secret.go │ ├── traffic_rules.go │ └── traffic_rules_test.go │ ├── server │ ├── api.go │ ├── application.go │ ├── healthcheck.go │ └── static.go │ ├── service │ ├── alert_service.go │ ├── alert_service_test.go │ ├── crypto_service.go │ ├── crypto_service_test.go │ ├── ensembler_image_service.go │ ├── ensembler_image_service_test.go │ ├── ensembler_service.go │ ├── ensembler_service_test.go │ ├── ensembling_job_service.go │ ├── ensembling_job_service_test.go │ ├── event_service.go │ ├── event_service_test.go │ ├── experiment_service.go │ ├── experiment_service_test.go │ ├── mlp_service.go │ ├── mlp_service_test.go │ ├── mocks │ │ ├── alert_service.go │ │ ├── crypto_service.go │ │ ├── ensembler_images_service.go │ │ ├── ensemblers_service.go │ │ ├── ensembling_job_service.go │ │ ├── event_service.go │ │ ├── experiments_service.go │ │ ├── mlp_service.go │ │ ├── pod_log_service.go │ │ ├── router_deployment_service.go │ │ ├── router_monitoring_service.go │ │ ├── router_versions_service.go │ │ ├── routers_service.go │ │ └── u_func.go │ ├── pagination.go │ ├── pod_log_service.go │ ├── pod_log_service_test.go │ ├── router_deployment_service.go │ ├── router_deployment_service_test.go │ ├── router_monitoring_service.go │ ├── router_monitoring_service_test.go │ ├── router_service.go │ ├── router_service_test.go │ ├── router_version_service.go │ └── router_version_service_test.go │ ├── testdata │ └── cluster │ │ └── servicebuilder │ │ ├── router_configmap_default.yml │ │ ├── router_configmap_default_upi.yml │ │ ├── router_configmap_ensembling.yml │ │ ├── router_configmap_exp_engine.yml │ │ ├── router_configmap_no_default_route.yml │ │ ├── router_configmap_std_ensembler_lazy_routing.yml │ │ ├── router_configmap_std_ensembler_with_exp_mappings.yml │ │ ├── router_configmap_std_ensembler_with_route_name_path.yml │ │ ├── router_configmap_traffic_splitting.yml │ │ ├── router_version_basic.json │ │ ├── router_version_basic_upi.json │ │ ├── router_version_missing_bigquery.json │ │ ├── router_version_no_default_route.json │ │ ├── router_version_success.json │ │ ├── router_version_success_docker_ensembler.json │ │ ├── router_version_success_experiment_engine.json │ │ ├── router_version_success_std_ensembler_lazy_routing.json │ │ ├── router_version_success_std_ensembler_with_exp_mappings.json │ │ ├── router_version_success_std_ensembler_with_route_name_path.json │ │ └── router_version_success_traffic_splitting.json │ ├── utils │ ├── json.go │ ├── json_test.go │ ├── merge.go │ └── merge_test.go │ ├── validation │ ├── validator.go │ └── validator_test.go │ └── webhook │ ├── mocks │ └── webhook.go │ ├── request.go │ ├── webhook.go │ └── webhook_test.go ├── docker-entrypoint.sh ├── docs ├── .gitbook │ └── assets │ │ ├── activity_log.png │ │ ├── alert_details_panel.png │ │ ├── alerts_config_view.png │ │ ├── alerts_team_panel.png │ │ ├── alerts_validation.png │ │ ├── autoscaling_policy_panel.png │ │ ├── bq_panel.png │ │ ├── configure_alerts_button.png │ │ ├── configure_expriement_engine.png │ │ ├── create_router_button.png │ │ ├── create_router_resources.png │ │ ├── create_router_rules.png │ │ ├── create_router_rules_priority.png │ │ ├── delete_ensembler_modal_active.png │ │ ├── delete_ensembler_modal_inactive.png │ │ ├── delete_ensembler_modal_inactive_filled.png │ │ ├── delete_ensembler_modal_success.png │ │ ├── delete_ensembler_modal_success_filled.png │ │ ├── delete_router_button.png │ │ ├── delete_router_modal.png │ │ ├── delete_version_button.png │ │ ├── delete_version_modal.png │ │ ├── deploy_version_action.png │ │ ├── deployed_router_badge.png │ │ ├── deployed_version_badge.png │ │ ├── docker_container_config.png │ │ ├── edit_router_header.png │ │ ├── edit_router_next_button.png │ │ ├── edit_router_version_comparison.png │ │ ├── ensembler_page.png │ │ ├── ensembler_page_empty.png │ │ ├── env_var_panel.png │ │ ├── failed_router_badge.png │ │ ├── failed_version_badge.png │ │ ├── general_router_settings.png │ │ ├── kafka_panel.png │ │ ├── log_config.png │ │ ├── monitoring_dashboard.png │ │ ├── monitoring_tab.png │ │ ├── nop_ensembler_config.png │ │ ├── not_deployed_router_badge.png │ │ ├── not_deployed_version_badge.png │ │ ├── projects_dropdown.png │ │ ├── pyfunc_ensembler_config.png │ │ ├── redeploy_router.png │ │ ├── redeploy_router_modal.png │ │ ├── redeploy_version_button.png │ │ ├── redeploy_version_config.png │ │ ├── redeploy_version_modal.png │ │ ├── resources_panel.png │ │ ├── router_config.png │ │ ├── router_details_header.png │ │ ├── router_row.png │ │ ├── routes_panel.png │ │ ├── select_undeploy_router.png │ │ ├── service_acc_dropdown.png │ │ ├── standard_ensembler_config.png │ │ ├── standard_ensembler_lazy_routing.png │ │ ├── success_router_delete.png │ │ ├── success_version_delete.png │ │ ├── toggle_alert.png │ │ ├── traffic_split_icon.png │ │ ├── undeploy_router_modal.png │ │ ├── update_alerts_button.png │ │ ├── update_version_history.png │ │ ├── updating_router.png │ │ ├── updating_router_badge.png │ │ ├── updating_version_badge.png │ │ ├── version_comparison.png │ │ ├── version_delete_action.png │ │ ├── versions_list.png │ │ ├── view_router_groupby_routes.png │ │ └── view_router_groupby_rules.png ├── README.md ├── SUMMARY.md ├── assets │ ├── turing_architecture.png │ ├── turing_router_components.png │ └── turing_ui_router_list.png ├── concepts.md ├── dev-docs │ ├── README.md │ └── monitoring-and-alerting │ │ ├── README.md │ │ ├── configure-alerting-backend.md │ │ └── configure-monitoring-backend.md └── how-to │ ├── create-a-router │ ├── README.md │ ├── configure-autoscaling.md │ ├── configure-enricher.md │ ├── configure-ensembler.md │ ├── configure-experiment-engine.md │ ├── configure-general-settings.md │ ├── configure-logging-request-response.md │ ├── configure-routes.md │ └── configure-traffic-rules.md │ ├── delete-a-router │ ├── README.md │ ├── delete-router-version-from-details-page.md │ ├── delete-router-version.md │ └── delete-router.md │ ├── delete-an-ensembler │ ├── README.md │ ├── delete-an-ensembler-active.md │ ├── delete-an-ensembler-inactive.md │ └── delete-an-ensembler.md │ ├── monitor-a-router │ ├── README.md │ ├── configure-alerts.md │ └── monitor-router-performance.md │ ├── redeploy-a-router │ ├── README.md │ ├── redeploy-from-version-detail.md │ ├── redeploy-undeployed-router.md │ └── redeploy-version-from-history.md │ ├── undeploying-router.md │ └── viewing-routers │ ├── README.md │ ├── configuration.md │ ├── edit-router.md │ ├── history.md │ ├── logs.md │ └── more-actions.md ├── engines ├── experiment │ ├── .gitignore │ ├── .golangci.yml │ ├── Makefile │ ├── README.md │ ├── config │ │ ├── config.go │ │ └── config_test.go │ ├── docs │ │ ├── assets │ │ │ ├── example_experiment_manager_router_ui.png │ │ │ ├── exp_manager_plugin_diagram.png │ │ │ ├── exp_runner_plugin_diagram.png │ │ │ ├── experiments_data_model.png │ │ │ └── standard_experiment_ui.png │ │ ├── concepts.md │ │ ├── developer_guide.md │ │ └── rpc_plugins.md │ ├── examples │ │ └── plugins │ │ │ ├── hardcoded │ │ │ ├── Dockerfile │ │ │ ├── Makefile │ │ │ ├── cmd │ │ │ │ └── main.go │ │ │ ├── config.go │ │ │ ├── configs │ │ │ │ ├── plugin_config_example.yaml │ │ │ │ └── router_config_example.json │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ ├── manager.go │ │ │ ├── runner.go │ │ │ ├── runner_test.go │ │ │ └── utils │ │ │ │ └── hash.go │ │ │ └── nop │ │ │ ├── cmd │ │ │ └── cmd.go │ │ │ ├── manager.go │ │ │ └── runner.go │ ├── factory.go │ ├── factory_test.go │ ├── go.mod │ ├── go.sum │ ├── log │ │ ├── hclog.go │ │ ├── hclog │ │ │ └── hclog.go │ │ ├── log.go │ │ └── zap.go │ ├── manager │ │ ├── adapter.go │ │ ├── adapter_test.go │ │ ├── base_standard_manager.go │ │ ├── base_standard_manager_test.go │ │ ├── manager.go │ │ ├── mocks │ │ │ ├── ExperimentManager.go │ │ │ └── StandardExperimentManager.go │ │ ├── testdata │ │ │ └── experiment_runner_config.json │ │ └── types.go │ ├── pkg │ │ └── request │ │ │ ├── request.go │ │ │ └── request_test.go │ ├── plugin.Dockerfile │ ├── plugin │ │ ├── inproc │ │ │ ├── factory.go │ │ │ ├── factory_test.go │ │ │ ├── manager │ │ │ │ ├── manager.go │ │ │ │ └── manager_test.go │ │ │ └── runner │ │ │ │ ├── nop │ │ │ │ └── experiment_runner.go │ │ │ │ ├── runner.go │ │ │ │ └── runner_test.go │ │ └── rpc │ │ │ ├── config.go │ │ │ ├── factory.go │ │ │ ├── factory_test.go │ │ │ ├── manager │ │ │ ├── manager.go │ │ │ ├── plugin.go │ │ │ ├── plugin_test.go │ │ │ ├── rpc_client.go │ │ │ ├── rpc_client_test.go │ │ │ ├── rpc_server.go │ │ │ └── rpc_server_test.go │ │ │ ├── mocks │ │ │ ├── client_protocol.go │ │ │ ├── experiment_manager.go │ │ │ ├── experiment_runner.go │ │ │ └── rpc_client.go │ │ │ ├── rpc.go │ │ │ ├── runner │ │ │ ├── plugin.go │ │ │ ├── plugin_test.go │ │ │ ├── rpc.go │ │ │ ├── rpc_test.go │ │ │ └── runner.go │ │ │ └── shared │ │ │ └── interface.go │ └── runner │ │ ├── intercept_runner.go │ │ ├── interceptor.go │ │ ├── mocks │ │ ├── experiment_runner.go │ │ ├── metrics_registration_helper.go │ │ └── types.go │ │ ├── nop │ │ ├── experiment_runner.go │ │ └── experiment_runner_test.go │ │ └── runner.go ├── pyfunc-ensembler-job │ ├── .dockerignore │ ├── .gitignore │ ├── Dockerfile │ ├── Makefile │ ├── README.md │ ├── app.Dockerfile │ ├── ensembler │ │ ├── __init__.py │ │ ├── dataset.py │ │ ├── ensembler.py │ │ ├── job.py │ │ ├── sink.py │ │ ├── source.py │ │ └── sql │ │ │ ├── bq_join.sql.jinja2 │ │ │ └── bq_select.sql.jinja2 │ ├── entrypoint.sh │ ├── env.yaml │ ├── main.py │ ├── process_conda_env.sh │ ├── requirements.dev.txt │ ├── requirements.txt │ ├── setup.py │ ├── tests │ │ ├── __init__.py │ │ ├── conftest.py │ │ ├── dataset_test.py │ │ ├── ensembler_test.py │ │ ├── sink_test.py │ │ └── utils │ │ │ ├── __init__.py │ │ │ └── openapi_utils.py │ └── version.py ├── pyfunc-ensembler-service │ ├── .dockerignore │ ├── .gitignore │ ├── Dockerfile │ ├── Makefile │ ├── README.md │ ├── app.Dockerfile │ ├── env.yaml │ ├── process_conda_env.sh │ ├── pyfunc_ensembler_runner │ │ ├── __init__.py │ │ ├── __main__.py │ │ ├── ensembler_runner.py │ │ ├── handler.py │ │ └── server.py │ ├── requirements.dev.txt │ ├── requirements.txt │ ├── setup.py │ ├── tests │ │ ├── __init__.py │ │ ├── conftest.py │ │ ├── test_ensembler_runner.py │ │ └── testdata │ │ │ ├── request_invalid.json │ │ │ ├── request_long.json │ │ │ └── request_short.json │ └── version.py └── router │ ├── .env.development │ ├── .gitignore │ ├── .golangci.yml │ ├── Dockerfile │ ├── Makefile │ ├── README.md │ ├── api │ └── openapi.yaml │ ├── compose │ ├── .env.development │ ├── app.yaml │ ├── docs.yaml │ ├── fluentd.yaml │ ├── kafka.yaml │ ├── monitoring.yaml │ └── tracing.yaml │ ├── configs │ ├── custom_ensembling_router.yaml │ ├── default_router.yaml │ └── traffic_splitting_router.yaml │ ├── go.mod │ ├── go.sum │ ├── missionctl │ ├── cmd │ │ └── main.go │ ├── config │ │ ├── config.go │ │ └── config_test.go │ ├── errors │ │ ├── errors.go │ │ └── errors_test.go │ ├── experiment │ │ ├── experiment.go │ │ ├── experiment_test.go │ │ ├── metrics_interceptor.go │ │ └── metrics_interceptor_test.go │ ├── fiberapi │ │ ├── fan_in.go │ │ ├── fan_in_test.go │ │ ├── interceptors.go │ │ ├── interceptors_test.go │ │ ├── internal │ │ │ └── testutils │ │ │ │ ├── helpers.go │ │ │ │ └── mocks.go │ │ ├── routing_policies.go │ │ ├── routing_policies_test.go │ │ ├── routing_strategy.go │ │ ├── routing_strategy_test.go │ │ ├── traffic_splitting_strategy.go │ │ ├── traffic_splitting_strategy_test.go │ │ ├── utils.go │ │ └── utils_test.go │ ├── instrumentation │ │ ├── metrics │ │ │ ├── metrics.go │ │ │ └── metrics_test.go │ │ ├── prometheus.go │ │ └── tracing │ │ │ ├── jaeger.go │ │ │ ├── jaeger_test.go │ │ │ ├── nop.go │ │ │ ├── nop_test.go │ │ │ ├── tracing.go │ │ │ └── tracing_test.go │ ├── internal │ │ ├── benchmark │ │ │ └── benchmark_e2e_test.go │ │ ├── mocks │ │ │ ├── FiberComponent.go │ │ │ ├── MissionControlUPI.go │ │ │ └── ServerTransportStream.go │ │ ├── safe_chan.go │ │ ├── safe_chan_test.go │ │ ├── testutils │ │ │ ├── log.go │ │ │ ├── request.go │ │ │ ├── response.go │ │ │ ├── upi_server.go │ │ │ ├── utils.go │ │ │ └── validation.go │ │ └── version.go │ ├── log │ │ ├── log.go │ │ └── resultlog │ │ │ ├── bigquery.go │ │ │ ├── bigquery_test.go │ │ │ ├── console.go │ │ │ ├── console_test.go │ │ │ ├── fluentd.go │ │ │ ├── fluentd_test.go │ │ │ ├── kafka.go │ │ │ ├── kafka_test.go │ │ │ ├── nop.go │ │ │ ├── nop_test.go │ │ │ ├── proto │ │ │ └── turing │ │ │ │ ├── TuringResultLog.pb.go │ │ │ │ └── TuringResultLog.proto │ │ │ ├── resultlog.go │ │ │ ├── resultlog_test.go │ │ │ ├── upi_kafka.go │ │ │ ├── upi_result_log.go │ │ │ └── upi_result_log_test.go │ ├── mission_control.go │ ├── mission_control_test.go │ ├── mission_control_upi.go │ ├── mission_control_upi_test.go │ ├── server │ │ ├── application.go │ │ ├── constant │ │ │ └── constant.go │ │ ├── http │ │ │ ├── handlers │ │ │ │ ├── batch_http_handler.go │ │ │ │ ├── batch_http_handler_test.go │ │ │ │ ├── compression │ │ │ │ │ └── compression.go │ │ │ │ ├── http_handler.go │ │ │ │ ├── http_handler_test.go │ │ │ │ ├── internal_api.go │ │ │ │ └── internal_api_test.go │ │ │ ├── response.go │ │ │ └── response_test.go │ │ └── upi │ │ │ ├── interceptors │ │ │ ├── recoverer.go │ │ │ └── recoverer_test.go │ │ │ ├── server.go │ │ │ └── server_test.go │ ├── testdata │ │ ├── batch_router_test.yaml │ │ ├── benchmark │ │ │ ├── single_route_upi_grpc.yaml │ │ │ └── single_route_upi_http.yaml │ │ ├── bq_schema_1_order_diff.json │ │ ├── bq_schema_1_original.json │ │ ├── bq_schema_2_field_diff.json │ │ ├── bq_schema_3_required_diff.json │ │ ├── bq_schema_4_nested_schema_diff.json │ │ ├── bq_turing_result_schema.json │ │ ├── enricher_response.json │ │ ├── ensembler_response.json │ │ ├── grpc │ │ │ ├── grpc_router_faulty_port.yaml │ │ │ ├── grpc_router_minimal.yaml │ │ │ └── grpc_router_minimal_two_route.yaml │ │ ├── large_payload.json │ │ ├── medium_payload.json │ │ ├── nop_default_router.yaml │ │ ├── nop_ensembling_response.json │ │ ├── nop_ensembling_router.yaml │ │ ├── route_response_control.json │ │ ├── route_response_route_id_1.json │ │ └── small_payload.json │ ├── turingctx │ │ ├── context.go │ │ └── context_test.go │ └── utils │ │ ├── utils.go │ │ └── utils_test.go │ ├── prometheus │ └── config.yml │ ├── traffic_rule_condition.go │ └── traffic_rule_condition_test.go ├── infra ├── charts │ ├── .helmignore │ ├── turing-init │ │ ├── .helmignore │ │ ├── Chart.lock │ │ ├── Chart.yaml │ │ ├── README.md │ │ ├── README.md.gotmpl │ │ ├── charts │ │ │ └── spark-operator-1.1.7.tgz │ │ ├── templates │ │ │ ├── _helpers.tpl │ │ │ ├── cleanup-job.yaml │ │ │ ├── cluster-role-binding.yaml │ │ │ ├── cluster-role.yaml │ │ │ ├── init-job.yaml │ │ │ ├── istio-operator-config-map.yaml │ │ │ └── serviceaccount.yaml │ │ └── values.yaml │ └── turing │ │ ├── .helmignore │ │ ├── Chart.yaml │ │ ├── README.md │ │ ├── README.md.gotmpl │ │ ├── charts │ │ ├── merlin-0.7.1.tgz │ │ ├── mlp-1.0.1.tgz │ │ └── postgresql-12.1.9.tgz │ │ ├── requirements.lock │ │ ├── requirements.yaml │ │ ├── subcharts │ │ ├── merlin │ │ │ ├── Chart.yaml │ │ │ ├── README.md │ │ │ ├── charts │ │ │ │ ├── postgresql-12.1.9.tgz │ │ │ │ └── vault-0.16.1.tgz │ │ │ ├── requirements.lock │ │ │ ├── requirements.yaml │ │ │ ├── templates │ │ │ │ ├── _helpers.tpl │ │ │ │ ├── configmap-environment.yaml │ │ │ │ ├── deployment.yaml │ │ │ │ ├── ingress.yaml │ │ │ │ ├── initialization-mlflow-db.yaml │ │ │ │ ├── secrets.yaml │ │ │ │ ├── service-swagger.yaml │ │ │ │ └── service.yaml │ │ │ └── values.yaml │ │ └── mlp │ │ │ ├── Chart.yaml │ │ │ ├── README.md │ │ │ ├── charts │ │ │ └── postgresql-12.1.9.tgz │ │ │ ├── db-migrations │ │ │ ├── 1_schema_creation.down.sql │ │ │ ├── 1_schema_creation.up.sql │ │ │ ├── 2_applications_config.down.sql │ │ │ └── 2_applications_config.up.sql │ │ │ ├── requirements.lock │ │ │ ├── requirements.yaml │ │ │ ├── templates │ │ │ ├── _helpers.tpl │ │ │ ├── configmap-db-migrations.yaml │ │ │ ├── deployment.yaml │ │ │ ├── ingress.yaml │ │ │ ├── secrets.yaml │ │ │ └── service.yaml │ │ │ └── values.yaml │ │ ├── templates │ │ ├── _helpers.tpl │ │ ├── cluster-role-binding.yaml │ │ ├── cluster-role.yaml │ │ ├── deployment.yaml │ │ ├── envs-secret.yaml │ │ ├── ingress.yaml │ │ ├── openapi-override-configmap.yaml │ │ ├── secrets.yaml │ │ ├── service-account.yaml │ │ ├── service-turing.yaml │ │ └── virtualservice-mlp.yaml │ │ ├── values.minimal.yaml │ │ └── values.yaml ├── cluster-init │ ├── Dockerfile │ ├── Makefile │ ├── README.md │ ├── cleanup.sh │ ├── init.sh │ └── knative-configmaps │ │ └── config-features.yaml ├── docker-compose │ └── dev │ │ ├── docker-compose.yml │ │ ├── extract_creds.sh │ │ ├── k3s │ │ └── registries.yaml │ │ ├── merlin │ │ ├── deployment-config.yaml │ │ └── dev.env │ │ └── mlp │ │ ├── db-migrations │ │ ├── 1_schema_creation.down.sql │ │ ├── 1_schema_creation.up.sql │ │ ├── 2_applications.down.sql │ │ ├── 2_applications.up.sql │ │ ├── 3_add_turing_app.down.sql │ │ ├── 3_add_turing_app.up.sql │ │ ├── 4_projects.down.sql │ │ ├── 4_projects.up.sql │ │ ├── 5_secrets.down.sql │ │ └── 5_secrets.up.sql │ │ └── dev.env └── e2e │ ├── turing.mockserver.yaml │ ├── turing.values.in-cluster.yaml │ ├── turing.values.remote.yaml │ ├── turing.values.yaml │ ├── vault.helm-values.yaml │ └── vault.ingress.yaml ├── scripts ├── fluentd-bigquery │ ├── .env.template │ ├── .gitignore │ ├── Dockerfile │ ├── Gemfile │ ├── Gemfile.lock │ ├── Makefile │ ├── README.md │ ├── entrypoint.sh │ └── fluent.conf ├── swagger-ui-generator │ ├── README.md │ ├── dist │ │ └── index.html │ └── swagger-ui-generator.sh └── vertagen │ ├── README.md │ └── vertagen.sh ├── sdk ├── .coveragerc ├── .gitignore ├── .openapi-generator-ignore ├── Makefile ├── README.md ├── docs │ ├── README.md │ └── assets │ │ └── turing-sdk-classes.png ├── e2e │ ├── 01_create_router_test.py │ ├── 02_update_router_invalid_test.py │ ├── 03_create_router_version_test.py │ ├── 04_undeploy_router_test.py │ ├── 05_deploy_invalid_config_test.py │ ├── 06_deploy_valid_config_test.py │ ├── 07_delete_router_test.py │ ├── 08_deploy_router_with_traffic_rules_test.py │ ├── 09_deploy_router_with_std_ensembler_test.py │ ├── 10_deploy_router_upi_test.py │ ├── 11_deploy_router_upi_traffic_split_test.py │ ├── 12_deploy_router_upi_std_ensembler_test.py │ └── conftest.py ├── openapi-codegen.yaml ├── requirements.dev.txt ├── requirements.txt ├── samples │ ├── README.md │ ├── batch_job │ │ └── batch_job.py │ ├── common │ │ └── __init__.py │ ├── quickstart │ │ └── quick_start.py │ └── router │ │ ├── create_from_existing_router.py │ │ ├── create_router_with_pyfunc_ensembler.py │ │ ├── edit_a_router.py │ │ └── general.py ├── setup.py ├── tests │ ├── __init__.py │ ├── batch │ │ ├── config │ │ │ ├── config_test.py │ │ │ ├── sink_test.py │ │ │ └── source_test.py │ │ └── job_test.py │ ├── conftest.py │ ├── ensembler_image_test.py │ ├── ensembler_test.py │ ├── project_test.py │ ├── router │ │ ├── config │ │ │ ├── autoscaling_policy_test.py │ │ │ ├── enricher_test.py │ │ │ ├── experiment_config_test.py │ │ │ ├── log_config_test.py │ │ │ ├── resource_request_test.py │ │ │ ├── route_test.py │ │ │ ├── router_config_test.py │ │ │ ├── router_ensembler_config_test.py │ │ │ ├── router_version_test.py │ │ │ └── traffic_rule_test.py │ │ └── router_test.py │ └── testdata │ │ └── api_responses │ │ ├── create_router_0000.json │ │ ├── get_job_0000.json │ │ ├── list_jobs_0000.json │ │ └── submit_job_0000.json └── turing │ ├── __init__.py │ ├── _base_types.py │ ├── batch │ ├── __init__.py │ ├── config │ │ ├── __init__.py │ │ ├── config.py │ │ ├── sink.py │ │ └── source.py │ └── job.py │ ├── ensembler.py │ ├── ensembler_image.py │ ├── generated │ ├── __init__.py │ ├── api │ │ ├── __init__.py │ │ ├── ensembler_api.py │ │ ├── ensembler_images_api.py │ │ ├── ensembling_job_api.py │ │ ├── project_api.py │ │ └── router_api.py │ ├── api_client.py │ ├── apis │ │ └── __init__.py │ ├── configuration.py │ ├── exceptions.py │ ├── model │ │ ├── __init__.py │ │ ├── autoscaling_policy.py │ │ ├── big_query_config.py │ │ ├── big_query_dataset.py │ │ ├── big_query_dataset_all_of.py │ │ ├── big_query_dataset_config.py │ │ ├── big_query_sink.py │ │ ├── big_query_sink_all_of.py │ │ ├── big_query_sink_config.py │ │ ├── build_ensembler_image_request.py │ │ ├── dataset.py │ │ ├── default_traffic_rule.py │ │ ├── enricher.py │ │ ├── ensembler.py │ │ ├── ensembler_config.py │ │ ├── ensembler_config_kind.py │ │ ├── ensembler_docker_config.py │ │ ├── ensembler_id.py │ │ ├── ensembler_image.py │ │ ├── ensembler_image_runner_type.py │ │ ├── ensembler_images.py │ │ ├── ensembler_infra_config.py │ │ ├── ensembler_job_status.py │ │ ├── ensembler_pyfunc_config.py │ │ ├── ensembler_standard_config.py │ │ ├── ensembler_standard_config_experiment_mappings.py │ │ ├── ensembler_type.py │ │ ├── ensemblers_paginated_results.py │ │ ├── ensemblers_paginated_results_all_of.py │ │ ├── ensemblers_paginated_results_all_of1.py │ │ ├── ensembling_job.py │ │ ├── ensembling_job_ensembler_spec.py │ │ ├── ensembling_job_ensembler_spec_result.py │ │ ├── ensembling_job_meta.py │ │ ├── ensembling_job_paginated_results.py │ │ ├── ensembling_job_paginated_results_all_of.py │ │ ├── ensembling_job_prediction_source.py │ │ ├── ensembling_job_prediction_source_all_of.py │ │ ├── ensembling_job_result_type.py │ │ ├── ensembling_job_sink.py │ │ ├── ensembling_job_source.py │ │ ├── ensembling_job_spec.py │ │ ├── ensembling_resources.py │ │ ├── env_var.py │ │ ├── event.py │ │ ├── experiment_config.py │ │ ├── field_source.py │ │ ├── generic_dataset.py │ │ ├── generic_ensembler.py │ │ ├── generic_sink.py │ │ ├── id_object.py │ │ ├── image_building_job_state.py │ │ ├── image_building_job_status.py │ │ ├── job_id.py │ │ ├── kafka_config.py │ │ ├── label.py │ │ ├── log_level.py │ │ ├── mounted_mlp_secret.py │ │ ├── pagination_paging.py │ │ ├── project.py │ │ ├── protocol.py │ │ ├── py_func_ensembler.py │ │ ├── py_func_ensembler_all_of.py │ │ ├── resource_request.py │ │ ├── result_logger_type.py │ │ ├── route.py │ │ ├── router.py │ │ ├── router_config.py │ │ ├── router_details.py │ │ ├── router_details_all_of.py │ │ ├── router_ensembler_config.py │ │ ├── router_events.py │ │ ├── router_id.py │ │ ├── router_id_and_version.py │ │ ├── router_id_object.py │ │ ├── router_status.py │ │ ├── router_version.py │ │ ├── router_version_config.py │ │ ├── router_version_config_log_config.py │ │ ├── router_version_log_config.py │ │ ├── router_version_status.py │ │ ├── save_mode.py │ │ ├── traffic_rule.py │ │ └── traffic_rule_condition.py │ ├── model_utils.py │ ├── models │ │ └── __init__.py │ └── rest.py │ ├── mounted_mlp_secret.py │ ├── project.py │ ├── router │ ├── __init__.py │ ├── config │ │ ├── __init__.py │ │ ├── autoscaling_policy.py │ │ ├── common │ │ │ ├── __init__.py │ │ │ └── env_var.py │ │ ├── enricher.py │ │ ├── experiment_config.py │ │ ├── log_config.py │ │ ├── resource_request.py │ │ ├── route.py │ │ ├── router_config.py │ │ ├── router_ensembler_config.py │ │ ├── router_version.py │ │ └── traffic_rule.py │ └── router.py │ ├── session.py │ └── version.py └── ui ├── .env ├── .env.development ├── .gitignore ├── README.md ├── craco.config.js ├── package.json ├── public ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── app.config.js ├── apple-touch-icon.png ├── browserconfig.xml ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── index.html ├── logo.svg ├── mstile-144x144.png ├── mstile-150x150.png ├── mstile-310x150.png ├── mstile-310x310.png ├── mstile-70x70.png ├── robots.txt ├── safari-pinned-tab.svg └── site.webmanifest ├── src ├── App.js ├── AppRoutes.js ├── Home.js ├── PrivateLayout.js ├── PrivateLayout.scss ├── assets │ ├── icons │ │ └── service-account.svg │ └── style.scss ├── bootstrap.js ├── components │ ├── card │ │ └── DraggableCardHeader.js │ ├── collapsible_badge_group │ │ ├── CollapsibleBadgeGroup.js │ │ └── CollapsibleBadgeGroup.scss │ ├── config_multi_section_panel │ │ └── ConfigMultiSectionPanel.js │ ├── config_section │ │ ├── ConfigSection.js │ │ ├── ConfigSectionPanel.js │ │ ├── ConfigSectionPanelTitle.js │ │ ├── ConfigSectionTitle.js │ │ ├── index.js │ │ └── index.scss │ ├── confirmation_modal │ │ ├── ConfirmationModal.js │ │ └── ConfirmationModal.scss │ ├── expandable_container │ │ ├── ExpandableContainer.js │ │ └── ExpandableContainer.scss │ ├── form │ │ ├── combo_box │ │ │ ├── ComboboxSuggestItem.js │ │ │ └── EuiComboBoxSelect.js │ │ ├── described_form_group │ │ │ ├── DescribedFormGroup.js │ │ │ └── DescribedFormGroup.scss │ │ ├── docker_image_combo_box │ │ │ ├── DockerRegistryPopover.js │ │ │ ├── SelectDockerImageComboBox.js │ │ │ └── SelectDockerImageComboBox.scss │ │ ├── endpoint_combo_box │ │ │ └── SelectEndpointComboBox.js │ │ ├── field_duration │ │ │ └── EuiFieldDuration.js │ │ ├── functions │ │ │ ├── extensible_function.js │ │ │ └── stackable_function.js │ │ ├── hooks │ │ │ └── useOnChangeHandler.js │ │ ├── in_memory_table_form │ │ │ ├── InMemoryTableForm.js │ │ │ └── InMemoryTableForm.scss │ │ ├── label_with_tooltip │ │ │ └── FormLabelWithToolTip.js │ │ ├── service_account_combo_box │ │ │ └── ServiceAccountComboBox.js │ │ └── utils.js │ ├── horizontal_description_list │ │ └── HorizontalDescriptionList.js │ ├── overlay_mask │ │ ├── OverlayMask.js │ │ └── OverlayMask.scss │ ├── page │ │ └── PageTitle.js │ ├── pod_logs_viewer │ │ ├── PodLogsViewer.js │ │ └── hooks │ │ │ ├── useLogsApiEmitter.js │ │ │ └── useLogsEmitter.js │ ├── remote_component │ │ ├── ExperimentEngineComponentLoader.js │ │ └── RemoteComponent.js │ ├── status_badge │ │ ├── StatusBadge.js │ │ └── StatusBadge.scss │ ├── status_health │ │ └── DeploymentStatusHealth.js │ └── validation.js ├── config.js ├── ensembler │ ├── components │ │ ├── modal │ │ │ ├── DeleteEnsemblerModal.js │ │ │ └── useEnsemblerModal.js │ │ └── table │ │ │ ├── ListEnsemblingJobsForEnsemblerTable.js │ │ │ └── ListRouterVersionsForEnsemblerTable.js │ └── list │ │ ├── ListEnsemblersTable.js │ │ └── ListEnsemblersView.js ├── experiment │ └── ExperimentsRouter.js ├── hooks │ ├── useDimension.js │ ├── useDynamicScript.js │ ├── useInitiallyLoaded.js │ ├── useMerlinApi.js │ ├── useRemoteComponent.js │ ├── useTuringApi.js │ ├── useTuringPollingApi.js │ └── useTuringPollingApiEmitter.js ├── index.js ├── jobs │ ├── EnsemblingJobsRouter.js │ ├── components │ │ ├── job_details_header │ │ │ └── EnsemblingJobDetailsPageHeader.js │ │ ├── modal │ │ │ ├── DeleteJobModal.js │ │ │ └── useJobModal.js │ │ └── page_navigation │ │ │ └── EnsemblingJobDetailsPageNavigation.js │ ├── details │ │ ├── EnsemblingJobDetailsView.js │ │ ├── config │ │ │ ├── EnsemblingJobConfigView.js │ │ │ ├── components │ │ │ │ └── bq_options_table │ │ │ │ │ └── BigQueryOptionsTable.js │ │ │ ├── configuration_section │ │ │ │ ├── ConfigurationConfigSection.js │ │ │ │ ├── misc_table │ │ │ │ │ └── MiscConfigSection.js │ │ │ │ └── resource_request_table │ │ │ │ │ └── ResourceRequestConfigTable.js │ │ │ ├── prediction_config_section │ │ │ │ ├── PredictionSourceConfigSection.js │ │ │ │ ├── PredictionsConfigSection.js │ │ │ │ └── PredictionsConfigSection.scss │ │ │ ├── sink_config_section │ │ │ │ ├── SinkConfigSection.js │ │ │ │ ├── bq_config_section │ │ │ │ │ └── BigQuerySinkConfigTable.js │ │ │ │ └── sink_config_table │ │ │ │ │ └── SinkConfigTable.js │ │ │ └── source_config_section │ │ │ │ ├── SourceConfigSection.js │ │ │ │ └── bq_config_section │ │ │ │ ├── BigQueryDatasetQueryView.js │ │ │ │ ├── BigQueryDatasetSection.js │ │ │ │ └── BigQueryDatasetSection.scss │ │ └── logs │ │ │ └── EnsemblingJobLogsView.js │ └── list │ │ ├── ListEnsemblingJobsLandingView.js │ │ ├── ListEnsemblingJobsTable.js │ │ └── ListEnsemblingJobsView.js ├── providers │ ├── docker │ │ └── context.js │ ├── endpoints │ │ ├── MerlinEndpointsProvider.js │ │ └── context.js │ ├── ensemblers │ │ └── context.js │ ├── environments │ │ └── context.js │ ├── experiments │ │ ├── ExperimentEngineContextProvider.js │ │ └── context.js │ ├── secrets │ │ └── context.js │ └── teams │ │ ├── TeamsProvider.js │ │ └── context.js ├── router │ ├── activity_log │ │ ├── RouterActivityLogView.js │ │ ├── RouterActivityLogView.scss │ │ └── events_list │ │ │ ├── EventsList.js │ │ │ └── EventsList.scss │ ├── alerts │ │ ├── RouterAlertsView.js │ │ ├── components │ │ │ ├── alert_config_section │ │ │ │ ├── AlertConfigSection.js │ │ │ │ └── AlertConfigSection.scss │ │ │ └── edit_alerts_form │ │ │ │ ├── components │ │ │ │ ├── EditAlertsForm.js │ │ │ │ ├── field_duration │ │ │ │ │ └── FieldDuration.js │ │ │ │ ├── metric_panel │ │ │ │ │ ├── MetricPanel.js │ │ │ │ │ └── MetricPanel.scss │ │ │ │ └── team_panel │ │ │ │ │ └── TeamPanel.js │ │ │ │ ├── hooks │ │ │ │ └── useAlertsApi.js │ │ │ │ └── validation │ │ │ │ └── schema.js │ │ ├── config.js │ │ ├── details │ │ │ └── RouterAlertDetails.js │ │ └── edit │ │ │ └── EditAlertsView.js │ ├── components │ │ ├── configuration │ │ │ ├── RouterConfigDetails.js │ │ │ └── components │ │ │ │ ├── EnricherConfigSection.js │ │ │ │ ├── EnsemblerConfigSection.js │ │ │ │ ├── ExperimentConfigSection.js │ │ │ │ ├── LoggingConfigSection.js │ │ │ │ ├── ResourcesConfigTable.js │ │ │ │ ├── RouterConfigSection.js │ │ │ │ ├── config.js │ │ │ │ ├── docker_config_section │ │ │ │ ├── ContainerConfigTable.js │ │ │ │ ├── DockerConfigViewGroup.js │ │ │ │ ├── EnvVariablesConfigTable.js │ │ │ │ ├── SecretsConfigTable.js │ │ │ │ └── SecretsConfigTable.scss │ │ │ │ ├── experiment_config_section │ │ │ │ ├── StandardExperimentConfigGroup.js │ │ │ │ ├── credentials │ │ │ │ │ ├── CredentialsConfigSection.js │ │ │ │ │ └── CredentialsConfigSection.scss │ │ │ │ ├── experiments │ │ │ │ │ ├── ExperimentsConfigTable.js │ │ │ │ │ └── ExperimentsConfigTable.scss │ │ │ │ └── variables │ │ │ │ │ └── VariablesConfigTable.js │ │ │ │ ├── nop_config_section │ │ │ │ └── NopConfigViewGroup.js │ │ │ │ ├── pyfunc_config_section │ │ │ │ ├── PyFuncConfigTable.js │ │ │ │ └── PyFuncConfigViewGroup.js │ │ │ │ ├── route_config_section │ │ │ │ └── RouteConfigSection.js │ │ │ │ ├── router_config_section │ │ │ │ ├── RoutesConfigTable.js │ │ │ │ ├── RoutesTableExpandedRow.js │ │ │ │ └── RulesConfigTable.js │ │ │ │ └── standard_config_section │ │ │ │ ├── FallbackRouteConfigSection.js │ │ │ │ ├── RoutingStrategyConfigSection.js │ │ │ │ ├── StandardConfigViewGroup.js │ │ │ │ └── TreatmentMappingConfigSection.js │ │ ├── form │ │ │ ├── CreateRouterForm.js │ │ │ ├── UpdateRouterForm.js │ │ │ ├── components │ │ │ │ ├── CPULimitsFormGroup.js │ │ │ │ ├── Panel.js │ │ │ │ ├── ResourcesPanel.js │ │ │ │ ├── RouteDropDownOption.js │ │ │ │ ├── RouterCreationSummary.js │ │ │ │ ├── RouterUpdateSummary.js │ │ │ │ ├── VersionCreationSummary.js │ │ │ │ ├── autoscaling_policy │ │ │ │ │ ├── AutoscalingPolicyPanel.js │ │ │ │ │ ├── AutoscalingPolicyPanelFlyout.js │ │ │ │ │ └── typeOptions.js │ │ │ │ ├── docker_config │ │ │ │ │ ├── DockerConfigFormGroup.js │ │ │ │ │ ├── DockerDeploymentPanel.js │ │ │ │ │ ├── EnvVariablesPanel.js │ │ │ │ │ ├── SecretsPanel.js │ │ │ │ │ └── SecretsPanel.scss │ │ │ │ ├── enricher_config │ │ │ │ │ ├── EnricherTypePanel.js │ │ │ │ │ └── typeOptions.js │ │ │ │ ├── ensembler_config │ │ │ │ │ ├── EnsemblerTypePanel.js │ │ │ │ │ ├── RouteSelectionPanel.js │ │ │ │ │ ├── standard_ensembler │ │ │ │ │ │ ├── StandardEnsemblerFormGroup.js │ │ │ │ │ │ ├── StandardEnsemblerPanel.js │ │ │ │ │ │ ├── StandardEnsemblerRoutingStrategyPanel.js │ │ │ │ │ │ ├── TreatmentMappingCard.js │ │ │ │ │ │ ├── TreatmentMappingCard.scss │ │ │ │ │ │ └── typeOptions.js │ │ │ │ │ └── typeOptions.js │ │ │ │ ├── experiment_config │ │ │ │ │ ├── EngineTypePanel.js │ │ │ │ │ ├── ExperimentConfigPanel.js │ │ │ │ │ ├── StandardExperimentConfigGroup.js │ │ │ │ │ ├── components │ │ │ │ │ │ ├── client_config │ │ │ │ │ │ │ └── ClientConfigPanel.js │ │ │ │ │ │ ├── experiments_config │ │ │ │ │ │ │ ├── ExperimentsConfigPanel.js │ │ │ │ │ │ │ └── experiment_card │ │ │ │ │ │ │ │ └── ExperimentCard.js │ │ │ │ │ │ └── variables_config │ │ │ │ │ │ │ ├── VariableConfigRow.js │ │ │ │ │ │ │ ├── VariableConfigRow.scss │ │ │ │ │ │ │ └── VariablesConfigPanel.js │ │ │ │ │ ├── config.js │ │ │ │ │ ├── providers │ │ │ │ │ │ ├── ExperimentContextProvider.js │ │ │ │ │ │ └── context.js │ │ │ │ │ ├── typeOptions.js │ │ │ │ │ └── validation │ │ │ │ │ │ └── schema.js │ │ │ │ ├── nop_config │ │ │ │ │ └── NopConfigFormGroup.js │ │ │ │ ├── outcome_config │ │ │ │ │ ├── ResultLoggingTypePanel.js │ │ │ │ │ ├── bigquery │ │ │ │ │ │ └── BigQueryConfigPanel.js │ │ │ │ │ ├── kafka │ │ │ │ │ │ └── KafkaConfigPanel.js │ │ │ │ │ └── typeOptions.js │ │ │ │ ├── pyfunc_config │ │ │ │ │ ├── PyFuncConfigFormGroup.js │ │ │ │ │ └── PyFuncDeploymentPanel.js │ │ │ │ └── router_config │ │ │ │ │ ├── GeneralSettingsPanel.js │ │ │ │ │ ├── RoutesPanel.js │ │ │ │ │ ├── RulesPanel.js │ │ │ │ │ ├── RulesPanelFlyout.js │ │ │ │ │ ├── route_card │ │ │ │ │ ├── RouteCard.js │ │ │ │ │ └── RouteCard.scss │ │ │ │ │ ├── rule_card │ │ │ │ │ ├── RuleCard.js │ │ │ │ │ └── RuleCard.scss │ │ │ │ │ └── typeOptions.js │ │ │ ├── steps │ │ │ │ ├── EnricherStep.js │ │ │ │ ├── EnsemblerStep.js │ │ │ │ ├── ExperimentStep.js │ │ │ │ ├── OutcomeStep.js │ │ │ │ └── RouterStep.js │ │ │ └── validation │ │ │ │ └── schema.js │ │ ├── page_header │ │ │ ├── PageSecondaryHeader.js │ │ │ └── PageSecondaryHeader.scss │ │ ├── request_field_source │ │ │ ├── FieldSourceFormLabel.js │ │ │ └── FieldSourceFormLabel.scss │ │ ├── router_endpoint │ │ │ └── RouterEndpoint.js │ │ └── traffic_rule_condition │ │ │ └── TrafficRuleCondition.js │ ├── create │ │ └── CreateRouterView.js │ ├── details │ │ ├── RouterDetailsView.js │ │ ├── components │ │ │ ├── RouterActions.js │ │ │ ├── modals │ │ │ │ ├── DeleteRouterModal.js │ │ │ │ ├── RedeployRouterModal.js │ │ │ │ ├── UndeployRouterModal.js │ │ │ │ └── useRouterModal.js │ │ │ ├── page_navigation │ │ │ │ └── RouterDetailsPageNavigation.js │ │ │ └── router_details_header │ │ │ │ ├── RouterDetailsPageHeader.js │ │ │ │ └── RouterDetailsPageHeader.scss │ │ ├── config │ │ │ └── RouterConfigView.js │ │ └── logs │ │ │ └── RouterLogsView.js │ ├── edit │ │ ├── EditRouterView.js │ │ └── components │ │ │ └── VersionComparisonView.js │ ├── history │ │ └── HistoryView.js │ ├── list │ │ ├── ListRoutersTable.js │ │ └── ListRoutersView.js │ └── versions │ │ ├── comparison │ │ └── VersionComparisonView.js │ │ ├── components │ │ ├── DefaultItemAction.js │ │ ├── RouterVersionActions.js │ │ ├── modals │ │ │ ├── DeleteVersionModal.js │ │ │ ├── VersionDeploymentModal.js │ │ │ └── useVersionModal.js │ │ └── version_diff │ │ │ └── VersionComparisonPanel.js │ │ ├── details │ │ ├── RouterVersionDetailsView.js │ │ ├── config │ │ │ └── RouterVersionConfigView.js │ │ ├── page_navigation │ │ │ └── RouterVersionDetailsPageNavigation.js │ │ └── version_details_header │ │ │ └── VersionDetailsPageHeader.js │ │ └── list │ │ ├── ListRouterVersionsTable.js │ │ ├── ListRouterVersionsView.js │ │ └── ListRouterVersionsView.scss ├── serviceWorker.js ├── services │ ├── alerts │ │ └── TuringAlerts.js │ ├── ensembler │ │ ├── DockerEnsembler.js │ │ ├── Ensembler.js │ │ ├── EnsemblerType.js │ │ ├── NopEnsembler.js │ │ ├── PyFuncEnsembler.js │ │ ├── StandardEnsembler.js │ │ └── index.js │ ├── enum │ │ └── Enum.js │ ├── experiment_engine │ │ ├── BaseExperimentEngine.js │ │ ├── ExperimentEngine.js │ │ ├── NopExperimentEngine.js │ │ └── index.js │ ├── job │ │ ├── JobStatus.js │ │ ├── SinkType.js │ │ └── SourceType.js │ ├── logs │ │ └── LogEntry.js │ ├── router │ │ └── TuringRouter.js │ ├── status │ │ └── Status.js │ └── version │ │ └── RouterVersion.js ├── setupProxy.js └── utils │ ├── createStackdriverUrl.js │ ├── gcp.js │ ├── object.js │ └── validation.js └── yarn.lock /.dockerignore: -------------------------------------------------------------------------------- 1 | ** 2 | 3 | !api/** 4 | !ui/build/** 5 | !docker-entrypoint.sh 6 | !scripts/swagger-ui-generator/** 7 | -------------------------------------------------------------------------------- /.gitbook.yaml: -------------------------------------------------------------------------------- 1 | root: ./docs/ 2 | 3 | structure: 4 | readme: README.md 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Support Request 4 | url: https://github.com/caraml-dev/turing/blob/main/CONTRIBUTING.md#question 5 | about: Questions and requests for support 6 | -------------------------------------------------------------------------------- /.github/workflows/codecov-config/codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | patch: 4 | default: 5 | threshold: 0.03% -------------------------------------------------------------------------------- /.github/workflows/trigger-caraml-doc-sync.yml: -------------------------------------------------------------------------------- 1 | # This workflow triggers sync of docs to caraml-dev/docs 2 | 3 | name: Trigger CaraML Docs Sync 4 | 5 | on: 6 | push: 7 | tags: 8 | - 'v[0-9]+.[0-9]+.[0-9]+*' 9 | 10 | jobs: 11 | trigger-sync: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Trigger Remote Doc Sync Workflow 15 | uses: caraml-dev/docs/.github/actions/trigger-remote-docs-sync@main 16 | with: 17 | module: 'router' 18 | git_https: 'https://github.com/caraml-dev/turing.git' 19 | doc_folder: 'docs' 20 | ref_name: ${{ github.ref_name }} 21 | ref_type: ${{ github.ref_type }} 22 | credentials: ${{ secrets.CARAML_SYNC }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | config-dev-w-creds.yaml 3 | environments-dev-w-creds.yaml 4 | .idea/ -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | build-tags: 3 | - e2e 4 | skip-dirs: 5 | - turing/generated 6 | 7 | linters: 8 | enable: 9 | - bodyclose 10 | - errcheck 11 | - gocyclo 12 | - gofmt 13 | - goimports 14 | - gosimple 15 | - govet 16 | - ineffassign 17 | - lll 18 | - misspell 19 | - revive 20 | - staticcheck 21 | - unused 22 | 23 | linters-settings: 24 | gocyclo: 25 | # Min code complexity to report, 30 by default (recommended 10-20) 26 | min-complexity: 25 27 | lll: 28 | line-length: 120 29 | -------------------------------------------------------------------------------- /api/.golangci.yml: -------------------------------------------------------------------------------- 1 | ../.golangci.yml -------------------------------------------------------------------------------- /api/api/.gitignore: -------------------------------------------------------------------------------- 1 | swagger-ui-dist/ 2 | .openapi-generator/ 3 | .openapi-generator-ignore 4 | README.md 5 | -------------------------------------------------------------------------------- /api/api/Makefile: -------------------------------------------------------------------------------- 1 | ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) 2 | 3 | all: swagger-ui-dist 4 | 5 | .PHONY: bundle-openapi 6 | bundle-openapi: 7 | @docker run --rm \ 8 | --volume ${ROOT_DIR}:/local \ 9 | openapitools/openapi-generator-cli:v5.1.1 generate \ 10 | --input-spec /local/openapi.yaml \ 11 | --generator-name openapi-yaml \ 12 | --additional-properties outputFile=openapi.bundle.yaml \ 13 | --output /local 14 | @for f in ".openapi-generator" ".openapi-generator-ignore" "README.md" ; do \ 15 | rm -rf ${ROOT_DIR}/$${f} ;\ 16 | done 17 | 18 | .PHONY: swagger-ui-dist 19 | swagger-ui-dist: bundle-openapi 20 | @../../scripts/swagger-ui-generator/swagger-ui-generator.sh \ 21 | --spec-file openapi.bundle.yaml \ 22 | --output swagger-ui-dist 23 | -------------------------------------------------------------------------------- /api/api/override-sample.yaml: -------------------------------------------------------------------------------- 1 | # Here it is possible to override the OpenAPI spec as long as it follows the OAS3 specifications. 2 | # Make sure to input the required configurations in .OpenapiConfig.SpecOverrideFile 3 | # and ensure that .OpenapiConfig.SwaggerUIConfig is configured as well as it only overrides 4 | # the bundled yaml file. 5 | # 6 | # To generate the bundled file, run make swagger-ui-dist in this folder. 7 | components: 8 | schemas: 9 | ExperimentEngineType: 10 | enum: 11 | - nop 12 | - proprietary 13 | -------------------------------------------------------------------------------- /api/db-migrations/000001_schema_creation.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS routers CASCADE; 2 | DROP TABLE IF EXISTS router_versions CASCADE; 3 | DROP TABLE IF EXISTS enrichers; 4 | DROP TABLE IF EXISTS ensemblers; 5 | DROP TYPE IF EXISTS router_status; -------------------------------------------------------------------------------- /api/db-migrations/000002_create_alerts_table.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS alerts; -------------------------------------------------------------------------------- /api/db-migrations/000003_create_events_table.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS events; -------------------------------------------------------------------------------- /api/db-migrations/000003_create_events_table.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS events 2 | ( 3 | id serial PRIMARY KEY, 4 | 5 | router_id integer NOT NULL references routers (id) ON DELETE CASCADE, 6 | version integer NOT NULL, 7 | event_type varchar(25) NOT NULL, 8 | stage varchar(128) NOT NULL, 9 | message text NOT NULL, 10 | 11 | created_at timestamp NOT NULL default current_timestamp, 12 | updated_at timestamp NOT NULL default current_timestamp 13 | ); 14 | 15 | -- Index by router id. 16 | CREATE INDEX events_idx ON events (router_id); 17 | -------------------------------------------------------------------------------- /api/db-migrations/000004_enricher_ensembler_service_account.down.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE ensemblers 2 | DROP COLUMN service_account; 3 | 4 | ALTER TABLE enrichers 5 | DROP COLUMN service_account; -------------------------------------------------------------------------------- /api/db-migrations/000004_enricher_ensembler_service_account.up.sql: -------------------------------------------------------------------------------- 1 | -- This update allows users to select the service account to be mounted in the 2 | -- user-container in enricher and ensembler turing component 3 | -- 4 | -- "service_account" column represents the secret name registered in the MLP project 5 | -- that contains the service account JSON key value. 6 | 7 | ALTER TABLE ensemblers 8 | ADD service_account text; 9 | 10 | ALTER TABLE enrichers 11 | ADD service_account text; -------------------------------------------------------------------------------- /api/db-migrations/000006_traffic_rules.down.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE router_versions 2 | DROP COLUMN traffic_rules; -------------------------------------------------------------------------------- /api/db-migrations/000006_traffic_rules.up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE router_versions 2 | ADD traffic_rules jsonb; -------------------------------------------------------------------------------- /api/db-migrations/000007_log_config_kafka_serialization_format.down.sql: -------------------------------------------------------------------------------- 1 | -- Remove 'serialization_format' field in 'kafka_config' 2 | 3 | UPDATE router_versions 4 | SET log_config = (SELECT log_config #- '{kafka_config,serialization_format}') 5 | WHERE log_config->>'result_logger_type' = 'kafka'; 6 | -------------------------------------------------------------------------------- /api/db-migrations/000007_log_config_kafka_serialization_format.up.sql: -------------------------------------------------------------------------------- 1 | -- Update log_config field value in router_versions table for 'kafka' logger type 2 | -- by adding 'serialization_format' in the 'kafka_config' field value. This is 3 | -- to support multiple serialization format 'json' / 'protobuf' in newer 4 | -- version of Turing. 5 | -- 6 | -- In previous SQL schema, there is no 'serialization_format' field and it is assumed 7 | -- to always be JSON. Hence, this migration script will set the 'serialization_format' 8 | -- for all existing rows to 'json'. 9 | 10 | UPDATE router_versions 11 | SET log_config = ( 12 | SELECT jsonb_set(log_config, '{kafka_config,serialization_format}', '"json"'::jsonb) 13 | ) 14 | WHERE log_config->>'result_logger_type' = 'kafka' AND 15 | log_config->'kafka_config'->>'serialization_format' IS NULL; 16 | -------------------------------------------------------------------------------- /api/db-migrations/000008_pyfunc_ensemblers.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS pyfunc_ensemblers; 2 | 3 | DROP TABLE IF EXISTS ensemblers; 4 | 5 | ALTER TABLE ensembler_configs RENAME TO ensemblers; 6 | -------------------------------------------------------------------------------- /api/db-migrations/000008_pyfunc_ensemblers.up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE ensemblers RENAME TO ensembler_configs; 2 | 3 | CREATE TABLE IF NOT EXISTS ensemblers 4 | ( 5 | id serial PRIMARY KEY, 6 | project_id integer NOT NULL, 7 | name varchar(50) , 8 | type varchar(20) , 9 | 10 | created_at timestamp NOT NULL default current_timestamp, 11 | updated_at timestamp NOT NULL default current_timestamp, 12 | 13 | CONSTRAINT uc_ensemblers_project_name UNIQUE (project_id, name) 14 | ); 15 | 16 | CREATE TABLE IF NOT EXISTS pyfunc_ensemblers 17 | ( 18 | mlflow_experiment_id integer NOT NULL, 19 | mlflow_run_id varchar(32) NOT NULL, 20 | artifact_uri varchar(128) NOT NULL, 21 | 22 | CONSTRAINT uc_pyfunc_ensemblers_project_name UNIQUE (project_id, name) 23 | ) inherits (ensemblers); 24 | -------------------------------------------------------------------------------- /api/db-migrations/000009_ensembling_job.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS ensembling_jobs; 2 | 3 | DROP TYPE IF EXISTS ensembling_job_status; 4 | -------------------------------------------------------------------------------- /api/db-migrations/000010_add_pyfunc_config.down.sql: -------------------------------------------------------------------------------- 1 | -- Removes pyfunc_config from table. 2 | -- Hence, this migration involves data loss for ensemblers with type that is "pyfunc" 3 | ALTER TABLE ensembler_configs DROP COLUMN pyfunc_config; 4 | -------------------------------------------------------------------------------- /api/db-migrations/000010_add_pyfunc_config.up.sql: -------------------------------------------------------------------------------- 1 | -- Adds pyfunc_config to table. 2 | ALTER TABLE ensembler_configs ADD pyfunc_config jsonb; 3 | 4 | -------------------------------------------------------------------------------- /api/db-migrations/000011_add_pyfunc_ensemblers_python_version.down.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE pyfunc_ensemblers DROP COLUMN python_version; 2 | -------------------------------------------------------------------------------- /api/db-migrations/000011_add_pyfunc_ensemblers_python_version.up.sql: -------------------------------------------------------------------------------- 1 | -- Introduce Python version column, with default as version 3.7 which is the 2 | -- only supported major version as of the introduction of the column. 3 | ALTER TABLE pyfunc_ensemblers ADD python_version varchar(16) NOT NULL DEFAULT '3.7.*'; 4 | -- Drop default after existing rows are updated 5 | ALTER TABLE pyfunc_ensemblers ALTER COLUMN python_version DROP DEFAULT; 6 | -------------------------------------------------------------------------------- /api/db-migrations/000014_autoscaling_policy.down.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE enrichers DROP COLUMN autoscaling_policy; 2 | ALTER TABLE router_versions DROP COLUMN autoscaling_policy; 3 | UPDATE ensembler_configs set docker_config = docker_config - 'autoscaling_policy' 4 | WHERE (type='docker' OR type='pyfunc') AND docker_config IS NOT NULL; 5 | UPDATE ensembler_configs set pyfunc_config = pyfunc_config - 'autoscaling_policy' WHERE type='pyfunc'; 6 | -------------------------------------------------------------------------------- /api/db-migrations/000015_add_router_protocol.down.sql: -------------------------------------------------------------------------------- 1 | -- Remove Router protocol 2 | ALTER TABLE router_versions DROP COLUMN protocol; 3 | 4 | -- Remove type for Router and Route protocol 5 | DROP TYPE IF EXISTS protocol; -------------------------------------------------------------------------------- /api/db-migrations/000015_add_router_protocol.up.sql: -------------------------------------------------------------------------------- 1 | -- Create type for Router protocol 2 | CREATE TYPE protocol as ENUM ('HTTP_JSON', 'UPI_V1'); 3 | 4 | -- Add Router protocol 5 | ALTER TABLE router_versions ADD protocol protocol NOT NULL DEFAULT 'HTTP_JSON'; -------------------------------------------------------------------------------- /api/db-migrations/000016_add_secrets_columns.down.sql: -------------------------------------------------------------------------------- 1 | -- Remove secrets column for enrichers 2 | ALTER TABLE enrichers DROP COLUMN secrets; 3 | 4 | -- Remove secrets field in docker_config and pyfunc_config columns for ensemblers 5 | UPDATE ensembler_configs set docker_config = docker_config - 'secrets' WHERE docker_config IS NOT NULL; 6 | 7 | UPDATE ensembler_configs set pyfunc_config = pyfunc_config - 'secrets' WHERE pyfunc_config IS NOT NULL; 8 | -------------------------------------------------------------------------------- /api/db-migrations/000016_add_secrets_columns.up.sql: -------------------------------------------------------------------------------- 1 | -- Create secrets column for enrichers 2 | ALTER TABLE enrichers ADD COLUMN secrets jsonb NOT NULL DEFAULT '[]'::jsonb; 3 | 4 | -- Create secrets field in docker_config and pyfunc_config columns for ensemblers 5 | UPDATE ensembler_configs SET docker_config = jsonb_set(docker_config, '{secrets}', '[]'::jsonb) WHERE docker_config IS NOT NULL AND docker_config->'secrets' IS NULL; 6 | 7 | UPDATE ensembler_configs SET pyfunc_config = jsonb_set(pyfunc_config, '{secrets}', '[]'::jsonb) WHERE pyfunc_config IS NOT NULL AND pyfunc_config->'secrets' IS NULL; 8 | -------------------------------------------------------------------------------- /api/e2e/test/config.yaml: -------------------------------------------------------------------------------- 1 | kubeconfig_use_local: true 2 | 3 | api_base_path: http://turing-gateway.127.0.0.1.nip.io/api/turing/v1 4 | 5 | echoserver: 6 | image: eexit/mirror-http-server:1.1.3 7 | 8 | mockserver: 9 | name: mockserver 10 | image: mockserver/mockserver:5.11.2 11 | endpoint: http://mockserver 12 | 13 | mockControlUPIServer: 14 | name: upi-control 15 | image: ghcr.io/caraml-dev/upi-echo-server:0.3.2-1-g1faf05e 16 | endpoint: mockserver-upi-control:80 17 | 18 | mockTreatmentUPIServer: 19 | name: upi-treatment-a 20 | image: ghcr.io/caraml-dev/upi-echo-server:0.3.2-1-g1faf05e 21 | endpoint: mockserver-upi-a:80 22 | 23 | cluster: 24 | name: dev 25 | credentials: {} 26 | 27 | project: 28 | id: 1 29 | name: default 30 | 31 | ensemblers: 32 | base_name: "turing-e2e-" 33 | -------------------------------------------------------------------------------- /api/e2e/test/testdata/responses/router_proprietary_exp_predict.json: -------------------------------------------------------------------------------- 1 | { 2 | "request": { 3 | "client": { 4 | "id": 4 5 | } 6 | }, 7 | "response": { 8 | "experiment": { 9 | "configuration": { 10 | "foo": "foo", 11 | "route_name": "control" 12 | } 13 | }, 14 | "route_responses": [ 15 | { 16 | "data": { 17 | "version": "control" 18 | }, 19 | "is_default": false, 20 | "route": "control" 21 | } 22 | ] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /api/e2e/test/testdata/responses/traffic_rules/no-rules.json: -------------------------------------------------------------------------------- 1 | { 2 | "request": { 3 | "service_type": { 4 | "id": "service-type-c" 5 | } 6 | }, 7 | "response": { 8 | "experiment": {}, 9 | "route_responses": [ 10 | { 11 | "data": { 12 | "version": "control" 13 | }, 14 | "is_default": false, 15 | "route": "control" 16 | } 17 | ] 18 | } 19 | } -------------------------------------------------------------------------------- /api/e2e/test/testdata/responses/traffic_rules/traffic-rule-1.json: -------------------------------------------------------------------------------- 1 | { 2 | "request": {}, 3 | "response": { 4 | "experiment": {}, 5 | "route_responses": [ 6 | { 7 | "data": { 8 | "version": "treatment-a" 9 | }, 10 | "is_default": false, 11 | "route": "treatment-a" 12 | } 13 | ] 14 | } 15 | } -------------------------------------------------------------------------------- /api/e2e/test/testdata/responses/traffic_rules/traffic-rule-2.json: -------------------------------------------------------------------------------- 1 | { 2 | "request": { 3 | "service_type": { 4 | "id": "service-type-b" 5 | } 6 | }, 7 | "response": { 8 | "experiment": {}, 9 | "route_responses": [ 10 | { 11 | "data": { 12 | "version": "treatment-b" 13 | }, 14 | "is_default": false, 15 | "route": "treatment-b" 16 | } 17 | ] 18 | } 19 | } -------------------------------------------------------------------------------- /api/environments-dev.yaml: -------------------------------------------------------------------------------- 1 | - name: k3s 2 | k8s_config: {} # populated after running docker-compose setup in infra/docker-compose/dev 3 | # Example k8s_config to connect to cluster using gke-gcloud-auth-plugin 4 | # name: dev-cluster 5 | # cluster: 6 | # server: https://k8s.cluster 7 | # certificate-authority-data: some_cert_data 8 | # user: 9 | # exec: 10 | # apiVersion: client.authentication.k8s.io/v1beta1 11 | # args: ["--use_application_default_credentials"] 12 | # command: gke-gcloud-auth-plugin 13 | # interactiveMode: IfAvailable 14 | # provideClusterInfo: true 15 | 16 | -------------------------------------------------------------------------------- /api/keto/policies/example_policy.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "admin-policy", 4 | "subjects": [ 5 | "users:test-user@gojek.com" 6 | ], 7 | "resources": [ 8 | "resources:mlp:projects:**" 9 | ], 10 | "actions": [ 11 | "actions:**" 12 | ], 13 | "effect": "allow" 14 | } 15 | ] -------------------------------------------------------------------------------- /api/openapi-codegen.yaml: -------------------------------------------------------------------------------- 1 | enumClassPrefix: true 2 | # Global Properties 3 | globalProperties: 4 | apiTests: false 5 | modelTests: false 6 | apiDocs: false 7 | modelDocs: false 8 | models: 9 | - BigQueryDataset 10 | - BigQueryDatasetConfig 11 | - BigQuerySink 12 | - BigQuerySinkConfig 13 | - Dataset 14 | - EnsemblerConfig 15 | - EnsemblerConfigKind 16 | - EnsemblingJobEnsemblerSpec 17 | - EnsemblingJobEnsemblerSpecResult 18 | - EnsemblingJobMeta 19 | - EnsemblingJobPredictionSource 20 | - EnsemblingJobResultType 21 | - EnsemblingJobSink 22 | - EnsemblingJobSource 23 | - EnsemblingJobSpec 24 | - EnsemblingResources 25 | - SaveMode 26 | - EnsemblerInfraConfig 27 | - EnvVar 28 | - MountedMLPSecret 29 | -------------------------------------------------------------------------------- /api/turing/api/projects_api.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | type ProjectsController struct { 8 | BaseController 9 | } 10 | 11 | func (c ProjectsController) ListProjects( 12 | _ *http.Request, 13 | vars RequestVars, 14 | _ interface{}, 15 | ) *Response { 16 | projectName, _ := vars.get("name") 17 | projects, err := c.MLPService.GetProjects(projectName) 18 | if err != nil { 19 | return InternalServerError("failed to fetch projects", err.Error()) 20 | } 21 | 22 | return Ok(projects) 23 | } 24 | 25 | func (c ProjectsController) Routes() []Route { 26 | return []Route{ 27 | { 28 | method: http.MethodGet, 29 | path: "/projects", 30 | handler: c.ListProjects, 31 | }, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /api/turing/api/request/ensembler_images.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | import "github.com/caraml-dev/turing/api/turing/models" 4 | 5 | type BuildEnsemblerImageRequest struct { 6 | RunnerType models.EnsemblerRunnerType `json:"runner_type" validate:"required"` 7 | } 8 | -------------------------------------------------------------------------------- /api/turing/api/request_utils.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | 7 | "github.com/caraml-dev/turing/api/turing/models" 8 | ) 9 | 10 | // gets an int from the provided request variables. If not found, will throw 11 | // an error. 12 | func getIntFromVars(vars RequestVars, key string) (int, error) { 13 | var v string 14 | var ok bool 15 | if v, ok = vars.get(key); !ok { 16 | return 0, fmt.Errorf("key %s not found in vars", key) 17 | } 18 | return strconv.Atoi(v) 19 | } 20 | 21 | // gets an ID from the provided request variables. If not found, will throw 22 | // an error. 23 | func getIDFromVars(vars RequestVars, key string) (models.ID, error) { 24 | id, err := getIntFromVars(vars, key) 25 | return models.ID(id), err 26 | } 27 | -------------------------------------------------------------------------------- /api/turing/batch/const.go: -------------------------------------------------------------------------------- 1 | package batch 2 | 3 | const ( 4 | // JobConfigFileName is the name of the defined container job config, e.g. ensembler config 5 | JobConfigFileName = "jobConfig.yaml" 6 | // JobConfigMount is where the job spec if mounted 7 | JobConfigMount = "/mnt/job-spec/" 8 | ) 9 | 10 | const ( 11 | // DatasetTypeBQ is the BQ source dataset type 12 | DatasetTypeBQ = "BQ" 13 | // SinkTypeBQ is the BQ sink dataset type 14 | SinkTypeBQ = "BQ" 15 | 16 | // Batch Ensembling Pod component types 17 | 18 | // ImageBuilderPodType is the image builder pod type 19 | ImageBuilderPodType = "image_builder" 20 | // DriverPodType is the spark driver pod type 21 | DriverPodType = "driver" 22 | // ExecutorPodType is the spark executor pod type 23 | ExecutorPodType = "executor" 24 | ) 25 | -------------------------------------------------------------------------------- /api/turing/batch/runner/base.go: -------------------------------------------------------------------------------- 1 | package batchrunner 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // BatchJobRunner is an interface that exposes the batch processes. 8 | type BatchJobRunner interface { 9 | Run() 10 | GetInterval() time.Duration 11 | } 12 | 13 | // RunBatchRunners will run all runners asynchronously. 14 | func RunBatchRunners(runners []BatchJobRunner) { 15 | for _, runner := range runners { 16 | go func(runner BatchJobRunner) { 17 | for { 18 | runner.Run() 19 | time.Sleep(runner.GetInterval()) 20 | } 21 | }(runner) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /api/turing/cluster/role.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | 3 | import ( 4 | apirbacv1 "k8s.io/api/rbac/v1" 5 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 6 | ) 7 | 8 | // Role contains the information to build a role 9 | type Role struct { 10 | Name string `json:"name"` 11 | Namespace string `json:"namespace"` 12 | Labels map[string]string `json:"labels"` 13 | PolicyRules []apirbacv1.PolicyRule `json:"rules"` 14 | } 15 | 16 | func (r *Role) BuildRole() *apirbacv1.Role { 17 | return &apirbacv1.Role{ 18 | ObjectMeta: metav1.ObjectMeta{ 19 | Name: r.Name, 20 | Namespace: r.Namespace, 21 | Labels: r.Labels, 22 | }, 23 | Rules: r.PolicyRules, 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /api/turing/cluster/role_test.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | apirbacv1 "k8s.io/api/rbac/v1" 8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | ) 10 | 11 | func TestBuildRole(t *testing.T) { 12 | testNamespace := "namespace" 13 | roleCfg := Role{ 14 | Name: "role-name", 15 | Namespace: testNamespace, 16 | Labels: labels, 17 | } 18 | expected := apirbacv1.Role{ 19 | ObjectMeta: metav1.ObjectMeta{ 20 | Name: "role-name", 21 | Namespace: testNamespace, 22 | Labels: labels, 23 | }, 24 | } 25 | got := roleCfg.BuildRole() 26 | assert.Equal(t, expected, *got) 27 | } 28 | -------------------------------------------------------------------------------- /api/turing/cluster/secret.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | 3 | import ( 4 | corev1 "k8s.io/api/core/v1" 5 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 6 | ) 7 | 8 | // Secret defines a kubernetes secret. 9 | type Secret struct { 10 | Name string 11 | Namespace string 12 | Data map[string]string 13 | Labels map[string]string 14 | } 15 | 16 | // BuildSecret builds a kubernetes secret from the given config. 17 | func (cfg *Secret) BuildSecret() *corev1.Secret { 18 | data := make(map[string][]byte, len(cfg.Data)) 19 | for k, v := range cfg.Data { 20 | data[k] = []byte(v) 21 | } 22 | return &corev1.Secret{ 23 | ObjectMeta: metav1.ObjectMeta{ 24 | Name: cfg.Name, 25 | Namespace: cfg.Namespace, 26 | Labels: cfg.Labels, 27 | }, 28 | Data: data, 29 | Type: corev1.SecretTypeOpaque, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /api/turing/cluster/service_account.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | 3 | import ( 4 | corev1 "k8s.io/api/core/v1" 5 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 6 | ) 7 | 8 | // ServiceAccount contains the information to build a service account 9 | type ServiceAccount struct { 10 | Name string `json:"name"` 11 | Namespace string `json:"namespace"` 12 | Labels map[string]string `json:"labels"` 13 | } 14 | 15 | func (sa *ServiceAccount) BuildServiceAccount() *corev1.ServiceAccount { 16 | return &corev1.ServiceAccount{ 17 | ObjectMeta: metav1.ObjectMeta{ 18 | Name: sa.Name, 19 | Namespace: sa.Namespace, 20 | Labels: sa.Labels, 21 | }, 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /api/turing/cluster/service_account_test.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | corev1 "k8s.io/api/core/v1" 8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | ) 10 | 11 | func TestBuildServiceAccount(t *testing.T) { 12 | testNamespace := "namespace" 13 | saCfg := ServiceAccount{ 14 | Name: "sa-name", 15 | Namespace: testNamespace, 16 | Labels: labels, 17 | } 18 | expected := corev1.ServiceAccount{ 19 | ObjectMeta: metav1.ObjectMeta{ 20 | Name: "sa-name", 21 | Namespace: testNamespace, 22 | Labels: labels, 23 | }, 24 | } 25 | got := saCfg.BuildServiceAccount() 26 | assert.Equal(t, expected, *got) 27 | } 28 | -------------------------------------------------------------------------------- /api/turing/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/caraml-dev/turing/api/turing/server" 4 | 5 | func main() { 6 | server.Run() 7 | } 8 | -------------------------------------------------------------------------------- /api/turing/config/testdata/env-config-1.yaml: -------------------------------------------------------------------------------- 1 | - name: "id-dev" 2 | k8s_config: 3 | name: dev-cluster 4 | cluster: 5 | server: https://k8s.api.server 6 | insecure-skip-tls-verify: true 7 | user: 8 | exec: 9 | apiVersion: client.authentication.k8s.io/v1beta1 10 | command: gke-gcloud-auth-plugin 11 | interactiveMode: IfAvailable 12 | provideClusterInfo: true 13 | -------------------------------------------------------------------------------- /api/turing/config/testdata/env-config-nil.yaml: -------------------------------------------------------------------------------- 1 | - name: "id-dev" 2 | -------------------------------------------------------------------------------- /api/turing/config/testdata/env-err.yaml: -------------------------------------------------------------------------------- 1 | - name: "id-dev" 2 | k8s_config: 3 | name: dev-cluster 4 | cluster: 5 | server: https://k8s.api.server 6 | insecure-skip-tls-verify: true 7 | user: 8 | exec: 9 | apiVersion: client.authentication.k8s.io/v1beta1 10 | command: gke-gcloud-auth-plugin 11 | interactiveMode: IfAvailable 12 | provideClusterInfo: true 13 | -------------------------------------------------------------------------------- /api/turing/config/testdata/invalid-duration-format.yaml: -------------------------------------------------------------------------------- 1 | DeployConfig: 2 | Timeout: 50rr 3 | -------------------------------------------------------------------------------- /api/turing/config/testdata/invalid-quantity-format.yaml: -------------------------------------------------------------------------------- 1 | DeployConfig: 2 | MaxCPU: 80pp -------------------------------------------------------------------------------- /api/turing/config/testdata/invalid-type.yaml: -------------------------------------------------------------------------------- 1 | Port: 2 | Value: 9999 -------------------------------------------------------------------------------- /api/turing/internal/ref/types.go: -------------------------------------------------------------------------------- 1 | package ref 2 | 3 | import openapi "github.com/caraml-dev/turing/api/turing/generated" 4 | 5 | func EnsemblingJobResultType(i openapi.EnsemblingJobResultType) *openapi.EnsemblingJobResultType { 6 | return &i 7 | } 8 | 9 | func Int(i int) *int { 10 | return &i 11 | } 12 | 13 | func Int32(i int32) *int32 { 14 | return &i 15 | } 16 | 17 | func String(i string) *string { 18 | return &i 19 | } 20 | -------------------------------------------------------------------------------- /api/turing/internal/testutils/validation.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | "github.com/google/go-cmp/cmp" 8 | ) 9 | 10 | // CompareObjects checks equality of 2 objects and returns a formatted error on failure. 11 | // If the object types have unexported fields, a custom marshaler is required to be defined. 12 | func CompareObjects(actual interface{}, expected interface{}) error { 13 | allowUnexportedOn := actual 14 | if reflect.TypeOf(allowUnexportedOn).Kind() == reflect.Ptr { 15 | allowUnexportedOn = reflect.ValueOf(actual).Elem().Interface() 16 | } 17 | if !cmp.Equal(actual, expected, cmp.AllowUnexported(allowUnexportedOn)) { 18 | return fmt.Errorf(cmp.Diff(actual, expected, cmp.AllowUnexported(allowUnexportedOn))) 19 | } 20 | return nil 21 | } 22 | -------------------------------------------------------------------------------- /api/turing/models/default_traffic_rule.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "database/sql/driver" 5 | "encoding/json" 6 | "errors" 7 | ) 8 | 9 | type DefaultTrafficRule struct { 10 | Routes []string `json:"routes" validate:"required,notBlank"` 11 | } 12 | 13 | func (r DefaultTrafficRule) Value() (driver.Value, error) { 14 | return json.Marshal(r) 15 | } 16 | 17 | func (r *DefaultTrafficRule) Scan(value interface{}) error { 18 | b, ok := value.([]byte) 19 | if !ok { 20 | return errors.New("type assertion to []byte failed") 21 | } 22 | 23 | return json.Unmarshal(b, r) 24 | } 25 | -------------------------------------------------------------------------------- /api/turing/models/router_version_test.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "database/sql" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestRouterVersionSetters(t *testing.T) { 11 | rv := RouterVersion{} 12 | rv.SetEnricherID(1) 13 | rv.SetEnsemblerID(2) 14 | // Validate 15 | assert.Equal(t, sql.NullInt32{Int32: int32(1), Valid: true}, rv.EnricherID) 16 | assert.Equal(t, sql.NullInt32{Int32: int32(2), Valid: true}, rv.EnsemblerID) 17 | } 18 | -------------------------------------------------------------------------------- /api/turing/server/healthcheck.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "database/sql" 5 | "time" 6 | 7 | "github.com/gorilla/mux" 8 | "github.com/heptiolabs/healthcheck" 9 | ) 10 | 11 | func AddHealthCheckHandler(r *mux.Router, path string, db *sql.DB) { 12 | sub := r.PathPrefix(path).Subrouter() 13 | 14 | health := healthcheck.NewHandler() 15 | health.AddReadinessCheck( 16 | "database", 17 | healthcheck.DatabasePingCheck(db, 1*time.Second)) 18 | 19 | sub.Path("/live").HandlerFunc(health.LiveEndpoint) 20 | sub.Path("/ready").HandlerFunc(health.ReadyEndpoint) 21 | } 22 | -------------------------------------------------------------------------------- /api/turing/service/crypto_service_test.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestCryptoService(t *testing.T) { 10 | testphrase := "test" 11 | cs := NewCryptoService("key") 12 | cipher, err := cs.Encrypt(testphrase) 13 | assert.NoError(t, err) 14 | assert.NotEqual(t, testphrase, cipher) 15 | plaintext, err := cs.Decrypt(cipher) 16 | assert.NoError(t, err) 17 | assert.Equal(t, testphrase, plaintext) 18 | } 19 | -------------------------------------------------------------------------------- /api/turing/testdata/cluster/servicebuilder/router_configmap_default.yml: -------------------------------------------------------------------------------- 1 | id: test-svc 2 | routes: 3 | - endpoint: http://www.mocky.io/v2/5e4caccc310000e2cad8c071 4 | id: control 5 | protocol: HTTP 6 | timeout: 2s 7 | type: PROXY 8 | strategy: 9 | properties: 10 | default_route_id: control 11 | experiment_engine: exp-engine-1 12 | experiment_engine_properties: 13 | client_id: client_id 14 | endpoint: exp-engine:8080 15 | experiments: 16 | - experiment_name: exp_exp_test_experiment_1 17 | segmentation_field: customer_id 18 | segmentation_field_source: payload 19 | segmentation_unit: customer 20 | timeout: 500ms 21 | user_data: 22 | app_version: 23 | field: appVer 24 | field_source: header 25 | type: fiber.DefaultTuringRoutingStrategy 26 | type: EAGER_ROUTER 27 | -------------------------------------------------------------------------------- /api/turing/testdata/cluster/servicebuilder/router_configmap_ensembling.yml: -------------------------------------------------------------------------------- 1 | fan_in: 2 | properties: 3 | default_route_id: control 4 | experiment_engine: nop 5 | type: fiber.EnsemblingFanIn 6 | id: test-svc 7 | routes: 8 | - endpoint: http://www.mocky.io/v2/5e4caccc310000e2cad8c071 9 | id: control 10 | protocol: HTTP 11 | timeout: 2s 12 | type: PROXY 13 | type: COMBINER 14 | -------------------------------------------------------------------------------- /api/turing/testdata/cluster/servicebuilder/router_configmap_exp_engine.yml: -------------------------------------------------------------------------------- 1 | id: router-with-exp-engine 2 | routes: 3 | - endpoint: http://www.mocky.io/v2/5e4caccc310000e2cad8c071 4 | id: control 5 | protocol: HTTP 6 | timeout: 2s 7 | type: PROXY 8 | - endpoint: http://localhost:8080/predict/treatment-a 9 | id: treatment-a 10 | protocol: HTTP 11 | timeout: 2s 12 | type: PROXY 13 | strategy: 14 | properties: 15 | default_route_id: control 16 | experiment_engine: exp-engine 17 | experiment_engine_liveness_period_seconds: 5 18 | experiment_engine_properties: 19 | key-1: value-1 20 | plugin_binary: /app/plugins/exp-engine 21 | type: fiber.DefaultTuringRoutingStrategy 22 | type: EAGER_ROUTER 23 | -------------------------------------------------------------------------------- /api/turing/testdata/cluster/servicebuilder/router_configmap_no_default_route.yml: -------------------------------------------------------------------------------- 1 | id: test-svc 2 | routes: 3 | - endpoint: http://www.mocky.io/v2/5e4caccc310000e2cad8c071 4 | id: control 5 | protocol: HTTP 6 | timeout: 2s 7 | type: PROXY 8 | strategy: 9 | properties: 10 | experiment_engine: nop 11 | type: fiber.DefaultTuringRoutingStrategy 12 | type: EAGER_ROUTER 13 | -------------------------------------------------------------------------------- /api/turing/utils/json.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "encoding/json" 4 | 5 | // MergeJSON adds (or overrides if such keys exist) key/value data from `overrides` map 6 | // into the `message` json.RawMessage and returns merged json data 7 | func MergeJSON(message json.RawMessage, overrides map[string]interface{}) (json.RawMessage, error) { 8 | var result map[string]interface{} 9 | if len(message) > 0 { 10 | err := json.Unmarshal(message, &result) 11 | 12 | if err != nil { 13 | return nil, err 14 | } 15 | 16 | result, err = MergeMaps(result, overrides) 17 | if err != nil { 18 | return nil, err 19 | } 20 | } else { 21 | result = overrides 22 | } 23 | 24 | return json.Marshal(result) 25 | } 26 | -------------------------------------------------------------------------------- /docs/.gitbook/assets/activity_log.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/activity_log.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/alert_details_panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/alert_details_panel.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/alerts_config_view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/alerts_config_view.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/alerts_team_panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/alerts_team_panel.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/alerts_validation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/alerts_validation.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/autoscaling_policy_panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/autoscaling_policy_panel.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/bq_panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/bq_panel.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/configure_alerts_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/configure_alerts_button.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/configure_expriement_engine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/configure_expriement_engine.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/create_router_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/create_router_button.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/create_router_resources.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/create_router_resources.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/create_router_rules.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/create_router_rules.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/create_router_rules_priority.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/create_router_rules_priority.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/delete_ensembler_modal_active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/delete_ensembler_modal_active.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/delete_ensembler_modal_inactive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/delete_ensembler_modal_inactive.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/delete_ensembler_modal_inactive_filled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/delete_ensembler_modal_inactive_filled.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/delete_ensembler_modal_success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/delete_ensembler_modal_success.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/delete_ensembler_modal_success_filled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/delete_ensembler_modal_success_filled.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/delete_router_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/delete_router_button.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/delete_router_modal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/delete_router_modal.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/delete_version_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/delete_version_button.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/delete_version_modal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/delete_version_modal.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/deploy_version_action.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/deploy_version_action.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/deployed_router_badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/deployed_router_badge.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/deployed_version_badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/deployed_version_badge.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/docker_container_config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/docker_container_config.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/edit_router_header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/edit_router_header.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/edit_router_next_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/edit_router_next_button.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/edit_router_version_comparison.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/edit_router_version_comparison.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/ensembler_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/ensembler_page.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/ensembler_page_empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/ensembler_page_empty.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/env_var_panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/env_var_panel.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/failed_router_badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/failed_router_badge.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/failed_version_badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/failed_version_badge.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/general_router_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/general_router_settings.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/kafka_panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/kafka_panel.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/log_config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/log_config.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/monitoring_dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/monitoring_dashboard.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/monitoring_tab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/monitoring_tab.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/nop_ensembler_config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/nop_ensembler_config.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/not_deployed_router_badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/not_deployed_router_badge.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/not_deployed_version_badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/not_deployed_version_badge.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/projects_dropdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/projects_dropdown.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/pyfunc_ensembler_config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/pyfunc_ensembler_config.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/redeploy_router.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/redeploy_router.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/redeploy_router_modal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/redeploy_router_modal.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/redeploy_version_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/redeploy_version_button.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/redeploy_version_config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/redeploy_version_config.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/redeploy_version_modal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/redeploy_version_modal.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/resources_panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/resources_panel.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/router_config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/router_config.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/router_details_header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/router_details_header.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/router_row.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/router_row.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/routes_panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/routes_panel.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/select_undeploy_router.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/select_undeploy_router.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/service_acc_dropdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/service_acc_dropdown.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/standard_ensembler_config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/standard_ensembler_config.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/standard_ensembler_lazy_routing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/standard_ensembler_lazy_routing.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/success_router_delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/success_router_delete.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/success_version_delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/success_version_delete.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/toggle_alert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/toggle_alert.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/traffic_split_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/traffic_split_icon.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/undeploy_router_modal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/undeploy_router_modal.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/update_alerts_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/update_alerts_button.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/update_version_history.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/update_version_history.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/updating_router.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/updating_router.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/updating_router_badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/updating_router_badge.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/updating_version_badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/updating_version_badge.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/version_comparison.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/version_comparison.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/version_delete_action.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/version_delete_action.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/versions_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/versions_list.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/view_router_groupby_routes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/view_router_groupby_routes.png -------------------------------------------------------------------------------- /docs/.gitbook/assets/view_router_groupby_rules.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/.gitbook/assets/view_router_groupby_rules.png -------------------------------------------------------------------------------- /docs/assets/turing_architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/assets/turing_architecture.png -------------------------------------------------------------------------------- /docs/assets/turing_router_components.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/assets/turing_router_components.png -------------------------------------------------------------------------------- /docs/assets/turing_ui_router_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/docs/assets/turing_ui_router_list.png -------------------------------------------------------------------------------- /docs/dev-docs/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | Please refer to the [repository README](https://github.com/caraml-dev/turing) for an introduction to the Turing architecture, steps for running Turing in the local environment and production deployment into Kubernetes clusters. 4 | 5 | The docs in this section provide more details on some topics. 6 | -------------------------------------------------------------------------------- /docs/dev-docs/monitoring-and-alerting/README.md: -------------------------------------------------------------------------------- 1 | # Monitoring and alerting 2 | 3 | Turing exposes a convenient user interface and provides a framework to enable fine-grained monitoring and alerting on the deployed resources (routers, ensemblers, etc.). The sub-sections provide details on how these features can be made available to the Turing application. 4 | 5 | {% page-ref page="configure-monitoring-backend.md" %} 6 | {% page-ref page="configure-alerting-backend.md" %} 7 | -------------------------------------------------------------------------------- /docs/how-to/delete-a-router/README.md: -------------------------------------------------------------------------------- 1 | # Deleting a Router 2 | 3 | A router version that is not deployed or in the process of deploying can be deleted. Deleting a router or deleting a router version will result in the purging of the router or router version metadata from the Turing database. This action is **irreversible**. 4 | 5 | {% page-ref page="delete-a-router-version.md" %} 6 | {% page-ref page="delete-a-router-version-from-details-page.md" %} 7 | {% page-ref page="delete-a-router.md" %} 8 | -------------------------------------------------------------------------------- /docs/how-to/delete-a-router/delete-router-version-from-details-page.md: -------------------------------------------------------------------------------- 1 | # Deleting a Router Version from Version Details Page 2 | 3 | Click on the row of your version. You should be able to see the existing version configuration. 4 | 5 | Within the More Actions panel, click on Delete This Version. 6 | 7 | ![](../../.gitbook/assets/version_delete_action.png) 8 | 9 | Confirm if you want to delete the specified version of your router. 10 | 11 | ![](../../.gitbook/assets/delete_version_modal.png) 12 | 13 | Once the specified version has been successfully deleted, you will be taken to the History Tab of the Router Details page and will no longer be able to see the deleted version. 14 | 15 | ![](../../.gitbook/assets/success_version_delete.png) 16 | -------------------------------------------------------------------------------- /docs/how-to/delete-an-ensembler/README.md: -------------------------------------------------------------------------------- 1 | # Deleting Ensembler 2 | 3 | An ensembler can only be deleted if it is not in active use by any routers or ensembling jobs. Deleting such an ensembler will result in the purging of all related inactive router versions and ensembling jobs, as well as the ensembler metadata from the Turing database. This action is **irreversible**. 4 | 5 | {% page-ref page="delete-an-ensembler.md" %} 6 | {% page-ref page="delete-an-ensembler-active.md" %} 7 | {% page-ref page="delete-an-ensembler-inactive.md" %} 8 | -------------------------------------------------------------------------------- /docs/how-to/delete-an-ensembler/delete-an-ensembler.md: -------------------------------------------------------------------------------- 1 | # Deleting an Ensembler without any related entity 2 | 3 | This page describes the process of deleting an ensembler **without any related entities** (ensembling jobs or router versions). 4 | 5 | Navigate to the Ensemblers page. Click on the 'Delete' button: 6 | 7 | ![](../../.gitbook/assets/ensembler_page.png) 8 | 9 | Type the ensembler's name in the text bar to confirm your decision: 10 | 11 | ![](../../.gitbook/assets/delete_ensembler_modal_success.png) 12 | ![](../../.gitbook/assets/delete_ensembler_modal_success_filled.png) 13 | 14 | Once the specified ensembler has been successfully deleted, you will no longer be able to see the ensembler on the Ensemblers page: 15 | 16 | ![](../../.gitbook/assets/ensembler_page_empty.png) 17 | -------------------------------------------------------------------------------- /docs/how-to/monitor-a-router/README.md: -------------------------------------------------------------------------------- 1 | # Monitoring a Router 2 | 3 | Turing provides a convenient user interface for monitoring the router deployments and getting the standard metrics out-of-the-box, and configuring alerts based on the performance of the routers. To take advantage of these features, an appropriate monitoring / alerting backend should have been configured. 4 | 5 | {% page-ref page="monitor-router-performance.md" %} 6 | {% page-ref page="configure-alerts.md" %} 7 | -------------------------------------------------------------------------------- /docs/how-to/monitor-a-router/monitor-router-performance.md: -------------------------------------------------------------------------------- 1 | # Monitoring Router Performance 2 | 3 | {% hint style="warning" %} 4 | This document assumes that an appropriate monitoring solution has been integrated to the deployment of Turing. 5 | The actual functionality may be different, according to the deployment configurations. 6 | {% endhint %} 7 | 8 | 1. Navigate to the Router Details View of your router. 9 | 10 | 2. Click on the monitoring link of your router under Router Actions. 11 | ![monitoring_tab](../../.gitbook/assets/monitoring_tab.png) 12 | 13 | 3. This link will bring you to the dashboard with standard router metrics such as 14 | throughput, latency and error rate of your Router. 15 | ![monitoring_dashboard](../../.gitbook/assets/monitoring_dashboard.png) 16 | -------------------------------------------------------------------------------- /docs/how-to/redeploy-a-router/README.md: -------------------------------------------------------------------------------- 1 | # Redeploying a Router 2 | 3 | You can redeploy an undeployed router or redeploy a version of your router which is not currently deployed. Resources in the cluster that were removed from the router or router version will be reallocated to the router or router version. There are three different ways to deploy a router. 4 | 5 | {% page-ref page="redeploy-undeployed-router.md" %} 6 | {% page-ref page="redeploy-version-from-history.md" %} 7 | {% page-ref page="redeploy-from-version-detail.md" %} 8 | -------------------------------------------------------------------------------- /docs/how-to/redeploy-a-router/redeploy-from-version-detail.md: -------------------------------------------------------------------------------- 1 | # Redeploy a router version from version details page 2 | 3 | Clicking on the row of your version in the history tab will bring you to the version details page. You should be able to see the existing version configuration here. 4 | 5 | Within the More Actions panel, click Deploy This Version. 6 | 7 | ![](../../.gitbook/assets/deploy_version_action.png) 8 | 9 | Confirm if you want to deploy the specified version of your router. 10 | 11 | ![](../../.gitbook/assets/redeploy_version_modal.png) 12 | 13 | Once the specified version is in the process of redeployment, the status of the version will be changed to Updating and it will be in the process of deploying your router. 14 | 15 | ![](../../.gitbook/assets/redeploy_version_config.png) 16 | -------------------------------------------------------------------------------- /docs/how-to/redeploy-a-router/redeploy-undeployed-router.md: -------------------------------------------------------------------------------- 1 | # Redeploy an undeployed router 2 | 3 | Navigate to the Router Details View of your router. 4 | 5 | Click on the More Actions button and select Redeploy router. 6 | 7 | Only an **undeployed** router (indicated by the router status `Not deployed`) can be redeployed and Turing will automatically redeploy the most recently deployed version for you. 8 | 9 | ![](../../.gitbook/assets/redeploy_router.png) 10 | 11 | Confirm if you want to redeploy your router. 12 | 13 | ![](../../.gitbook/assets/redeploy_router_modal.png) 14 | 15 | Once the router is in the process of redeployment, you will see that the status of your router has changed from `Not deployed` to `Updating`. 16 | 17 | ![](../../.gitbook/assets/updating_router.png) 18 | -------------------------------------------------------------------------------- /docs/how-to/undeploying-router.md: -------------------------------------------------------------------------------- 1 | # Undeploying Router 2 | 3 | Undeploying a router stops a deployed router and removes any resources deployed in the cluster for that router. This action is reversible and the router can be redeployed. 4 | 5 | Navigate to the Router Details View of your router. 6 | 7 | Click on the More Actions button and select Undeploy router. Only a **deployed** router, with the status badge `Deployed` can be undeployed. 8 | 9 | ![](../.gitbook/assets/select_undeploy_router.png) 10 | 11 | Confirm if you want to undeploy your router. 12 | 13 | ![](../.gitbook/assets/undeploy_router_modal.png) 14 | 15 | Once the router has been successfully undeployed, you will see the status of your router has changed to `Not Deployed` and will no longer be monitored. 16 | -------------------------------------------------------------------------------- /docs/how-to/viewing-routers/logs.md: -------------------------------------------------------------------------------- 1 | ## Logs 2 | 3 | The logs tab displays the container logs of the router, enricher or ensembler (if an enricher or ensembler) is configured. Click on the `Router`, `Enricher` or `Ensembler` to toggle between the containers. 4 | 5 | ![](../../.gitbook/assets/log_config.png) 6 | -------------------------------------------------------------------------------- /docs/how-to/viewing-routers/more-actions.md: -------------------------------------------------------------------------------- 1 | # More Actions 2 | 3 | Opening the More Actions panel displays the actions that can be done to the specified router. These actions are: 4 | 5 | * **Edit Router**: An existing router configuration can be edited and deployed as a new version within the router. 6 | 7 | * **Undeploy Router**: A deployed router can be undeployed. 8 | 9 | * **Redeploy Router**: An undeployed router can be redeployed. 10 | 11 | * **Delete Router**: An undeployed router can be deleted. 12 | -------------------------------------------------------------------------------- /engines/experiment/.gitignore: -------------------------------------------------------------------------------- 1 | # Test coverage 2 | cover.out 3 | 4 | # Go vendor 5 | vendor/ -------------------------------------------------------------------------------- /engines/experiment/.golangci.yml: -------------------------------------------------------------------------------- 1 | ../../.golangci.yml -------------------------------------------------------------------------------- /engines/experiment/Makefile: -------------------------------------------------------------------------------- 1 | SRC_ROOT=. 2 | 3 | .PHONY: default 4 | default: test 5 | 6 | .PHONY: setup 7 | setup: 8 | @echo "Setting up tools..." 9 | @test -x $(shell go env GOPATH)/bin/golangci-lint || \ 10 | curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/v1.48.0/install.sh | sh -s -- -b $(shell go env GOPATH)/bin v1.48.0 11 | 12 | .PHONY: tidy 13 | tidy: 14 | @echo "Fetching dependencies..." 15 | go mod tidy 16 | 17 | .PHONY: fmt 18 | fmt: 19 | @echo "Formatting code..." 20 | gofmt -s -w ${SRC_ROOT} 21 | 22 | .PHONY: lint 23 | lint: setup 24 | @echo "Linting code..." 25 | golangci-lint -v run $(if $(filter true,$(fix)),--fix,) 26 | 27 | .PHONY: test 28 | test: tidy 29 | @echo "Running tests..." 30 | go test -v -race -short -cover -coverprofile cover.out ${SRC_ROOT}/... -tags integration 31 | go tool cover -func cover.out -------------------------------------------------------------------------------- /engines/experiment/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "encoding/json" 4 | 5 | // EngineConfig is a struct used to decode engine's configuration into 6 | // It consists of an optional PluginBinary (if the experiment engine is implemented 7 | // as net/rpc plugin) and unstructured EngineConfiguration of key/value data, that is 8 | // used to configure experiment manager/runner 9 | type EngineConfig struct { 10 | PluginBinary string `mapstructure:"plugin_binary"` 11 | EngineConfiguration map[string]interface{} `mapstructure:",remain"` 12 | } 13 | 14 | func (c EngineConfig) IsPlugin() bool { 15 | return c.PluginBinary != "" 16 | } 17 | 18 | func (c EngineConfig) RawEngineConfig() (json.RawMessage, error) { 19 | return json.Marshal(c.EngineConfiguration) 20 | } 21 | -------------------------------------------------------------------------------- /engines/experiment/docs/assets/example_experiment_manager_router_ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/engines/experiment/docs/assets/example_experiment_manager_router_ui.png -------------------------------------------------------------------------------- /engines/experiment/docs/assets/exp_manager_plugin_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/engines/experiment/docs/assets/exp_manager_plugin_diagram.png -------------------------------------------------------------------------------- /engines/experiment/docs/assets/exp_runner_plugin_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/engines/experiment/docs/assets/exp_runner_plugin_diagram.png -------------------------------------------------------------------------------- /engines/experiment/docs/assets/experiments_data_model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/engines/experiment/docs/assets/experiments_data_model.png -------------------------------------------------------------------------------- /engines/experiment/docs/assets/standard_experiment_ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/engines/experiment/docs/assets/standard_experiment_ui.png -------------------------------------------------------------------------------- /engines/experiment/examples/plugins/hardcoded/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/hashicorp/go-hclog" 5 | 6 | "github.com/caraml-dev/turing/engines/experiment/examples/plugins/hardcoded" 7 | "github.com/caraml-dev/turing/engines/experiment/log" 8 | "github.com/caraml-dev/turing/engines/experiment/plugin/rpc" 9 | ) 10 | 11 | func main() { 12 | logger := hclog.New(&hclog.LoggerOptions{ 13 | Level: hclog.Info, 14 | Name: "example-plugin", 15 | JSONFormat: true, 16 | }) 17 | log.SetGlobalLogger(logger) 18 | 19 | rpc.Serve(&rpc.ClientServices{ 20 | Manager: &hardcoded.ExperimentManager{}, 21 | Runner: &hardcoded.ExperimentRunner{}, 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /engines/experiment/examples/plugins/hardcoded/configs/plugin_config_example.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | engine: 3 | name: plugin-example 4 | display_name: Plugin Example 5 | type: standard 6 | standard_experiment_manager_config: 7 | client_selection_enabled: false 8 | experiment_selection_enabled: true 9 | experiments: 10 | - id: '001' 11 | name: exp_1 12 | variants: 13 | - name: control 14 | - name: treatment-1 15 | variants_configuration: 16 | control: 17 | traffic: 0.85 18 | treatment_configuration: 19 | foo: bar 20 | treatment-1: 21 | traffic: 0.15 22 | treatment_configuration: 23 | bar: baz 24 | variables: 25 | '001': 26 | - name: client 27 | required: true 28 | type: unit 29 | -------------------------------------------------------------------------------- /engines/experiment/examples/plugins/hardcoded/configs/router_config_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "experiments": [ 3 | { 4 | "id": "001", 5 | "name": "exp_1" 6 | } 7 | ], 8 | "variables": { 9 | "experiment_variables": { 10 | "001": [ 11 | { 12 | "name": "client_id", 13 | "type": "unit", 14 | "required": true 15 | } 16 | ] 17 | }, 18 | "config": [ 19 | { 20 | "name": "client_id", 21 | "required": true, 22 | "field": "client.id", 23 | "field_source": "payload" 24 | } 25 | ] 26 | } 27 | } -------------------------------------------------------------------------------- /engines/experiment/examples/plugins/hardcoded/utils/hash.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "hash/fnv" 4 | 5 | func Hash(s string) uint32 { 6 | h := fnv.New32a() 7 | h.Write([]byte(s)) 8 | return h.Sum32() 9 | } 10 | -------------------------------------------------------------------------------- /engines/experiment/examples/plugins/nop/cmd/cmd.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/caraml-dev/turing/engines/experiment/examples/plugins/nop" 5 | "github.com/caraml-dev/turing/engines/experiment/plugin/rpc" 6 | ) 7 | 8 | func main() { 9 | rpc.Serve(&rpc.ClientServices{ 10 | Manager: &nop.ExperimentManager{}, 11 | Runner: &nop.ExperimentRunner{}, 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /engines/experiment/examples/plugins/nop/runner.go: -------------------------------------------------------------------------------- 1 | package nop 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | 7 | "github.com/caraml-dev/mlp/api/pkg/instrumentation/metrics" 8 | 9 | "github.com/caraml-dev/turing/engines/experiment/runner" 10 | ) 11 | 12 | type ExperimentRunner struct{} 13 | 14 | func (r *ExperimentRunner) Configure(json.RawMessage) error { 15 | return nil 16 | } 17 | 18 | func (r *ExperimentRunner) GetTreatmentForRequest( 19 | http.Header, 20 | []byte, 21 | runner.GetTreatmentOptions, 22 | ) (*runner.Treatment, error) { 23 | return &runner.Treatment{ 24 | Name: "my treatment", 25 | Config: json.RawMessage(`{"config-1": "value-a"}`), 26 | }, nil 27 | } 28 | 29 | func (r *ExperimentRunner) RegisterMetricsCollector(_ metrics.Collector, _ runner.MetricsRegistrationHelper) error { 30 | return nil 31 | } 32 | -------------------------------------------------------------------------------- /engines/experiment/log/hclog/hclog.go: -------------------------------------------------------------------------------- 1 | package hclog 2 | 3 | import ( 4 | "github.com/caraml-dev/turing/engines/experiment/log" 5 | ) 6 | 7 | // When this package is imported, the global logger is replaced with hcLogger, 8 | // which is recommended to be used in RPC plugins as it provides a structured 9 | // logging output in the host application, that calls the plugin implementation 10 | func init() { 11 | log.SetGlobalLogger(log.DefaultHCLogger()) 12 | } 13 | -------------------------------------------------------------------------------- /engines/experiment/manager/testdata/experiment_runner_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "client": { 3 | "username": "client_name" 4 | }, 5 | "experiments": [ 6 | { 7 | "name": "exp_name" 8 | } 9 | ], 10 | "variables": { 11 | "client_variables": [ 12 | { 13 | "name": "var_name" 14 | } 15 | ] 16 | } 17 | } -------------------------------------------------------------------------------- /engines/experiment/plugin.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.15 2 | 3 | ARG turing_user="turing" 4 | ARG turing_user_group="app" 5 | 6 | RUN addgroup -S ${turing_user_group} \ 7 | && adduser -S ${turing_user} -G ${turing_user_group} -H 8 | 9 | ARG PLUGIN_BINARY 10 | 11 | ENV PLUGIN_NAME "" 12 | ENV PLUGINS_DIR "/app/plugins" 13 | 14 | ADD --chown=${turing_user}:${turing_user_group} ${PLUGIN_BINARY} /go/bin/plugin 15 | 16 | CMD ["sh", "-c", "cp /go/bin/plugin ${PLUGINS_DIR}/${PLUGIN_NAME:?variable must be SET}"] 17 | -------------------------------------------------------------------------------- /engines/experiment/plugin/inproc/runner/nop/experiment_runner.go: -------------------------------------------------------------------------------- 1 | package nop 2 | 3 | import ( 4 | "log" 5 | 6 | plugin "github.com/caraml-dev/turing/engines/experiment/plugin/inproc/runner" 7 | "github.com/caraml-dev/turing/engines/experiment/runner/nop" 8 | ) 9 | 10 | // init ensures this runner is registered when the package is imported. 11 | func init() { 12 | err := plugin.Register("nop", nop.NewExperimentRunner) 13 | if err != nil { 14 | log.Fatal(err) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /engines/experiment/plugin/rpc/config.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "github.com/hashicorp/go-plugin" 5 | 6 | "github.com/caraml-dev/turing/engines/experiment/plugin/rpc/manager" 7 | "github.com/caraml-dev/turing/engines/experiment/plugin/rpc/runner" 8 | ) 9 | 10 | const ( 11 | ManagerPluginIdentifier = "experiment_manager" 12 | RunnerPluginIdentifier = "experiment_runner" 13 | ) 14 | 15 | var ( 16 | handshakeConfig = plugin.HandshakeConfig{ 17 | ProtocolVersion: 1, 18 | MagicCookieKey: "EXPERIMENTS_PLUGIN", 19 | MagicCookieValue: "turing", 20 | } 21 | 22 | pluginMap = map[string]plugin.Plugin{ 23 | ManagerPluginIdentifier: &manager.ExperimentManagerPlugin{}, 24 | RunnerPluginIdentifier: &runner.ExperimentRunnerPlugin{}, 25 | } 26 | ) 27 | -------------------------------------------------------------------------------- /engines/experiment/plugin/rpc/manager/plugin.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | import ( 4 | "net/rpc" 5 | 6 | "github.com/hashicorp/go-plugin" 7 | ) 8 | 9 | // ExperimentManagerPlugin implements hashicorp/go-plugin's Plugin interface 10 | // for manager.ExperimentManager 11 | type ExperimentManagerPlugin struct { 12 | Impl ConfigurableExperimentManager 13 | } 14 | 15 | func (p *ExperimentManagerPlugin) Server(*plugin.MuxBroker) (interface{}, error) { 16 | return &rpcServer{Impl: p.Impl}, nil 17 | } 18 | 19 | func (ExperimentManagerPlugin) Client(_ *plugin.MuxBroker, c *rpc.Client) (interface{}, error) { 20 | return &rpcClient{RPCClient: c}, nil 21 | } 22 | -------------------------------------------------------------------------------- /engines/experiment/plugin/rpc/mocks/rpc_client.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import "github.com/stretchr/testify/mock" 4 | 5 | type RPCClient struct { 6 | mock.Mock 7 | } 8 | 9 | func (_m *RPCClient) Call(serviceMethod string, args interface{}, reply interface{}) error { 10 | ret := _m.Called(serviceMethod, args, reply) 11 | 12 | var r0 error 13 | if rf, ok := ret.Get(0).(func() error); ok { 14 | r0 = rf() 15 | } else { 16 | r0 = ret.Error(0) 17 | } 18 | 19 | return r0 20 | } 21 | -------------------------------------------------------------------------------- /engines/experiment/plugin/rpc/shared/interface.go: -------------------------------------------------------------------------------- 1 | package shared 2 | 3 | import "encoding/json" 4 | 5 | // RPCClient is a minimal interface to describe *rpc.Client implementation 6 | // Used to simplify mocking of the *rpc.Client in unit tests 7 | type RPCClient interface { 8 | Call(serviceMethod string, args interface{}, reply interface{}) error 9 | } 10 | 11 | // Configurable interface can be implemented by plugins that require 12 | // to be configured with a JSON config data 13 | type Configurable interface { 14 | Configure(cfg json.RawMessage) error 15 | } 16 | -------------------------------------------------------------------------------- /engines/experiment/runner/interceptor.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import "context" 4 | 5 | // ContextKey is a type that represents a key in a Golang context 6 | type ContextKey string 7 | 8 | const ( 9 | // ExperimentEngineKey represents the key for the experiment engine name, stored in the context 10 | ExperimentEngineKey ContextKey = "experimentEngineKey" 11 | ) 12 | 13 | // Interceptor interface is used to define concrete interceptors whose methods will 14 | // be run before and after a single fetch treatment call 15 | type Interceptor interface { 16 | BeforeDispatch(ctx context.Context) context.Context 17 | AfterCompletion(ctx context.Context, err error) 18 | } 19 | -------------------------------------------------------------------------------- /engines/experiment/runner/nop/experiment_runner_test.go: -------------------------------------------------------------------------------- 1 | package nop 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | "github.com/caraml-dev/turing/engines/experiment/runner" 9 | ) 10 | 11 | func TestGetTreatmentForRequest(t *testing.T) { 12 | expRunner := ExperimentRunner{} 13 | treatment, err := expRunner.GetTreatmentForRequest(nil, nil, runner.GetTreatmentOptions{}) 14 | assert.NoError(t, err) 15 | assert.Equal(t, nopTreatment, treatment) 16 | } 17 | -------------------------------------------------------------------------------- /engines/pyfunc-ensembler-job/.dockerignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | .dockerignore 3 | 4 | env/ 5 | tests/ 6 | 7 | .mypy_cache/ 8 | .pytest_cache/ 9 | -------------------------------------------------------------------------------- /engines/pyfunc-ensembler-job/.gitignore: -------------------------------------------------------------------------------- 1 | env/ 2 | .coverage 3 | **/mlruns/ 4 | **/__pycache__ 5 | 6 | vendor/ 7 | temp-deps/ 8 | -------------------------------------------------------------------------------- /engines/pyfunc-ensembler-job/ensembler/sql/bq_select.sql.jinja2: -------------------------------------------------------------------------------- 1 | SELECT 2 | {%- if columns | length > 0 -%} 3 | {%- for col in columns %} 4 | {{ col }}{% if not loop.last %},{% endif -%} 5 | {% endfor -%} 6 | {% else %} 7 | * 8 | {%- endif %} 9 | FROM `{{ table }}` -------------------------------------------------------------------------------- /engines/pyfunc-ensembler-job/env.yaml: -------------------------------------------------------------------------------- 1 | name: pyfunc-ensembler-job 2 | dependencies: 3 | - python=3.10.* 4 | - pip=22.2.2 5 | - pip: 6 | - -r requirements.txt 7 | - --extra-index-url=https://test.pypi.org/simple 8 | - --trusted-host=test.pypi.org 9 | -------------------------------------------------------------------------------- /engines/pyfunc-ensembler-job/requirements.dev.txt: -------------------------------------------------------------------------------- 1 | black==22.6.0 2 | chispa 3 | mypy>=0.910 4 | # The next release 8.2.0 of pytest breaks the unit tests 5 | pytest<=8.1.2 6 | pytest-cov 7 | pylint 8 | -------------------------------------------------------------------------------- /engines/pyfunc-ensembler-job/requirements.txt: -------------------------------------------------------------------------------- 1 | cloudpickle==2.0.0 2 | google-cloud-storage>=1.37.0 3 | jinjasql==0.1.8 4 | jinja2==3.0.3 5 | numpy==1.22.0 6 | pandas==1.3.5 7 | py4j==0.10.9 8 | pyarrow>=0.14.1,<=9.0.0 9 | pyspark==3.0.1 10 | pyyaml==6.0 11 | setuptools<75 12 | turing-sdk==0.0.0 13 | -------------------------------------------------------------------------------- /engines/pyfunc-ensembler-job/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/engines/pyfunc-ensembler-job/tests/__init__.py -------------------------------------------------------------------------------- /engines/pyfunc-ensembler-job/tests/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/engines/pyfunc-ensembler-job/tests/utils/__init__.py -------------------------------------------------------------------------------- /engines/pyfunc-ensembler-job/tests/utils/openapi_utils.py: -------------------------------------------------------------------------------- 1 | import yaml 2 | from turing.generated import Configuration 3 | from turing.generated.model_utils import validate_and_convert_types 4 | 5 | 6 | def from_yaml(text: str, required_type): 7 | json_dict = yaml.safe_load(text) 8 | return validate_and_convert_types( 9 | input_value=json_dict, 10 | required_types_mixed=(required_type,), 11 | path_to_item=[], 12 | spec_property_naming=True, 13 | _check_type=True, 14 | configuration=Configuration(), 15 | ) 16 | -------------------------------------------------------------------------------- /engines/pyfunc-ensembler-job/version.py: -------------------------------------------------------------------------------- 1 | VERSION = "0.0.0" 2 | -------------------------------------------------------------------------------- /engines/pyfunc-ensembler-service/.dockerignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | .dockerignore 3 | 4 | env/ 5 | tests/ 6 | 7 | .mypy_cache/ 8 | .pytest_cache/ 9 | -------------------------------------------------------------------------------- /engines/pyfunc-ensembler-service/.gitignore: -------------------------------------------------------------------------------- 1 | env/ 2 | .coverage 3 | **/mlruns/ 4 | **/__pycache__ 5 | 6 | ensembler/* 7 | -------------------------------------------------------------------------------- /engines/pyfunc-ensembler-service/env.yaml: -------------------------------------------------------------------------------- 1 | name: pyfunc-ensembler-service 2 | dependencies: 3 | - python=3.10.* 4 | - pip=22.2.2 5 | - pip: 6 | - -r requirements.txt 7 | - --extra-index-url=https://test.pypi.org/simple 8 | - --trusted-host=test.pypi.org -------------------------------------------------------------------------------- /engines/pyfunc-ensembler-service/pyfunc_ensembler_runner/__init__.py: -------------------------------------------------------------------------------- 1 | from .ensembler_runner import PyFuncEnsemblerRunner 2 | -------------------------------------------------------------------------------- /engines/pyfunc-ensembler-service/pyfunc_ensembler_runner/ensembler_runner.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from typing import Dict, List, Any 4 | from mlflow import pyfunc 5 | 6 | 7 | class PyFuncEnsemblerRunner: 8 | """ 9 | PyFunc ensembler runner used for real-time outputs 10 | """ 11 | 12 | def __init__(self, artifact_dir: str): 13 | self.artifact_dir = artifact_dir 14 | self._ensembler = None 15 | 16 | def load(self): 17 | self._ensembler = pyfunc.load_model(self.artifact_dir) 18 | 19 | def predict(self, body: Dict[str, Any], headers: Dict[str, str]) -> List[Any]: 20 | logging.info(f"Input request payload: {body}") 21 | output = self._ensembler.predict({"headers": dict(headers), "body": body}) 22 | logging.info(f"Output response: {output}") 23 | return output 24 | -------------------------------------------------------------------------------- /engines/pyfunc-ensembler-service/pyfunc_ensembler_runner/server.py: -------------------------------------------------------------------------------- 1 | import tornado.web 2 | 3 | from pyfunc_ensembler_runner.handler import EnsemblerHandler 4 | from pyfunc_ensembler_runner.ensembler_runner import PyFuncEnsemblerRunner 5 | 6 | 7 | class PyFuncEnsemblerServer: 8 | def __init__(self, ensembler: PyFuncEnsemblerRunner): 9 | self.ensembler = ensembler 10 | 11 | def create_application(self): 12 | return tornado.web.Application( 13 | [(r"/ensemble", EnsemblerHandler, dict(ensembler=self.ensembler))] 14 | ) 15 | -------------------------------------------------------------------------------- /engines/pyfunc-ensembler-service/requirements.dev.txt: -------------------------------------------------------------------------------- 1 | black==22.6.0 2 | # The next release 8.2.0 of pytest breaks the unit tests 3 | pytest<=8.1.2 4 | pytest-cov 5 | pylint -------------------------------------------------------------------------------- /engines/pyfunc-ensembler-service/requirements.txt: -------------------------------------------------------------------------------- 1 | argparse>=1.4.0 2 | cloudpickle==2.0.0 3 | orjson==3.6.8 4 | setuptools<75 5 | tornado==6.1 6 | turing-sdk==0.0.0 7 | -------------------------------------------------------------------------------- /engines/pyfunc-ensembler-service/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/engines/pyfunc-ensembler-service/tests/__init__.py -------------------------------------------------------------------------------- /engines/pyfunc-ensembler-service/tests/testdata/request_short.json: -------------------------------------------------------------------------------- 1 | { 2 | "request": { 3 | "feature_0": 0.12, 4 | "feature_1": "a good feature" 5 | }, 6 | "response": { 7 | "route_responses": [ 8 | { 9 | "route": "control", 10 | "data": { 11 | "predictions": 213 12 | }, 13 | "is_default": true 14 | }, 15 | { 16 | "route": "route_1", 17 | "data": { 18 | "predictions": "a bad prediction" 19 | }, 20 | "is_default": false 21 | } 22 | ], 23 | "experiment": { 24 | "configuration": { 25 | "name": "configuration_0" 26 | }, 27 | "error": "" 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /engines/pyfunc-ensembler-service/version.py: -------------------------------------------------------------------------------- 1 | VERSION = "0.0.0" 2 | -------------------------------------------------------------------------------- /engines/router/.golangci.yml: -------------------------------------------------------------------------------- 1 | ../../.golangci.yml -------------------------------------------------------------------------------- /engines/router/compose/.env.development: -------------------------------------------------------------------------------- 1 | ../.env.development -------------------------------------------------------------------------------- /engines/router/compose/app.yaml: -------------------------------------------------------------------------------- 1 | version: '3.1' 2 | 3 | services: 4 | turing-router-app: 5 | depends_on: 6 | - kafka 7 | deploy: 8 | replicas: 2 9 | image: turing-router:${IMAGE_VERSION} 10 | env_file: 11 | - ./.env.development 12 | environment: 13 | - ROUTER_CONFIG_FILE=/app/configs/default_router.yaml 14 | - GOOGLE_APPLICATION_CREDENTIALS=/run/secrets/google_app_creds 15 | - LITMUS_PASSKEY=${LITMUS_PASSKEY} 16 | - XP_PASSKEY=${XP_PASSKEY} 17 | - APP_FLUENTD_HOST=turing_stack_fluentd 18 | - APP_JAEGER_REPORTER_HOST=turing_stack_jaeger 19 | - APP_KAFKA_BROKERS=kafka:9092 20 | secrets: 21 | - google_app_creds 22 | volumes: 23 | - ./configs:/app/configs 24 | ports: 25 | - 8080:8080 26 | 27 | secrets: 28 | google_app_creds: 29 | external: true -------------------------------------------------------------------------------- /engines/router/compose/docs.yaml: -------------------------------------------------------------------------------- 1 | version: '3.1' 2 | 3 | services: 4 | swagger-ui: 5 | image: swaggerapi/swagger-ui:v3.52.0 6 | ports: 7 | - 8081:5555 8 | volumes: 9 | - ../api:/app:ro 10 | environment: 11 | - PORT=5555 12 | - LAYOUT=BaseLayout 13 | - SWAGGER_JSON=/app/openapi.yaml 14 | -------------------------------------------------------------------------------- /engines/router/compose/fluentd.yaml: -------------------------------------------------------------------------------- 1 | version: '3.1' 2 | 3 | services: 4 | fluentd: 5 | image: asia.gcr.io/gcp-project-id/fluentd-bigquery:latest 6 | environment: 7 | - FLUENTD_WORKER_COUNT=1 8 | - FLUENTD_LOG_LEVEL=info 9 | - FLUENTD_LOG_PATH=/fluentd/log/bq_load_logs.*.buffer 10 | - FLUENTD_BUFFER_LIMIT=3g 11 | - FLUENTD_FLUSH_INTERVAL_SECONDS=60 12 | - FLUENTD_TAG=${APP_FLUENTD_TAG} 13 | - FLUENTD_GCP_JSON_KEY_PATH=/run/secrets/google_app_creds 14 | - FLUENTD_GCP_PROJECT=${APP_GCP_PROJECT} 15 | - FLUENTD_BQ_DATASET=${APP_BQ_DATASET} 16 | - FLUENTD_BQ_TABLE=${APP_BQ_TABLE} 17 | secrets: 18 | - google_app_creds 19 | volumes: 20 | - ./fluentd_logs:/fluentd/log 21 | ports: 22 | - 9880:9880 23 | - 24224:24224 24 | -------------------------------------------------------------------------------- /engines/router/compose/monitoring.yaml: -------------------------------------------------------------------------------- 1 | version: '3.1' 2 | 3 | volumes: 4 | prometheus_data: {} 5 | 6 | services: 7 | prometheus: 8 | image: prom/prometheus:v2.1.0 9 | volumes: 10 | - ./prometheus/config.yml:/etc/prometheus/prometheus.yml:ro 11 | - prometheus_data:/prometheus 12 | command: 13 | - '--config.file=/etc/prometheus/prometheus.yml' 14 | - '--storage.tsdb.path=/prometheus' 15 | - '--web.console.libraries=/usr/share/prometheus/console_libraries' 16 | - '--web.console.templates=/usr/share/prometheus/consoles' 17 | ports: 18 | - 9090:9090 19 | -------------------------------------------------------------------------------- /engines/router/compose/tracing.yaml: -------------------------------------------------------------------------------- 1 | version: '3.1' 2 | 3 | services: 4 | jaeger: 5 | image: jaegertracing/all-in-one:latest 6 | ports: 7 | - 6831:6831/udp 8 | - 16686:16686 9 | -------------------------------------------------------------------------------- /engines/router/configs/default_router.yaml: -------------------------------------------------------------------------------- 1 | type: EAGER_ROUTER 2 | id: eager-router 3 | routes: 4 | - id: route_id_1 5 | type: PROXY 6 | endpoint: "http://www.mocky.io/v2/5e4caccc310000e2cad8c071" 7 | timeout: 5s 8 | - id: control 9 | type: PROXY 10 | endpoint: "http://www.mocky.io/v2/5e4cacd4310000e1cad8c073" 11 | timeout: 5s 12 | strategy: 13 | type: fiber.DefaultTuringRoutingStrategy 14 | properties: 15 | default_route_id: route_id_1 16 | experiment_engine: nop 17 | -------------------------------------------------------------------------------- /engines/router/missionctl/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/caraml-dev/turing/engines/router/missionctl/server" 4 | 5 | func main() { 6 | server.Run() 7 | } 8 | -------------------------------------------------------------------------------- /engines/router/missionctl/instrumentation/metrics/metrics_test.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/caraml-dev/mlp/api/pkg/instrumentation/metrics" 8 | "github.com/stretchr/testify/assert" 9 | 10 | tu "github.com/caraml-dev/turing/engines/router/missionctl/internal/testutils" 11 | ) 12 | 13 | func TestInitMetricsCollectorPrometheus(t *testing.T) { 14 | err := InitMetricsCollector(true) 15 | // Validate 16 | assert.NoError(t, err) 17 | if _, ok := metrics.Glob().(*metrics.PrometheusClient); !ok { 18 | err := fmt.Errorf("Prometheus metrics collector was not initialised") 19 | tu.FailOnError(t, err) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /engines/router/missionctl/instrumentation/tracing/nop_test.go: -------------------------------------------------------------------------------- 1 | package tracing 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestNopMethods(t *testing.T) { 11 | tr := newNopTracer() 12 | 13 | // Create test context 14 | testCtx := context.Background() 15 | 16 | // Test methods with return values 17 | assert.Equal(t, false, tr.IsEnabled()) 18 | 19 | closer, err := tr.InitGlobalTracer("", nil) 20 | assert.NoError(t, err) 21 | err = closer.Close() 22 | assert.NoError(t, err) 23 | 24 | sp, ctx := tr.StartSpanFromRequestHeader(testCtx, "", nil) 25 | assert.Nil(t, sp) 26 | assert.Equal(t, testCtx, ctx) 27 | 28 | sp, ctx = tr.StartSpanFromContext(testCtx, "") 29 | assert.Nil(t, sp) 30 | assert.Equal(t, testCtx, ctx) 31 | } 32 | -------------------------------------------------------------------------------- /engines/router/missionctl/internal/testutils/request.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | "bytes" 5 | "net/http" 6 | "testing" 7 | ) 8 | 9 | // MakeTestRequest creates a dummy request for testing 10 | func MakeTestRequest( 11 | t *testing.T, 12 | httpRequestModifier func(*http.Request), 13 | ) *http.Request { 14 | payload := `{"customer_id": "test_customer"}` 15 | req, err := http.NewRequest(http.MethodPost, "/test", bytes.NewBuffer([]byte(payload))) 16 | req.Header.Set("req_id", "test_req_id") 17 | FailOnError(t, err) 18 | httpRequestModifier(req) 19 | return req 20 | } 21 | 22 | // NopHTTPRequestModifier makes no modification to the input request 23 | func NopHTTPRequestModifier(_ *http.Request) {} 24 | -------------------------------------------------------------------------------- /engines/router/missionctl/internal/testutils/response.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "net/http" 7 | 8 | mchttp "github.com/caraml-dev/turing/engines/router/missionctl/server/http" 9 | ) 10 | 11 | // MakeTestMisisonControlResponse makes a success response with a dummy json body for testing 12 | func MakeTestMisisonControlResponse() mchttp.Response { 13 | httpResponse := &http.Response{ 14 | StatusCode: http.StatusOK, 15 | Body: io.NopCloser(bytes.NewBuffer([]byte(`{"data": "test"}`))), 16 | Header: http.Header{}, 17 | } 18 | mcResp, _ := mchttp.NewCachedResponseFromHTTP(httpResponse) 19 | return mcResp 20 | } 21 | -------------------------------------------------------------------------------- /engines/router/missionctl/internal/testutils/utils.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | "io" 5 | "os" 6 | ) 7 | 8 | // ReadFile reads a file and returns the byte contents 9 | func ReadFile(filepath string) ([]byte, error) { 10 | // Open file 11 | fileObj, err := os.Open(filepath) 12 | if err != nil { 13 | return nil, err 14 | } 15 | defer fileObj.Close() 16 | // Read contents 17 | byteValue, err := io.ReadAll(fileObj) 18 | if err != nil { 19 | return nil, err 20 | } 21 | return byteValue, nil 22 | } 23 | -------------------------------------------------------------------------------- /engines/router/missionctl/internal/version.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import "runtime" 4 | 5 | // Info is struct that holds information about the current build of the app 6 | type Info struct { 7 | Version string `json:"version"` 8 | Branch string `json:"branch"` 9 | BuildUser string `json:"build_user"` 10 | BuildDate string `json:"build_date"` 11 | GoVersion string `json:"go_version"` 12 | } 13 | 14 | // Build information. Populated at build-time. 15 | var ( 16 | Version string 17 | Branch string 18 | BuildUser string 19 | BuildDate string 20 | 21 | VersionInfo = &Info{ 22 | Version: Version, 23 | Branch: Branch, 24 | BuildUser: BuildUser, 25 | BuildDate: BuildDate, 26 | GoVersion: runtime.Version(), 27 | } 28 | ) 29 | -------------------------------------------------------------------------------- /engines/router/missionctl/log/resultlog/nop.go: -------------------------------------------------------------------------------- 1 | package resultlog 2 | 3 | import "github.com/caraml-dev/turing/engines/router/missionctl/log/resultlog/proto/turing" 4 | 5 | // NopLogger generates instance of NopLog for logging results 6 | type NopLogger struct{} 7 | 8 | // NewNopLogger generates an instance of NewNopLogger 9 | func NewNopLogger() *NopLogger { 10 | return &NopLogger{} 11 | } 12 | 13 | // write is a nop method that satisfies the NopLogger interface 14 | func (*NopLogger) write(_ *turing.TuringResultLogMessage) error { 15 | return nil 16 | } 17 | -------------------------------------------------------------------------------- /engines/router/missionctl/log/resultlog/nop_test.go: -------------------------------------------------------------------------------- 1 | package resultlog 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | "github.com/caraml-dev/turing/engines/router/missionctl/log/resultlog/proto/turing" 9 | ) 10 | 11 | func TestNewNopLogger(t *testing.T) { 12 | testLogger := NewNopLogger() 13 | assert.Equal(t, NopLogger{}, *testLogger) 14 | } 15 | 16 | func TestNopLoggerWrite(t *testing.T) { 17 | testLogger := &NopLogger{} 18 | err := testLogger.write(&turing.TuringResultLogMessage{}) 19 | assert.NoError(t, err) 20 | } 21 | -------------------------------------------------------------------------------- /engines/router/missionctl/log/resultlog/upi_kafka.go: -------------------------------------------------------------------------------- 1 | package resultlog 2 | 3 | import ( 4 | "github.com/caraml-dev/turing/engines/router/missionctl/config" 5 | upiv1 "github.com/caraml-dev/universal-prediction-interface/gen/go/grpc/caraml/upi/v1" 6 | ) 7 | 8 | type UPIKafkaLogger struct { 9 | *KafkaLogger 10 | } 11 | 12 | func NewUPIKafkaLogger(cfg *config.KafkaConfig) (*UPIKafkaLogger, error) { 13 | kafkaLogger, err := NewKafkaLogger(cfg) 14 | if err != nil { 15 | return nil, err 16 | } 17 | return &UPIKafkaLogger{kafkaLogger}, nil 18 | } 19 | 20 | func (l *UPIKafkaLogger) write(routerLog *upiv1.RouterLog) error { 21 | return l.writeToKafka( 22 | routerLog, 23 | routerLog.PredictionId, 24 | routerLog.RequestTimestamp, 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /engines/router/missionctl/server/constant/constant.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | const TuringReqIDHeaderKey = "Turing-Req-ID" 4 | -------------------------------------------------------------------------------- /engines/router/missionctl/server/upi/interceptors/recoverer.go: -------------------------------------------------------------------------------- 1 | package interceptors 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "google.golang.org/grpc" 8 | ) 9 | 10 | // PanicRecoveryInterceptor interceptor to recover after facing panic 11 | func PanicRecoveryInterceptor() grpc.UnaryServerInterceptor { 12 | return func(ctx context.Context, 13 | req interface{}, 14 | _ *grpc.UnaryServerInfo, 15 | handler grpc.UnaryHandler) (resp interface{}, err error) { 16 | defer func(_ context.Context) { 17 | if r := recover(); r != nil { 18 | if e, ok := r.(error); ok { 19 | err = e 20 | } else { 21 | err = fmt.Errorf("panic: %s", r) 22 | } 23 | } 24 | }(ctx) 25 | 26 | resp, err = handler(ctx, req) 27 | return resp, err 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /engines/router/missionctl/server/upi/interceptors/recoverer_test.go: -------------------------------------------------------------------------------- 1 | package interceptors 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | upiv1 "github.com/caraml-dev/universal-prediction-interface/gen/go/grpc/caraml/upi/v1" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestPanicRecovery(t *testing.T) { 12 | t.Run("Test panic recovery", func(t *testing.T) { 13 | panicIntercep := PanicRecoveryInterceptor() 14 | _, err := panicIntercep( 15 | context.Background(), 16 | &upiv1.PredictValuesRequest{}, 17 | nil, 18 | func(_ context.Context, _ interface{}) (interface{}, error) { 19 | panic("something wrong") 20 | }) 21 | assert.Equal(t, "panic: something wrong", err.Error()) 22 | }) 23 | 24 | } 25 | -------------------------------------------------------------------------------- /engines/router/missionctl/testdata/batch_router_test.yaml: -------------------------------------------------------------------------------- 1 | type: EAGER_ROUTER 2 | id: eager-router 3 | routes: 4 | - id: route_id_1 5 | type: PROXY 6 | endpoint: "http://localhost:8080/route1" 7 | strategy: 8 | type: fiber.DefaultTuringRoutingStrategy 9 | properties: 10 | default_route_id: route_id_1 11 | experiment_engine: Nop 12 | -------------------------------------------------------------------------------- /engines/router/missionctl/testdata/benchmark/single_route_upi_grpc.yaml: -------------------------------------------------------------------------------- 1 | type: EAGER_ROUTER 2 | id: router-id 3 | routes: 4 | - endpoint: "localhost:50604" 5 | protocol: "grpc" 6 | service_method: "caraml.upi.v1.UniversalPredictionService/PredictValues" 7 | id: control 8 | timeout: 20s 9 | type: PROXY 10 | strategy: 11 | properties: 12 | default_route_id: control 13 | experiment_engine: nop 14 | type: fiber.DefaultTuringRoutingStrategy 15 | -------------------------------------------------------------------------------- /engines/router/missionctl/testdata/benchmark/single_route_upi_http.yaml: -------------------------------------------------------------------------------- 1 | type: EAGER_ROUTER 2 | id: router-id 3 | routes: 4 | - endpoint: "http://localhost:9004/predict_values" 5 | protocol: "http" 6 | id: control 7 | timeout: 20s 8 | type: PROXY 9 | strategy: 10 | properties: 11 | default_route_id: control 12 | experiment_engine: nop 13 | type: fiber.DefaultTuringRoutingStrategy 14 | -------------------------------------------------------------------------------- /engines/router/missionctl/testdata/bq_schema_1_order_diff.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "mode": "REQUIRED", 4 | "name": "ts", 5 | "type": "TIMESTAMP" 6 | }, 7 | { 8 | "mode": "REQUIRED", 9 | "type": "STRING", 10 | "name": "turing_req_id" 11 | }, 12 | { 13 | "fields": [ 14 | { 15 | "type": "STRING", 16 | "name": "body" 17 | }, 18 | { 19 | "name": "header", 20 | "type": "STRING" 21 | } 22 | ], 23 | "mode": "REQUIRED", 24 | "name": "request", 25 | "type": "RECORD" 26 | } 27 | ] -------------------------------------------------------------------------------- /engines/router/missionctl/testdata/bq_schema_1_original.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "mode": "REQUIRED", 4 | "name": "turing_req_id", 5 | "type": "STRING" 6 | }, 7 | { 8 | "mode": "REQUIRED", 9 | "name": "ts", 10 | "type": "TIMESTAMP" 11 | }, 12 | { 13 | "fields": [ 14 | { 15 | "name": "header", 16 | "type": "STRING" 17 | }, 18 | { 19 | "name": "body", 20 | "type": "STRING" 21 | } 22 | ], 23 | "mode": "REQUIRED", 24 | "name": "request", 25 | "type": "RECORD" 26 | } 27 | ] -------------------------------------------------------------------------------- /engines/router/missionctl/testdata/bq_schema_2_field_diff.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "mode": "REQUIRED", 4 | "name": "turing_req_id", 5 | "type": "STRING" 6 | }, 7 | { 8 | "fields": [ 9 | { 10 | "name": "header", 11 | "type": "STRING" 12 | }, 13 | { 14 | "name": "body", 15 | "type": "STRING" 16 | } 17 | ], 18 | "mode": "REQUIRED", 19 | "name": "request", 20 | "type": "RECORD" 21 | } 22 | ] -------------------------------------------------------------------------------- /engines/router/missionctl/testdata/bq_schema_3_required_diff.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "turing_req_id", 4 | "type": "STRING" 5 | }, 6 | { 7 | "mode": "REQUIRED", 8 | "name": "ts", 9 | "type": "TIMESTAMP" 10 | }, 11 | { 12 | "fields": [ 13 | { 14 | "name": "header", 15 | "type": "STRING" 16 | }, 17 | { 18 | "name": "body", 19 | "type": "STRING" 20 | } 21 | ], 22 | "mode": "REQUIRED", 23 | "name": "request", 24 | "type": "RECORD" 25 | } 26 | ] -------------------------------------------------------------------------------- /engines/router/missionctl/testdata/bq_schema_4_nested_schema_diff.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "mode": "REQUIRED", 4 | "name": "turing_req_id", 5 | "type": "STRING" 6 | }, 7 | { 8 | "mode": "REQUIRED", 9 | "name": "ts", 10 | "type": "TIMESTAMP" 11 | }, 12 | { 13 | "fields": [ 14 | { 15 | "name": "header", 16 | "type": "STRING" 17 | } 18 | ], 19 | "mode": "REQUIRED", 20 | "name": "request", 21 | "type": "RECORD" 22 | } 23 | ] -------------------------------------------------------------------------------- /engines/router/missionctl/testdata/enricher_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "customer_id": "1230" 3 | } -------------------------------------------------------------------------------- /engines/router/missionctl/testdata/ensembler_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "result": "ensembled" 3 | } -------------------------------------------------------------------------------- /engines/router/missionctl/testdata/grpc/grpc_router_faulty_port.yaml: -------------------------------------------------------------------------------- 1 | type: EAGER_ROUTER 2 | id: router-id 3 | routes: 4 | - endpoint: "localhost:55555" 5 | service_method: "caraml.upi.v1.UniversalPredictionService/PredictValues" 6 | protocol: "grpc" 7 | id: control 8 | timeout: 20s 9 | type: PROXY 10 | strategy: 11 | properties: 12 | default_route_id: control 13 | experiment_engine: nop 14 | type: fiber.DefaultTuringRoutingStrategy 15 | -------------------------------------------------------------------------------- /engines/router/missionctl/testdata/grpc/grpc_router_minimal.yaml: -------------------------------------------------------------------------------- 1 | type: EAGER_ROUTER 2 | id: router-id 3 | routes: 4 | - endpoint: "localhost:50550" 5 | service_method: "caraml.upi.v1.UniversalPredictionService/PredictValues" 6 | protocol: "grpc" 7 | id: control 8 | timeout: 20s 9 | type: PROXY 10 | strategy: 11 | properties: 12 | default_route_id: control 13 | experiment_engine: nop 14 | type: fiber.DefaultTuringRoutingStrategy 15 | -------------------------------------------------------------------------------- /engines/router/missionctl/testdata/grpc/grpc_router_minimal_two_route.yaml: -------------------------------------------------------------------------------- 1 | type: EAGER_ROUTER 2 | id: router-id 3 | routes: 4 | - endpoint: "localhost:50556" 5 | service_method: "caraml.upi.v1.UniversalPredictionService/PredictValues" 6 | protocol: "grpc" 7 | id: control 8 | timeout: 20s 9 | type: PROXY 10 | - endpoint: "localhost:50557" 11 | service_method: "caraml.upi.v1.UniversalPredictionService/PredictValues" 12 | protocol: "grpc" 13 | id: route1 14 | timeout: 20s 15 | type: PROXY 16 | strategy: 17 | properties: 18 | default_route_id: control 19 | experiment_engine: nop 20 | type: fiber.DefaultTuringRoutingStrategy 21 | -------------------------------------------------------------------------------- /engines/router/missionctl/testdata/nop_default_router.yaml: -------------------------------------------------------------------------------- 1 | type: EAGER_ROUTER 2 | id: eager-router 3 | routes: 4 | - id: route_id_1 5 | type: PROXY 6 | endpoint: "http://localhost:9000/route1/" 7 | - id: control 8 | type: PROXY 9 | endpoint: "http://localhost:9000/control/" 10 | strategy: 11 | type: fiber.DefaultTuringRoutingStrategy 12 | properties: 13 | default_route_id: route_id_1 14 | experiment_engine: Nop 15 | -------------------------------------------------------------------------------- /engines/router/missionctl/testdata/nop_ensembling_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "experiment": {}, 3 | "route_responses": [ 4 | { 5 | "route": "control", 6 | "data": { 7 | "version": 2 8 | }, 9 | "is_default": false 10 | }, 11 | { 12 | "route": "route_id_1", 13 | "data": { 14 | "version": 1 15 | }, 16 | "is_default": true 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /engines/router/missionctl/testdata/nop_ensembling_router.yaml: -------------------------------------------------------------------------------- 1 | type: COMBINER 2 | id: combiner 3 | fan_in: 4 | type: fiber.EnsemblingFanIn 5 | properties: 6 | default_route_id: route_id_1 7 | experiment_engine: Nop 8 | routes: 9 | - id: route_id_1 10 | type: PROXY 11 | endpoint: "http://localhost:9000/route1/" 12 | - id: control 13 | type: PROXY 14 | endpoint: "http://localhost:9000/control/" 15 | -------------------------------------------------------------------------------- /engines/router/missionctl/testdata/route_response_control.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2 3 | } -------------------------------------------------------------------------------- /engines/router/missionctl/testdata/route_response_route_id_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1 3 | } -------------------------------------------------------------------------------- /engines/router/missionctl/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "reflect" 4 | 5 | // GetStructFieldsAsString returns a list of the struct's field values, 6 | // if they can be successfully cast as strings 7 | func GetStructFieldsAsString(object interface{}) []string { 8 | vals := getStructFieldValues(object) 9 | 10 | stringVals := []string{} 11 | for _, val := range vals { 12 | if stringVal, ok := val.(string); ok { 13 | stringVals = append(stringVals, stringVal) 14 | } 15 | } 16 | 17 | return stringVals 18 | } 19 | 20 | func getStructFieldValues(object interface{}) []interface{} { 21 | v := reflect.ValueOf(object) 22 | 23 | values := make([]interface{}, v.NumField()) 24 | 25 | for i := 0; i < v.NumField(); i++ { 26 | values[i] = v.Field(i).Interface() 27 | } 28 | 29 | return values 30 | } 31 | -------------------------------------------------------------------------------- /engines/router/missionctl/utils/utils_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | type testStructType struct { 10 | Name string 11 | Number int 12 | } 13 | 14 | var testStruct = testStructType{ 15 | Name: "string_val", 16 | Number: 10, 17 | } 18 | 19 | func TestGetStructFieldValues(t *testing.T) { 20 | expected := []interface{}{"string_val", 10} 21 | actual := getStructFieldValues(testStruct) 22 | assert.Equal(t, expected, actual) 23 | } 24 | 25 | func TestGetStructFieldsAsString(t *testing.T) { 26 | expected := []string{"string_val"} 27 | actual := GetStructFieldsAsString(testStruct) 28 | assert.Equal(t, expected, actual) 29 | } 30 | -------------------------------------------------------------------------------- /engines/router/prometheus/config.yml: -------------------------------------------------------------------------------- 1 | global: 2 | scrape_interval: 5s 3 | evaluation_interval: 10s 4 | 5 | scrape_configs: 6 | - job_name: turing_custom_metrics 7 | # Collect from 2 replicas 8 | dns_sd_configs: 9 | - names: 10 | - 'tasks.turing-app' 11 | type: 'A' 12 | port: 8080 13 | # Add labels 14 | relabel_configs: 15 | - source_labels: 16 | - status 17 | target_label: namespace 18 | regex: (.*) 19 | replacement: 'test_namespace' 20 | action: replace 21 | - source_labels: 22 | - status 23 | target_label: service 24 | regex: (.*) 25 | replacement: 'turing_router' 26 | action: replace 27 | -------------------------------------------------------------------------------- /infra/charts/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /infra/charts/turing-init/.helmignore: -------------------------------------------------------------------------------- 1 | ../.helmignore -------------------------------------------------------------------------------- /infra/charts/turing-init/Chart.lock: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: spark-operator 3 | repository: https://googlecloudplatform.github.io/spark-on-k8s-operator 4 | version: 1.1.7 5 | digest: sha256:65104e5f202245bc5f61e0b2551c30c95548cd1681e7161e42134b8f9cafffdc 6 | generated: "2021-10-26T16:32:09.187817+08:00" 7 | -------------------------------------------------------------------------------- /infra/charts/turing-init/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: turing-init 3 | description: A Helm chart for Kubernetes 4 | type: application 5 | version: 0.2.0 6 | dependencies: 7 | - name: spark-operator 8 | version: 1.1.7 9 | repository: https://googlecloudplatform.github.io/spark-on-k8s-operator 10 | -------------------------------------------------------------------------------- /infra/charts/turing-init/charts/spark-operator-1.1.7.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/infra/charts/turing-init/charts/spark-operator-1.1.7.tgz -------------------------------------------------------------------------------- /infra/charts/turing-init/templates/cluster-role-binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: {{ include "turing-init.serviceAccountName" . }} 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: {{ include "turing-init.serviceAccountName" . }} 9 | subjects: 10 | - kind: ServiceAccount 11 | name: {{ include "turing-init.serviceAccountName" . }} 12 | namespace: {{ .Release.Namespace }} 13 | -------------------------------------------------------------------------------- /infra/charts/turing-init/templates/istio-operator-config-map.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: {{ include "turing-init.fullname" . }}-istio-operator 5 | data: 6 | operator.yaml: | 7 | {{- toYaml .Values.istio.operatorConfig | nindent 4 }} 8 | -------------------------------------------------------------------------------- /infra/charts/turing-init/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: {{ include "turing-init.serviceAccountName" . }} 5 | -------------------------------------------------------------------------------- /infra/charts/turing/.helmignore: -------------------------------------------------------------------------------- 1 | ../.helmignore -------------------------------------------------------------------------------- /infra/charts/turing/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | description: "Turing: ML Experimentation System" 3 | name: turing 4 | version: 0.2.17 5 | appVersion: v1.0.0 6 | -------------------------------------------------------------------------------- /infra/charts/turing/charts/merlin-0.7.1.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/infra/charts/turing/charts/merlin-0.7.1.tgz -------------------------------------------------------------------------------- /infra/charts/turing/charts/mlp-1.0.1.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/infra/charts/turing/charts/mlp-1.0.1.tgz -------------------------------------------------------------------------------- /infra/charts/turing/charts/postgresql-12.1.9.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/infra/charts/turing/charts/postgresql-12.1.9.tgz -------------------------------------------------------------------------------- /infra/charts/turing/requirements.lock: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: postgresql 3 | repository: https://charts.bitnami.com/bitnami 4 | version: 12.1.9 5 | - name: mlp 6 | repository: file://subcharts/mlp 7 | version: 1.0.1 8 | - name: merlin 9 | repository: file://subcharts/merlin 10 | version: 0.7.1 11 | digest: sha256:55a2f2b7460214e0660755dd1d4602439773b865106e2e2c44a2c46daf86c71d 12 | generated: "2023-01-20T10:48:23.086322+08:00" 13 | -------------------------------------------------------------------------------- /infra/charts/turing/requirements.yaml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: postgresql 3 | repository: https://charts.bitnami.com/bitnami 4 | version: 12.1.9 5 | tags: 6 | - db 7 | - name: mlp 8 | version: 1.0.1 9 | repository: file://subcharts/mlp 10 | tags: 11 | - mlp 12 | - name: merlin 13 | version: 0.7.1 14 | repository: file://subcharts/merlin 15 | tags: 16 | - mlp 17 | -------------------------------------------------------------------------------- /infra/charts/turing/subcharts/merlin/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | description: Kubernetes-friendly ML model management, deployment, and serving. 3 | name: merlin 4 | version: 0.7.1 5 | appVersion: v0.9.1 6 | -------------------------------------------------------------------------------- /infra/charts/turing/subcharts/merlin/charts/postgresql-12.1.9.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/infra/charts/turing/subcharts/merlin/charts/postgresql-12.1.9.tgz -------------------------------------------------------------------------------- /infra/charts/turing/subcharts/merlin/charts/vault-0.16.1.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/infra/charts/turing/subcharts/merlin/charts/vault-0.16.1.tgz -------------------------------------------------------------------------------- /infra/charts/turing/subcharts/merlin/requirements.lock: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: postgresql 3 | repository: https://charts.bitnami.com/bitnami 4 | version: 12.1.9 5 | - name: vault 6 | repository: https://helm.releases.hashicorp.com 7 | version: 0.16.1 8 | digest: sha256:0c56c208f20e6b9c4aa6408f1a77795f7267e048b481c288718233b846709382 9 | generated: "2023-01-20T10:54:44.740643+08:00" 10 | -------------------------------------------------------------------------------- /infra/charts/turing/subcharts/merlin/requirements.yaml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: postgresql 3 | repository: https://charts.bitnami.com/bitnami 4 | version: 12.1.9 5 | - name: vault 6 | repository: https://helm.releases.hashicorp.com 7 | version: 0.16.1 8 | condition: vault.enabled -------------------------------------------------------------------------------- /infra/charts/turing/subcharts/merlin/templates/configmap-environment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | namespace: {{ .Release.Namespace }} 5 | name: {{ template "merlin.fullname" . }}-environments 6 | labels: 7 | app: {{ include "merlin.name" . }} 8 | release: {{ .Release.Name }} 9 | heritage: {{ .Release.Service }} 10 | helm.sh/chart: {{ include "merlin.chart" . }} 11 | app.kubernetes.io/name: {{ include "merlin.name" . }} 12 | app.kubernetes.io/instance: {{ .Release.Name }} 13 | app.kubernetes.io/managed-by: {{ .Release.Service }} 14 | data: 15 | environment.yaml: |- 16 | {{- if .Values.environmentConfigs }} 17 | {{ toYaml .Values.environmentConfigs | indent 4 }} 18 | {{- end }} 19 | -------------------------------------------------------------------------------- /infra/charts/turing/subcharts/merlin/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ template "merlin.fullname" . }} 5 | namespace: {{ .Release.Namespace }} 6 | labels: 7 | app: {{ include "merlin.name" . }} 8 | release: {{ .Release.Name }} 9 | heritage: {{ .Release.Service }} 10 | helm.sh/chart: {{ include "merlin.chart" . }} 11 | app.kubernetes.io/name: {{ include "merlin.name" . }} 12 | app.kubernetes.io/instance: {{ .Release.Name }} 13 | app.kubernetes.io/managed-by: {{ .Release.Service }} 14 | spec: 15 | type: ClusterIP 16 | ports: 17 | - port: {{ .Values.service.externalPort }} 18 | targetPort: {{ .Values.service.internalPort }} 19 | protocol: TCP 20 | selector: 21 | app: {{ template "merlin.name" . }} 22 | release: {{ .Release.Name }} 23 | -------------------------------------------------------------------------------- /infra/charts/turing/subcharts/mlp/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | description: MLP API 3 | name: mlp 4 | version: 1.0.1 5 | appVersion: v1.14.6 6 | -------------------------------------------------------------------------------- /infra/charts/turing/subcharts/mlp/charts/postgresql-12.1.9.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/infra/charts/turing/subcharts/mlp/charts/postgresql-12.1.9.tgz -------------------------------------------------------------------------------- /infra/charts/turing/subcharts/mlp/db-migrations/1_schema_creation.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS users CASCADE; 2 | DROP TABLE IF EXISTS accounts CASCADE; 3 | DROP TABLE IF EXISTS applications; 4 | DROP TABLE IF EXISTS projects; 5 | DROP TABLE IF EXISTS secrets; 6 | -------------------------------------------------------------------------------- /infra/charts/turing/subcharts/mlp/db-migrations/2_applications_config.down.sql: -------------------------------------------------------------------------------- 1 | DELETE FROM applications WHERE name = 'Turing'; -------------------------------------------------------------------------------- /infra/charts/turing/subcharts/mlp/requirements.lock: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: postgresql 3 | repository: https://charts.bitnami.com/bitnami 4 | version: 12.1.9 5 | digest: sha256:3615da30e6713b58d131a8323d888e7eae763d6416acf558bbb7c22841bd65ef 6 | generated: "2023-01-20T10:58:21.680932+08:00" 7 | -------------------------------------------------------------------------------- /infra/charts/turing/subcharts/mlp/requirements.yaml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: postgresql 3 | repository: https://charts.bitnami.com/bitnami 4 | version: 12.1.9 5 | -------------------------------------------------------------------------------- /infra/charts/turing/subcharts/mlp/templates/configmap-db-migrations.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: {{ template "mlp.fullname" . }}-db-migrations 5 | data: 6 | {{- $root := . }} 7 | {{ range $path, $_ := .Files.Glob "db-migrations/*.sql" }} 8 | {{ base $path | indent 2 }}: |- 9 | {{ $root.Files.Get $path | indent 4}} 10 | {{- end }} 11 | -------------------------------------------------------------------------------- /infra/charts/turing/subcharts/mlp/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ template "mlp.fullname" . }} 5 | namespace: {{ .Release.Namespace }} 6 | labels: 7 | app: {{ template "mlp.name" . }} 8 | chart: {{ template "mlp.chart" . }} 9 | release: {{ .Release.Name }} 10 | heritage: {{ .Release.Service }} 11 | spec: 12 | type: ClusterIP 13 | ports: 14 | - port: {{ .Values.service.externalPort }} 15 | targetPort: {{ .Values.service.internalPort }} 16 | protocol: TCP 17 | selector: 18 | app: {{ template "mlp.name" . }} 19 | release: {{ .Release.Name }} 20 | -------------------------------------------------------------------------------- /infra/charts/turing/templates/cluster-role-binding.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.turing.clusterConfig.useInClusterConfig -}} 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRoleBinding 4 | metadata: 5 | name: {{ template "turing.serviceAccount.name" . }}-role-binding 6 | labels: 7 | {{ include "turing.labels" . | indent 4 }} 8 | roleRef: 9 | apiGroup: rbac.authorization.k8s.io 10 | kind: ClusterRole 11 | name: {{ template "turing.serviceAccount.name" . }}-cluster-role 12 | subjects: 13 | - kind: ServiceAccount 14 | name: {{ template "turing.serviceAccount.name" . }} 15 | namespace: {{ .Release.Namespace }} 16 | {{- end }} 17 | -------------------------------------------------------------------------------- /infra/charts/turing/templates/envs-secret.yaml: -------------------------------------------------------------------------------- 1 | {{- if not .Values.mlp.environmentConfigSecret.name }} 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | namespace: {{ .Release.Namespace }} 6 | name: {{ template "turing.fullname" . }}-environments 7 | labels: 8 | {{ include "turing.labels" . | indent 4 }} 9 | stringData: 10 | {{ .Values.turing.clusterConfig.environmentConfigPath }}: | 11 | {{- include "turing.environments" . | nindent 4 -}} 12 | {{- end }} 13 | -------------------------------------------------------------------------------- /infra/charts/turing/templates/openapi-override-configmap.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.turing.openApiSpecOverrides }} 2 | apiVersion: v1 3 | kind: ConfigMap 4 | metadata: 5 | namespace: {{ .Release.Namespace }} 6 | name: {{ template "turing.fullname" . }}-openapi 7 | labels: 8 | {{ include "turing.labels" . | indent 4 }} 9 | data: 10 | override.yaml: | 11 | {{- toYaml .Values.turing.openApiSpecOverrides | nindent 4 -}} 12 | {{- end }} 13 | -------------------------------------------------------------------------------- /infra/charts/turing/templates/secrets.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | namespace: {{ .Release.Namespace }} 5 | name: {{ template "turing.fullname" . }}-api-config 6 | labels: 7 | {{ include "turing.labels" . | indent 4 }} 8 | stringData: 9 | config.yaml: | 10 | {{- include "turing.config" . | nindent 4 -}} 11 | {{- if .Values.turing.uiConfig }} 12 | ui.config.json: | 13 | {{- include "turing.ui.config" . | nindent 4 -}} 14 | {{- end }} 15 | -------------------------------------------------------------------------------- /infra/charts/turing/templates/service-account.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.turing.clusterConfig.useInClusterConfig -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ template "turing.serviceAccount.name" . }} 6 | labels: 7 | {{ include "turing.labels" . | indent 4 }} 8 | {{- end -}} 9 | -------------------------------------------------------------------------------- /infra/charts/turing/templates/service-turing.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ template "turing.fullname" . }} 5 | namespace: {{ .Release.Namespace }} 6 | labels: 7 | app: {{ template "turing.fullname" . }} 8 | chart: {{ template "turing.chart" . }} 9 | release: {{ .Release.Name }} 10 | heritage: {{ .Release.Service }} 11 | {{ if .Values.turing.labels -}} 12 | {{ toYaml .Values.turing.labels | indent 4 -}} 13 | {{- end }} 14 | spec: 15 | type: ClusterIP 16 | ports: 17 | - name: http 18 | port: {{ .Values.turing.service.externalPort }} 19 | targetPort: {{ .Values.turing.service.internalPort }} 20 | protocol: TCP 21 | selector: 22 | app: {{ template "turing.fullname" . }} 23 | release: {{ .Release.Name }} 24 | -------------------------------------------------------------------------------- /infra/charts/turing/values.minimal.yaml: -------------------------------------------------------------------------------- 1 | global: 2 | # -- Google OAuth2 Client ID used by the Turing UI to authenticate users. 3 | # see: https://developers.google.com/adwords/api/docs/guides/authentication#generate_oauth2_credentials 4 | oauthClientId: "your-oauth-client-id" 5 | 6 | mlp: 7 | encryption: 8 | # -- Set MLP's encryption key that will be used to encrypt sensitive data retrieved from MLP API 9 | key: "encryption-key" 10 | 11 | turing: 12 | 13 | clusterConfig: 14 | # -- Use in-cluster authentication mode, so Turing routers will be deployed to the same cluster, 15 | # where Turing API is deployed 16 | useInClusterConfig: true 17 | -------------------------------------------------------------------------------- /infra/cluster-init/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3 2 | 3 | COPY --from=bitnami/kubectl:1.22.16 /opt/bitnami/kubectl/bin/kubectl /usr/bin/kubectl 4 | 5 | WORKDIR /app 6 | RUN apk add --no-cache curl bash 7 | 8 | COPY . . 9 | -------------------------------------------------------------------------------- /infra/cluster-init/Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash 2 | 3 | APP_NAME := cluster-init 4 | 5 | .PHONY: build-image 6 | build-image: version 7 | @$(eval IMAGE_TAG = $(if $(DOCKER_REGISTRY),$(DOCKER_REGISTRY)/,)${APP_NAME}:${VERSION}) 8 | @echo "Building docker image: ${IMAGE_TAG}" 9 | @docker build . --tag ${IMAGE_TAG} 10 | 11 | .PHONY: version 12 | version: 13 | $(eval VERSION=$(if $(OVERWRITE_VERSION),$(OVERWRITE_VERSION),v$(shell ../../scripts/vertagen/vertagen.sh -p ${APP_NAME}/))) 14 | @echo "turing-cluster-init version:" $(VERSION) 15 | -------------------------------------------------------------------------------- /infra/cluster-init/README.md: -------------------------------------------------------------------------------- 1 | # Cluster Init 2 | 3 | This is the container used to spin up Knative and Istio as they do not have publicly hosted helm charts. The `turingcluster` Helm charts will use this container to install those components. During Helm delete, the cluster will also use the `cleanup.sh` script to clean up everything that it has installed. 4 | -------------------------------------------------------------------------------- /infra/cluster-init/knative-configmaps/config-features.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: config-features 5 | namespace: knative-serving 6 | labels: 7 | app.kubernetes.io/name: knative-serving 8 | app.kubernetes.io/component: controller 9 | app.kubernetes.io/version: devel 10 | annotations: 11 | knative.dev/example-checksum: "4d5feafc" 12 | data: 13 | kubernetes.podspec-volumes-emptydir: "enabled" 14 | kubernetes.podspec-init-containers: "enabled" 15 | kubernetes.podspec-topologyspreadconstraints: "enabled" -------------------------------------------------------------------------------- /infra/docker-compose/dev/k3s/registries.yaml: -------------------------------------------------------------------------------- 1 | mirrors: 2 | "localhost:5000": 3 | endpoint: 4 | - "http://local-registry:5000" 5 | -------------------------------------------------------------------------------- /infra/docker-compose/dev/merlin/deployment-config.yaml: -------------------------------------------------------------------------------- 1 | - name: "dev" 2 | is_default: true 3 | cluster: "k3s" 4 | region: "" 5 | gcp_project: "" 6 | deployment_timeout: "10m" 7 | namespace_timeout: "2m" 8 | min_replica: 0 9 | max_replica: 1 10 | cpu_request: "500m" 11 | memory_request: "500Mi" 12 | cpu_limit: "1" 13 | memory_limit: "1Gi" 14 | max_cpu: "4" 15 | max_memory: "8Gi" 16 | queue_resource_percentage: "20" 17 | is_prediction_job_enabled: true 18 | is_default_prediction_job: true 19 | prediction_job_config: 20 | executor_replica: 3 21 | driver_cpu_request: "2" 22 | driver_memory_request: "2Gi" 23 | executor_cpu_request: "2" 24 | executor_memory_request: "2Gi" -------------------------------------------------------------------------------- /infra/docker-compose/dev/mlp/db-migrations/1_schema_creation.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS users CASCADE; 2 | DROP TABLE IF EXISTS accounts CASCADE; 3 | -------------------------------------------------------------------------------- /infra/docker-compose/dev/mlp/db-migrations/2_applications.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS applications; -------------------------------------------------------------------------------- /infra/docker-compose/dev/mlp/db-migrations/3_add_turing_app.down.sql: -------------------------------------------------------------------------------- 1 | UPDATE applications SET is_disabled = FALSE WHERE name = 'Clockwork'; 2 | 3 | DELETE FROM applications WHERE name = 'Turing'; 4 | -------------------------------------------------------------------------------- /infra/docker-compose/dev/mlp/db-migrations/3_add_turing_app.up.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO applications 2 | ( 3 | name, 4 | href, 5 | description, 6 | icon, 7 | use_projects, 8 | is_in_beta, 9 | is_disabled 10 | ) 11 | VALUES 12 | ('Turing', '/turing', 'Platform for setting up ML experiments', 'graphApp', TRUE, TRUE, FALSE); 13 | 14 | UPDATE applications SET is_disabled = TRUE WHERE name = 'Clockwork'; 15 | -------------------------------------------------------------------------------- /infra/docker-compose/dev/mlp/db-migrations/4_projects.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE projects; -------------------------------------------------------------------------------- /infra/docker-compose/dev/mlp/db-migrations/4_projects.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS projects 2 | ( 3 | id serial PRIMARY KEY, 4 | name varchar(50) NOT NULL, 5 | mlflow_tracking_url varchar(100) NOT NULL, 6 | k8s_cluster_name varchar(100) NOT NULL default '', 7 | administrators varchar(256)[], 8 | readers varchar(256)[], 9 | team varchar(64), 10 | stream varchar(64), 11 | labels jsonb, 12 | created_at timestamp NOT NULL default current_timestamp, 13 | updated_at timestamp NOT NULL default current_timestamp, 14 | UNIQUE (name) 15 | ); 16 | -------------------------------------------------------------------------------- /infra/docker-compose/dev/mlp/db-migrations/5_secrets.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE secrets; -------------------------------------------------------------------------------- /infra/docker-compose/dev/mlp/db-migrations/5_secrets.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS secrets 2 | ( 3 | id serial PRIMARY KEY, 4 | project_id integer REFERENCES projects(id) NOT NULL, 5 | name varchar(100) NOT NULL, 6 | data text NOT NULL, 7 | created_at timestamp NOT NULL default current_timestamp, 8 | updated_at timestamp NOT NULL default current_timestamp, 9 | UNIQUE (project_id, name) 10 | ); 11 | -------------------------------------------------------------------------------- /infra/docker-compose/dev/mlp/dev.env: -------------------------------------------------------------------------------- 1 | DATABASE_HOST=mlp-postgres 2 | DATABASE_USER=mlp 3 | DATABASE_PASSWORD=mlp 4 | DATABASE_NAME=mlp 5 | 6 | ENCRYPTION_KEY=password 7 | GITLAB_ENABLED=false 8 | MLFLOW_TRACKING_URL=http://example.com 9 | -------------------------------------------------------------------------------- /infra/e2e/turing.values.in-cluster.yaml: -------------------------------------------------------------------------------- 1 | turing: 2 | clusterConfig: 3 | useInClusterConfig: true -------------------------------------------------------------------------------- /infra/e2e/vault.helm-values.yaml: -------------------------------------------------------------------------------- 1 | server: 2 | dev: 3 | enabled: true 4 | # When running a Vault server in dev-mode, the v2 kv secrets engine is 5 | # enabled by default at the path 'secret/' (for non-dev servers, it is currently v1). 6 | # 7 | # Merlin looks for cluster credentials under `secret/` and it expects 8 | # it to be stored as a KV v1 secret, hence we need to disable whatever secrets engine 9 | # is enabled under 'secret/' and enable KV v1 secrets engine under this path. 10 | postStart: 11 | - /bin/sh 12 | - -c 13 | - >- 14 | sleep 5 && 15 | vault secrets disable secret/ && 16 | vault secrets enable -path=secret -version=1 kv 17 | -------------------------------------------------------------------------------- /infra/e2e/vault.ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | annotations: 5 | kubernetes.io/ingress.class: istio 6 | name: vault 7 | spec: 8 | rules: 9 | - host: vault.127.0.0.1.nip.io 10 | http: 11 | paths: 12 | - path: / 13 | pathType: Prefix 14 | backend: 15 | service: 16 | name: vault 17 | port: 18 | number: 8200 19 | -------------------------------------------------------------------------------- /scripts/fluentd-bigquery/.gitignore: -------------------------------------------------------------------------------- 1 | .env -------------------------------------------------------------------------------- /scripts/fluentd-bigquery/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | ruby '~> 3.1.2' 3 | 4 | gem "fluent-plugin-bigquery", "~> 3.0.0" 5 | -------------------------------------------------------------------------------- /scripts/fluentd-bigquery/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: version 2 | version: 3 | $(eval VERSION=$(if $(OVERWRITE_VERSION),$(OVERWRITE_VERSION),v$(shell ../vertagen/vertagen.sh -f docker))) 4 | @echo "version:" $(VERSION) 5 | 6 | 7 | .PHONY: build-image 8 | build-image: version 9 | @$(eval IMAGE_TAG = $(if $(DOCKER_REGISTRY),$(DOCKER_REGISTRY)/,)fluentd:${VERSION}) 10 | @echo "Building docker image: ${IMAGE_TAG}" 11 | docker build . --tag ${IMAGE_TAG} 12 | -------------------------------------------------------------------------------- /scripts/fluentd-bigquery/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #source vars if file exists 4 | DEFAULT=/etc/default/fluentd 5 | 6 | if [ -r $DEFAULT ]; then 7 | set -o allexport 8 | . $DEFAULT 9 | set +o allexport 10 | fi 11 | 12 | # If the user has supplied only arguments append them to `fluentd` command 13 | if [ "${1#-}" != "$1" ]; then 14 | set -- fluentd "$@" 15 | fi 16 | 17 | # If user does not supply config file or plugins, use the default 18 | if [ "$1" = "fluentd" ]; then 19 | if ! echo $@ | grep ' \-c' ; then 20 | set -- "$@" -c /fluentd/etc/${FLUENTD_CONF} 21 | fi 22 | 23 | if ! echo $@ | grep ' \-p' ; then 24 | set -- "$@" -p /fluentd/plugins 25 | fi 26 | fi 27 | 28 | exec "$@" 29 | -------------------------------------------------------------------------------- /scripts/swagger-ui-generator/README.md: -------------------------------------------------------------------------------- 1 | # Swagger UI Generator 2 | 3 | Tiny script to download Swagger UI distribution and configure it to be used with a local spec file. 4 | 5 | ## Usage 6 | 7 | ```bash 8 | $ swagger-ui-generator.sh 9 | -h, --help Display help 10 | -s, --spec-file A file containing the API definition 11 | -o, --output The output path for the generated Swagger UI files 12 | -v, --version The Swagger-UI version to use (default: DEFAULT_SWAGGER_UI_VERSION)" 13 | ``` -------------------------------------------------------------------------------- /sdk/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = 3 | turing/generated/* -------------------------------------------------------------------------------- /sdk/.gitignore: -------------------------------------------------------------------------------- 1 | .openapi-generator/ 2 | 3 | build/ 4 | dist/ 5 | turing_sdk.egg-info/ 6 | env/ 7 | __pycache__/ 8 | .coverage 9 | -------------------------------------------------------------------------------- /sdk/.openapi-generator-ignore: -------------------------------------------------------------------------------- 1 | # OpenAPI Generator Ignore 2 | # Generated by openapi-generator https://github.com/openapitools/openapi-generator 3 | 4 | # Use this file to prevent files from being overwritten by the generator. 5 | # The patterns follow closely to .gitignore or .dockerignore. 6 | 7 | turing/__init__.py 8 | turing/generated_README.md -------------------------------------------------------------------------------- /sdk/README.md: -------------------------------------------------------------------------------- 1 | # Turing SDK 2 | 3 | Python SDK for interacting with Turing, machine learning models testing 4 | and experiments configuration component Gojek MLP. 5 | 6 | ## Install 7 | Install Turing SDK from PyPI: 8 | ```shell 9 | pip install turing-sdk 10 | ``` 11 | 12 | ## Getting Started 13 | 14 | Check out [samples](./samples) for examples on how to use Turing SDK. 15 | 16 | * Quickstart – [samples/quickstart](./samples/quickstart) 17 | 18 | ## Development 19 | 20 | #### Prerequisites 21 | 22 | * Python >=3.8,<3.10 23 | * openapi-generator >= 5.1.0 (`brew install openapi-generator`) 24 | 25 | ### Make commands 26 | 27 | * Setup development environment 28 | ```shell 29 | make setup 30 | ``` 31 | 32 | * (Re-)generate openapi client 33 | ```shell 34 | make gen-client 35 | ``` 36 | 37 | * Run unit tests 38 | ```shell 39 | make test 40 | ``` 41 | -------------------------------------------------------------------------------- /sdk/docs/assets/turing-sdk-classes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/sdk/docs/assets/turing-sdk-classes.png -------------------------------------------------------------------------------- /sdk/openapi-codegen.yaml: -------------------------------------------------------------------------------- 1 | projectName: turing-sdk 2 | packageName: turing.generated 3 | generateSourceCodeOnly: true 4 | # Global Properties 5 | globalProperties: 6 | apiTests: false 7 | modelTests: false 8 | apiDocs: false 9 | modelDocs: false -------------------------------------------------------------------------------- /sdk/requirements.dev.txt: -------------------------------------------------------------------------------- 1 | black==22.6.0 2 | setuptools>=21.0.0,<75 3 | wheel 4 | twine 5 | # The next release 8.2.0 of pytest breaks the unit tests 6 | pytest<=8.1.2 7 | pytest-cov 8 | urllib3-mock>=0.3.3 9 | caraml-upi-protos 10 | grpcio-reflection 11 | -------------------------------------------------------------------------------- /sdk/requirements.txt: -------------------------------------------------------------------------------- 1 | cloudpickle==2.0.0 2 | deprecation==2.1.0 3 | fire 4 | google-cloud-storage>=1.19.0 5 | mlflow>=1.26.1,<2.0.0 6 | pandas 7 | protobuf>=3.12.0,<5.0.0 # Determined by the mlflow dependency 8 | python_dateutil>=2.5.3 9 | requests 10 | urllib3>=1.25.3 11 | caraml-auth-google==0.0.0.post9 12 | PyPrind>=2.11.2 13 | numpy>=1.26.0 14 | -------------------------------------------------------------------------------- /sdk/samples/README.md: -------------------------------------------------------------------------------- 1 | ## Turing SDK Examples 2 | --- 3 | 4 | ### Quick Start example 5 | * [`quickstart/quick_start.py`](./quickstart/quick_start.py) 6 | Basic example to introduce Turing SDK concepts and usage patterns 7 | **Usage:** 8 | ```python 9 | python quick_start.py \ 10 | --turing_api= \ 11 | --project= 12 | ``` 13 | 14 | ### Other examples 15 | * [`batch_job/batch_job.py`](batch_job/batch_job.py) 16 | Example that demonstrates how to configure and submit a batch ensembling job 17 | with Turing 18 | **Usage:** 19 | ```python 20 | python batch_job.py \ 21 | --turing_api= \ 22 | --project= 23 | ``` -------------------------------------------------------------------------------- /sdk/samples/common/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Optional 2 | import turing 3 | import pandas 4 | 5 | 6 | class MyEnsembler(turing.ensembler.PyFunc): 7 | """ 8 | Simple ensembler, that returns predictions from the `model_odd` 9 | if `customer_id` is odd, or predictions from `model_even` otherwise 10 | """ 11 | 12 | def initialize(self, artifacts: dict): 13 | pass 14 | 15 | def ensemble( 16 | self, 17 | input: pandas.Series, 18 | predictions: pandas.Series, 19 | treatment_config: Optional[dict], 20 | **kwargs: Optional[dict] 21 | ) -> Any: 22 | customer_id = input["customer_id"] 23 | if (customer_id % 2) == 0: 24 | return predictions["model_even"] 25 | else: 26 | return predictions["model_odd"] 27 | -------------------------------------------------------------------------------- /sdk/turing/batch/__init__.py: -------------------------------------------------------------------------------- 1 | from turing.batch.job import EnsemblingJob, EnsemblingJobStatus 2 | 3 | __all__ = ["EnsemblingJob", "EnsemblingJobStatus"] 4 | -------------------------------------------------------------------------------- /sdk/turing/batch/config/__init__.py: -------------------------------------------------------------------------------- 1 | from .config import EnsemblingJobConfig, ResourceRequest, ResultConfig, ResultType 2 | 3 | __all__ = [ 4 | "EnsemblingJobConfig", 5 | "ResourceRequest", 6 | "ResultConfig", 7 | "ResultType", 8 | ] 9 | -------------------------------------------------------------------------------- /sdk/turing/generated/api/__init__.py: -------------------------------------------------------------------------------- 1 | # do not import all apis into this module because that uses a lot of memory and stack frames 2 | # if you need the ability to import all apis from one package, import them with 3 | # from turing.generated.apis import EnsemblerApi 4 | -------------------------------------------------------------------------------- /sdk/turing/generated/apis/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | # flake8: noqa 3 | 4 | # Import all APIs into this package. 5 | # If you have many APIs here with many many models used in each API this may 6 | # raise a `RecursionError`. 7 | # In order to avoid this, import only the API that you directly need like: 8 | # 9 | # from .api.ensembler_api import EnsemblerApi 10 | # 11 | # or import this package, but before doing it, use: 12 | # 13 | # import sys 14 | # sys.setrecursionlimit(n) 15 | 16 | # Import APIs into API package: 17 | from turing.generated.api.ensembler_api import EnsemblerApi 18 | from turing.generated.api.ensembler_images_api import EnsemblerImagesApi 19 | from turing.generated.api.ensembling_job_api import EnsemblingJobApi 20 | from turing.generated.api.project_api import ProjectApi 21 | from turing.generated.api.router_api import RouterApi 22 | -------------------------------------------------------------------------------- /sdk/turing/generated/model/__init__.py: -------------------------------------------------------------------------------- 1 | # we can not import model classes here because that would create a circular 2 | # reference which would not work in python2 3 | # do not import all models into this module because that uses a lot of memory and stack frames 4 | # if you need the ability to import all models from one package, import them with 5 | # from {{packageName}.models import ModelA, ModelB 6 | -------------------------------------------------------------------------------- /sdk/turing/router/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/sdk/turing/router/__init__.py -------------------------------------------------------------------------------- /sdk/turing/router/config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/sdk/turing/router/config/__init__.py -------------------------------------------------------------------------------- /sdk/turing/router/config/common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/sdk/turing/router/config/common/__init__.py -------------------------------------------------------------------------------- /sdk/turing/version.py: -------------------------------------------------------------------------------- 1 | # Do not update this field manually; this value gets updated automically during build time 2 | VERSION = "0.0.0" 3 | -------------------------------------------------------------------------------- /ui/.env.development: -------------------------------------------------------------------------------- 1 | REACT_APP_ENVIRONMENT=dev 2 | 3 | REACT_APP_TURING_API=http://localhost:8080/v1 4 | -------------------------------------------------------------------------------- /ui/.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | /.pnp 4 | .pnp.js 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | npm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | 16 | .env.development.local 17 | /.eslintcache 18 | -------------------------------------------------------------------------------- /ui/public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/ui/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /ui/public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/ui/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /ui/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/ui/public/apple-touch-icon.png -------------------------------------------------------------------------------- /ui/public/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #00aba9 7 | 8 | 9 | -------------------------------------------------------------------------------- /ui/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/ui/public/favicon-16x16.png -------------------------------------------------------------------------------- /ui/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/ui/public/favicon-32x32.png -------------------------------------------------------------------------------- /ui/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/ui/public/favicon.ico -------------------------------------------------------------------------------- /ui/public/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/ui/public/mstile-144x144.png -------------------------------------------------------------------------------- /ui/public/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/ui/public/mstile-150x150.png -------------------------------------------------------------------------------- /ui/public/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/ui/public/mstile-310x150.png -------------------------------------------------------------------------------- /ui/public/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/ui/public/mstile-310x310.png -------------------------------------------------------------------------------- /ui/public/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caraml-dev/turing/332508e94281841eaf5a47d79c012fd7bfa0b1bf/ui/public/mstile-70x70.png -------------------------------------------------------------------------------- /ui/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /ui/public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Turing", 3 | "short_name": "Turing", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff" 18 | } 19 | -------------------------------------------------------------------------------- /ui/src/PrivateLayout.scss: -------------------------------------------------------------------------------- 1 | .main-component-layout { 2 | padding-top: 49px; 3 | } 4 | -------------------------------------------------------------------------------- /ui/src/assets/icons/service-account.svg: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /ui/src/assets/style.scss: -------------------------------------------------------------------------------- 1 | @import "~@elastic/eui/dist/eui_theme_light.css"; 2 | @import "~@caraml-dev/ui-lib/dist/index.css"; 3 | 4 | // don't stretch all the way 5 | @media only screen and (min-width: 960px) { 6 | .euiPage { 7 | max-width: 92% !important; 8 | } 9 | } 10 | 11 | .euiBody--collapsibleNavIsDocked { 12 | padding-left: 262px !important; 13 | } 14 | 15 | // to overwrite this from @caraml-dev/ui-lib 16 | .euiTableCellContent .cell-first-column, 17 | .euiTableCellContent svg { 18 | margin-right: 0 !important; 19 | } 20 | -------------------------------------------------------------------------------- /ui/src/components/collapsible_badge_group/CollapsibleBadgeGroup.scss: -------------------------------------------------------------------------------- 1 | .euiBadgeGroup---collapsible { 2 | white-space: break-spaces !important; 3 | line-height: initial !important; 4 | } 5 | -------------------------------------------------------------------------------- /ui/src/components/config_section/ConfigSection.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from "react"; 2 | import { ConfigSectionTitle } from "."; 3 | 4 | export const ConfigSection = ({ title, iconType, children }) => ( 5 | 6 | 7 | {children} 8 | 9 | ); 10 | -------------------------------------------------------------------------------- /ui/src/components/config_section/ConfigSectionPanel.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { ConfigMultiSectionPanel } from "../config_multi_section_panel/ConfigMultiSectionPanel"; 3 | 4 | export const ConfigSectionPanel = (props) => ( 5 | 9 | ); 10 | -------------------------------------------------------------------------------- /ui/src/components/config_section/ConfigSectionPanelTitle.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from "react"; 2 | import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiTitle } from "@elastic/eui"; 3 | 4 | export const ConfigSectionPanelTitle = ({ title, append }) => ( 5 | 6 | {append ? 7 | 8 | 9 | 10 | {title} 11 | 12 | 13 | 14 | {append} 15 | 16 | 17 | : 18 | {title} 19 | } 20 | 21 | 22 | ); 23 | -------------------------------------------------------------------------------- /ui/src/components/config_section/ConfigSectionTitle.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { EuiIcon, EuiTextColor, EuiTitle, EuiSpacer } from "@elastic/eui"; 3 | 4 | export const ConfigSectionTitle = ({ title, iconType }) => ( 5 | 6 | 7 | 8 | 9 | 10 | {!!iconType && ( 11 | 12 | )} 13 |  {title} 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | -------------------------------------------------------------------------------- /ui/src/components/config_section/index.js: -------------------------------------------------------------------------------- 1 | import "./index.scss"; 2 | 3 | export * from "./ConfigSection"; 4 | export * from "./ConfigSectionTitle"; 5 | export * from "./ConfigSectionPanel"; 6 | export * from "./ConfigSectionPanelTitle"; 7 | -------------------------------------------------------------------------------- /ui/src/components/confirmation_modal/ConfirmationModal.scss: -------------------------------------------------------------------------------- 1 | .euiModalBody__overflow { 2 | -webkit-mask-image: none !important; 3 | } -------------------------------------------------------------------------------- /ui/src/components/expandable_container/ExpandableContainer.scss: -------------------------------------------------------------------------------- 1 | .scrollableContainer { 2 | overflow-y: auto !important; 3 | } 4 | -------------------------------------------------------------------------------- /ui/src/components/form/described_form_group/DescribedFormGroup.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { EuiFlexGroup, EuiFlexItem, EuiText } from "@elastic/eui"; 3 | import "./DescribedFormGroup.scss"; 4 | 5 | export const DescribedFormGroup = ({ description, ...props }) => ( 6 | 7 | {props.children} 8 | 9 | {description && ( 10 | 11 | 12 | {description} 13 | 14 | 15 | )} 16 | 17 | ); 18 | -------------------------------------------------------------------------------- /ui/src/components/form/described_form_group/DescribedFormGroup.scss: -------------------------------------------------------------------------------- 1 | .euiFlexItem--engineTypeDescription { 2 | padding-top: 18px; 3 | padding-left: 24px; 4 | } 5 | -------------------------------------------------------------------------------- /ui/src/components/form/docker_image_combo_box/DockerRegistryPopover.js: -------------------------------------------------------------------------------- 1 | import { EuiSuperSelect } from "@elastic/eui"; 2 | 3 | export const DockerRegistryPopover = ({ value, registryOptions, onChange }) => { 4 | 5 | return ( 6 | 13 | ); 14 | }; -------------------------------------------------------------------------------- /ui/src/components/form/docker_image_combo_box/SelectDockerImageComboBox.scss: -------------------------------------------------------------------------------- 1 | @import "@elastic/eui/src/themes/amsterdam/colors_light"; 2 | @import "@elastic/eui/src/themes/amsterdam/globals"; 3 | 4 | .euiComboBox--dockerImage .euiFormControlLayout { 5 | height: $euiFormControlHeight; 6 | } 7 | -------------------------------------------------------------------------------- /ui/src/components/form/endpoint_combo_box/SelectEndpointComboBox.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { isValidUrl } from "../../../utils/validation"; 3 | import { EuiComboBoxSelect } from "../combo_box/EuiComboBoxSelect"; 4 | 5 | export const SelectEndpointComboBox = ({ 6 | value, 7 | protocol, 8 | onChange, 9 | options, 10 | ...props 11 | }) => { 12 | const onCreateOption = (value) => { 13 | if (!isValidUrl(value, protocol)) { 14 | return false; 15 | } 16 | onChange(value); 17 | }; 18 | 19 | return ( 20 | 28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /ui/src/components/form/field_duration/EuiFieldDuration.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { EuiFieldNumber, EuiFormLabel } from "@elastic/eui"; 3 | 4 | const durationRegex = /([0-9]+)(ms|s|m|h)$/; 5 | 6 | const parseDuration = (value) => { 7 | if (value) { 8 | let matches = `${value}`.match(durationRegex); 9 | if (matches) { 10 | return [parseInt(matches[1]), matches[2]]; 11 | } 12 | } 13 | return undefined; 14 | }; 15 | 16 | export const EuiFieldDuration = (props) => { 17 | const [value, timeUnit] = parseDuration(props.value) || [0, "ms"]; 18 | 19 | return ( 20 | props.onChange(`${e.target.value}${timeUnit}`)} 25 | append={{timeUnit}} 26 | /> 27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /ui/src/components/form/functions/extensible_function.js: -------------------------------------------------------------------------------- 1 | export class ExtensibleFunction extends Function { 2 | constructor(f) { 3 | super(); 4 | return Object.setPrototypeOf(f, new.target.prototype); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ui/src/components/form/functions/stackable_function.js: -------------------------------------------------------------------------------- 1 | import { ExtensibleFunction } from "./extensible_function"; 2 | 3 | export class StackableFunction extends ExtensibleFunction { 4 | constructor(args, apply) { 5 | super((value) => { 6 | apply(args, value); 7 | }); 8 | this.apply = apply; 9 | this.args = args; 10 | } 11 | 12 | withArg(arg) { 13 | return new StackableFunction([...this.args, arg], this.apply); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ui/src/components/form/hooks/useOnChangeHandler.js: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useRef } from "react"; 2 | 3 | export const useOnChangeHandler = (onChangeHandler) => { 4 | const nested = useRef({}); 5 | 6 | // reset nested handlers when the parent handler is changed 7 | useEffect(() => { 8 | nested.current = {}; 9 | }, [onChangeHandler]); 10 | 11 | const onChange = useCallback( 12 | (arg) => { 13 | if (!nested.current[arg]) { 14 | nested.current[arg] = onChangeHandler.withArg(arg); 15 | } 16 | return nested.current[arg]; 17 | }, 18 | [onChangeHandler] 19 | ); 20 | 21 | return { onChangeHandler, onChange }; 22 | }; 23 | -------------------------------------------------------------------------------- /ui/src/components/form/in_memory_table_form/InMemoryTableForm.scss: -------------------------------------------------------------------------------- 1 | .euiBasicTable--inMemoryFormTable { 2 | .euiTableCellContent { 3 | display: grid; 4 | 5 | .cell-first-column, 6 | svg { 7 | margin-right: inherit; 8 | } 9 | } 10 | 11 | input[type="text"].inlineTableInput { 12 | float: left; 13 | padding: 0; 14 | font-size: inherit !important; 15 | line-height: inherit !important; 16 | font-family: inherit !important; 17 | height: inherit !important; 18 | transition: inherit !important; 19 | background-color: inherit !important; 20 | box-shadow: none !important; 21 | min-width: 90px; 22 | 23 | &:focus { 24 | background-size: 0 100%; 25 | } 26 | } 27 | 28 | .euiTableRow--isInvalid { 29 | border-left: 2px solid #bd271e; 30 | background-color: #f8e9e98c; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ui/src/components/form/label_with_tooltip/FormLabelWithToolTip.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { EuiIcon, EuiToolTip } from "@elastic/eui"; 3 | 4 | export const FormLabelWithToolTip = ({ label, content, size = "s" }) => ( 5 | 6 | 7 | {label}  8 | 14 | 15 | 16 | ); 17 | -------------------------------------------------------------------------------- /ui/src/components/form/utils.js: -------------------------------------------------------------------------------- 1 | /*eslint no-sequences: 0*/ 2 | export const normalizePath = (key) => key.replace(/\[([^}\]]+)]/g, ".$1"); 3 | 4 | export const get = (obj, key) => { 5 | return key.split(".").reduce(function (o, x) { 6 | return typeof o === "undefined" || o === null ? o : o[x]; 7 | }, obj); 8 | }; 9 | 10 | export const set = (obj, key, value) => { 11 | let props = key.split("."), 12 | arrIndex = -1; 13 | props.reduce( 14 | (o, d, i) => ( 15 | (arrIndex = d.indexOf("[") > -1 && d[d.indexOf("[") + 1]), 16 | arrIndex && (d = d.slice(0, d.indexOf("["))), 17 | i === props.length - 1 18 | ? (o[d] = value) 19 | : ((o[d] = o[d] || {}), 20 | arrIndex && (Array.isArray(o[d]) || (o[d] = [o[d]])), 21 | (arrIndex && o[d][arrIndex]) || o[d]) 22 | ), 23 | obj 24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /ui/src/components/horizontal_description_list/HorizontalDescriptionList.js: -------------------------------------------------------------------------------- 1 | import { EuiDescriptionList, EuiFlexGroup, EuiFlexItem } from "@elastic/eui"; 2 | import React from "react"; 3 | 4 | export const HorizontalDescriptionList = ({ 5 | listItems, 6 | titleProps, 7 | descriptionProps, 8 | ...props 9 | }) => ( 10 | 15 | {listItems.map((item, idx) => ( 16 | 17 | 22 | 23 | ))} 24 | 25 | ); 26 | -------------------------------------------------------------------------------- /ui/src/components/overlay_mask/OverlayMask.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import "./OverlayMask.scss"; 4 | import useDimension from "../../hooks/useDimension"; 5 | 6 | export const OverlayMask = ({ parentRef, opacity = 0.5, children }) => { 7 | const { width, height } = useDimension(parentRef); 8 | 9 | return ( 10 |
13 | {children} 14 |
15 | ); 16 | }; 17 | -------------------------------------------------------------------------------- /ui/src/components/overlay_mask/OverlayMask.scss: -------------------------------------------------------------------------------- 1 | .overlayMask { 2 | position: absolute; 3 | display: -webkit-box; 4 | display: -ms-flexbox; 5 | display: flex; 6 | -webkit-box-align: center; 7 | -ms-flex-align: center; 8 | align-items: center; 9 | -webkit-box-pack: center; 10 | -ms-flex-pack: center; 11 | justify-content: center; 12 | z-index: 999; 13 | } 14 | -------------------------------------------------------------------------------- /ui/src/components/page/PageTitle.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiTitle } from "@elastic/eui"; 3 | import { useConfig } from "../../config"; 4 | 5 | export const PageTitle = ({ title, size, icon, iconSize, prepend }) => { 6 | const { appConfig } = useConfig(); 7 | return ( 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | {title} 16 | 17 | 18 | 19 | {!!prepend && {prepend}} 20 | 21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /ui/src/components/remote_component/RemoteComponent.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import useRemoteComponent from "../../hooks/useRemoteComponent"; 3 | 4 | export const RemoteComponent = ({ scope, name, fallback, ...props }) => { 5 | const Component = useRemoteComponent(scope, name); 6 | return ( 7 | 8 | 9 | 10 | ); 11 | }; 12 | -------------------------------------------------------------------------------- /ui/src/components/status_badge/StatusBadge.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { EuiBadge } from "@elastic/eui"; 3 | import "./StatusBadge.scss"; 4 | 5 | export const StatusBadge = ({ status }) => 6 | !!status ? ( 7 | 11 | {status.label} 12 | 13 | ) : null; 14 | -------------------------------------------------------------------------------- /ui/src/components/status_badge/StatusBadge.scss: -------------------------------------------------------------------------------- 1 | .euiBadge--status { 2 | border-radius: 24px; 3 | padding: 2px 10px; 4 | letter-spacing: initial !important; 5 | } 6 | -------------------------------------------------------------------------------- /ui/src/components/status_health/DeploymentStatusHealth.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { EuiHealth } from "@elastic/eui"; 3 | 4 | export const DeploymentStatusHealth = ({ status }) => { 5 | return {status.label}; 6 | }; 7 | -------------------------------------------------------------------------------- /ui/src/ensembler/components/modal/useEnsemblerModal.js: -------------------------------------------------------------------------------- 1 | import { useCallback, useState } from "react"; 2 | 3 | export const useEnsemblerModal = (closeModalRef) => { 4 | const [ensembler, setEnsembler] = useState(); 5 | 6 | const openModal = useCallback((onSubmit) => { 7 | return (ensembler) => { 8 | setEnsembler(ensembler); 9 | onSubmit(); 10 | }; 11 | }, []); 12 | 13 | const closeModal = useCallback(() => { 14 | setEnsembler(undefined); 15 | closeModalRef.current(); 16 | }, [closeModalRef]); 17 | 18 | return [ensembler, openModal, closeModal]; 19 | }; 20 | -------------------------------------------------------------------------------- /ui/src/hooks/useInitiallyLoaded.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | export const useInitiallyLoaded = (isLoaded) => { 4 | const [hasInitiallyLoaded, setHasInitiallyLoaded] = useState(isLoaded); 5 | 6 | useEffect(() => { 7 | if (!hasInitiallyLoaded) { 8 | setHasInitiallyLoaded(isLoaded); 9 | } 10 | }, [isLoaded, hasInitiallyLoaded, setHasInitiallyLoaded]); 11 | 12 | return hasInitiallyLoaded; 13 | }; 14 | -------------------------------------------------------------------------------- /ui/src/hooks/useMerlinApi.js: -------------------------------------------------------------------------------- 1 | import { AuthContext, useApi } from "@caraml-dev/ui-lib"; 2 | import { useContext } from "react"; 3 | import { useConfig } from "../config"; 4 | 5 | export const useMerlinApi = ( 6 | endpoint, 7 | options, 8 | result, 9 | callImmediately = true 10 | ) => { 11 | const { apiConfig } = useConfig(); 12 | const authCtx = useContext(AuthContext); 13 | 14 | return useApi( 15 | endpoint, 16 | { 17 | baseApiUrl: apiConfig.merlinApiUrl, 18 | timeout: apiConfig.apiTimeout, 19 | useMockData: apiConfig.useMockData, 20 | ...options, 21 | }, 22 | authCtx, 23 | result, 24 | callImmediately 25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /ui/src/hooks/useTuringApi.js: -------------------------------------------------------------------------------- 1 | import { AuthContext, useApi } from "@caraml-dev/ui-lib"; 2 | import { useContext } from "react"; 3 | import { useConfig } from "../config"; 4 | 5 | export const useTuringApi = ( 6 | endpoint, 7 | options, 8 | result, 9 | callImmediately = true 10 | ) => { 11 | const { apiConfig } = useConfig(); 12 | const authCtx = useContext(AuthContext); 13 | 14 | return useApi( 15 | endpoint, 16 | { 17 | baseApiUrl: apiConfig.turingApiUrl, 18 | timeout: apiConfig.apiTimeout, 19 | useMockData: apiConfig.useMockData, 20 | ...options, 21 | }, 22 | authCtx, 23 | result, 24 | callImmediately 25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /ui/src/index.js: -------------------------------------------------------------------------------- 1 | import("./bootstrap"); 2 | -------------------------------------------------------------------------------- /ui/src/jobs/EnsemblingJobsRouter.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Route, Routes, useParams } from "react-router-dom"; 3 | import { ListEnsemblingJobsLandingView } from "./list/ListEnsemblingJobsLandingView"; 4 | import { EnsemblingJobDetailsView } from "./details/EnsemblingJobDetailsView"; 5 | import { EnsemblersContextProvider } from "../providers/ensemblers/context"; 6 | 7 | export const EnsemblingJobsRouter = () => { 8 | const { projectId } = useParams(); 9 | return ( 10 | 11 | 12 | } /> 13 | } /> 14 | 15 | 16 | ) 17 | }; 18 | -------------------------------------------------------------------------------- /ui/src/jobs/components/modal/useJobModal.js: -------------------------------------------------------------------------------- 1 | import { useCallback, useState } from "react"; 2 | 3 | export const useJobModal = (closeModalRef) => { 4 | const [job, setJob] = useState(); 5 | 6 | const openModal = useCallback((onSubmit) => { 7 | return (job) => { 8 | setJob(job); 9 | onSubmit(); 10 | }; 11 | }, []); 12 | 13 | const closeModal = useCallback(() => { 14 | setJob(undefined); 15 | closeModalRef.current(); 16 | }, [closeModalRef]); 17 | 18 | return [job, openModal, closeModal]; 19 | }; 20 | -------------------------------------------------------------------------------- /ui/src/jobs/components/page_navigation/EnsemblingJobDetailsPageNavigation.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { EuiIcon } from "@elastic/eui"; 3 | import { PageNavigation } from "@caraml-dev/ui-lib"; 4 | 5 | export const EnsemblingJobDetailsPageNavigation = ({ job, selectedTab }) => { 6 | const tabs = [ 7 | { 8 | id: "details", 9 | name: "Configuration", 10 | }, 11 | { 12 | id: "logs", 13 | name: "Logs", 14 | }, 15 | { 16 | id: "monitoring", 17 | name: ( 18 | 19 | Monitoring  20 | 21 | 22 | ), 23 | href: job.monitoring_url, 24 | disabled: !job.monitoring_url, 25 | }, 26 | ]; 27 | 28 | return ; 29 | }; 30 | -------------------------------------------------------------------------------- /ui/src/jobs/details/config/components/bq_options_table/BigQueryOptionsTable.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { EuiDescriptionList, EuiText } from "@elastic/eui"; 3 | 4 | export const BigQueryOptionsTable = ({ options }) => 5 | options && Object.entries(options).length ? ( 6 | ({ 11 | title, 12 | description, 13 | }))} 14 | columnWidths={[1, 7/3]} 15 | /> 16 | ) : ( 17 | 18 | None 19 | 20 | ); 21 | -------------------------------------------------------------------------------- /ui/src/jobs/details/config/prediction_config_section/PredictionsConfigSection.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from "react"; 2 | import { EuiSpacer, EuiSteps } from "@elastic/eui"; 3 | import { PredictionSourceConfigSection } from "./PredictionSourceConfigSection"; 4 | 5 | import "./PredictionsConfigSection.scss"; 6 | 7 | export const PredictionsConfigSection = ({ 8 | job: { 9 | job_config: { 10 | spec: { predictions }, 11 | }, 12 | }, 13 | }) => ( 14 | 15 | 16 | ({ 20 | title: id, 21 | children: , 22 | }))} 23 | /> 24 | 25 | ); 26 | -------------------------------------------------------------------------------- /ui/src/jobs/details/config/prediction_config_section/PredictionsConfigSection.scss: -------------------------------------------------------------------------------- 1 | @import "@elastic/eui/src/themes/amsterdam/colors_light"; 2 | @import "@elastic/eui/src/themes/amsterdam/globals"; 3 | 4 | .predictionSources { 5 | .euiStepNumber--small { 6 | background-color: $euiColorSuccess; 7 | } 8 | 9 | .euiStep__content { 10 | margin-left: 16px; 11 | padding: 0 0 16px 16px; 12 | } 13 | 14 | .euiStep:last-child { 15 | .euiStep__content { 16 | padding-bottom: 0; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ui/src/jobs/details/config/source_config_section/bq_config_section/BigQueryDatasetQueryView.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { EuiCodeBlock } from "@elastic/eui"; 3 | import dedent from "ts-dedent"; 4 | 5 | export const BigQueryDatasetQueryView = ({ query, maxHeight = 300 }) => ( 6 | 12 | {dedent(query)} 13 | 14 | ); 15 | -------------------------------------------------------------------------------- /ui/src/jobs/details/config/source_config_section/bq_config_section/BigQueryDatasetSection.scss: -------------------------------------------------------------------------------- 1 | .euiAccordion { 2 | .childWrapper { 3 | padding-left: 18px; 4 | } 5 | } 6 | 7 | .euiBadgeGroup---insideDescriptionList { 8 | padding: 4px 0; 9 | } 10 | -------------------------------------------------------------------------------- /ui/src/providers/docker/context.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useConfig } from "../../config"; 3 | 4 | const DockerRegistriesContext = React.createContext([]); 5 | 6 | export const DockerRegistriesContextProvider = ({ children }) => { 7 | const { appConfig } = useConfig(); 8 | 9 | const registries = [ 10 | ...appConfig.privateDockerRegistries.map((registry) => ({ 11 | value: registry, 12 | inputDisplay: registry, 13 | })), 14 | { 15 | value: "docker.io", 16 | inputDisplay: "Docker Hub", 17 | }, 18 | ]; 19 | 20 | return ( 21 | 22 | {children} 23 | 24 | ); 25 | }; 26 | 27 | export default DockerRegistriesContext; 28 | -------------------------------------------------------------------------------- /ui/src/providers/endpoints/context.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const EndpointsContext = React.createContext([]); 4 | 5 | export default EndpointsContext; 6 | -------------------------------------------------------------------------------- /ui/src/providers/environments/context.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useMerlinApi } from "../../hooks/useMerlinApi"; 3 | 4 | const EnvironmentsContext = React.createContext([]); 5 | 6 | export const EnvironmentsContextProvider = ({ children }) => { 7 | const [{ data: environments }] = useMerlinApi("/environments", {}, []); 8 | 9 | return ( 10 | 11 | {children} 12 | 13 | ); 14 | }; 15 | 16 | export default EnvironmentsContext; 17 | -------------------------------------------------------------------------------- /ui/src/providers/experiments/context.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const ExperimentEngineContext = React.createContext({}); 4 | 5 | export default ExperimentEngineContext; 6 | -------------------------------------------------------------------------------- /ui/src/providers/secrets/context.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useMerlinApi } from "../../hooks/useMerlinApi"; 3 | 4 | const SecretsContext = React.createContext([]); 5 | 6 | export const SecretsContextProvider = ({ projectId, children }) => { 7 | const [{ data: secrets }] = useMerlinApi( 8 | `/projects/${projectId}/secrets`, 9 | {}, 10 | [] 11 | ); 12 | 13 | return ( 14 | 15 | {children} 16 | 17 | ); 18 | }; 19 | 20 | export default SecretsContext; 21 | -------------------------------------------------------------------------------- /ui/src/providers/teams/TeamsProvider.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import TeamsContext from "./context"; 3 | import { useMerlinApi } from "../../hooks/useMerlinApi"; 4 | 5 | export const TeamsProvider = ({ ...props }) => { 6 | const [{ data: teams }] = useMerlinApi( 7 | `/alerts/teams`, 8 | { muteError: true }, 9 | [] 10 | ); 11 | 12 | return ( 13 | 14 | {props.children} 15 | 16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /ui/src/providers/teams/context.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const TeamsContext = React.createContext([]); 4 | 5 | export default TeamsContext; 6 | -------------------------------------------------------------------------------- /ui/src/router/activity_log/RouterActivityLogView.scss: -------------------------------------------------------------------------------- 1 | .euiPanel--activityLog { 2 | min-height: 480px; 3 | flex-basis: 0; 4 | flex-grow: 1; 5 | overflow-y: auto; 6 | 7 | .scrollToBottom--container { 8 | height: 100%; 9 | 10 | .euiCommentEvent { 11 | height: calc(100% + 16px) !important; 12 | border: 0; 13 | 14 | .euiCommentEvent__header { 15 | border-bottom: 0; 16 | background: none; 17 | padding: 4px; 18 | } 19 | 20 | .euiCommentEvent__body { 21 | padding: 4px; 22 | } 23 | } 24 | 25 | .followButton { 26 | display: none; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ui/src/router/activity_log/events_list/EventsList.scss: -------------------------------------------------------------------------------- 1 | .activityLogEvent { 2 | // white-space: pre-wrap renders \n in the text as a new line 3 | white-space: pre-wrap; 4 | // word-break: break-word will prevent the possibility of horizontal scroll 5 | // on the text by always breaking when the text overflows 6 | word-break: break-word; 7 | } 8 | -------------------------------------------------------------------------------- /ui/src/router/alerts/components/alert_config_section/AlertConfigSection.scss: -------------------------------------------------------------------------------- 1 | .euiDescriptionList--alertConfigSection { 2 | .euiDescriptionList__title { 3 | margin-top: 0 !important; 4 | padding-top: 8px; 5 | } 6 | .euiDescriptionList__description { 7 | padding-bottom: 8px; 8 | } 9 | 10 | *:nth-child(4n) { 11 | background: #f5f7fa; 12 | } 13 | *:nth-child(4n-1) { 14 | background: #f5f7fa; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ui/src/router/alerts/components/edit_alerts_form/components/metric_panel/MetricPanel.scss: -------------------------------------------------------------------------------- 1 | .durationRow { 2 | max-width: 700px; 3 | 4 | .euiFormControlLayout { 5 | max-width: 700px; 6 | height: 42px; 7 | } 8 | 9 | .euiFormRow__fieldWrapper .euiFormControlLayout .euiFormControlLayout { 10 | min-width: 120px; 11 | max-width: 200px; 12 | } 13 | 14 | .euiFieldNumber { 15 | max-width: 500px; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ui/src/router/components/configuration/components/EnricherConfigSection.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { EuiPanel } from "@elastic/eui"; 3 | import { DockerConfigViewGroup } from "./docker_config_section/DockerConfigViewGroup"; 4 | 5 | export const EnricherConfigSection = ({ config: { enricher } }) => { 6 | return !enricher || enricher.type === "nop" ? ( 7 | Not Configured 8 | ) : ( 9 | 10 | ); 11 | }; 12 | -------------------------------------------------------------------------------- /ui/src/router/components/configuration/components/config.js: -------------------------------------------------------------------------------- 1 | import template from "lodash/template"; 2 | import templateSettings from "lodash/templateSettings"; 3 | 4 | export const getFormattedHomepageUrl = (homepageUrl, projectId) => { 5 | // If {{projectId}} present in the URL, substitute with actual project id. 6 | templateSettings.interpolate = /{{([\s\S]+?)}}/g; 7 | return template(homepageUrl)({ 8 | projectId: projectId, 9 | }); 10 | }; 11 | 12 | export const getExperimentUrl = (homepageUrl, experiment) => { 13 | return !!homepageUrl 14 | ? `${getFormattedHomepageUrl(homepageUrl)}/experiments/${experiment.id}` 15 | : ""; 16 | }; 17 | -------------------------------------------------------------------------------- /ui/src/router/components/configuration/components/docker_config_section/SecretsConfigTable.scss: -------------------------------------------------------------------------------- 1 | .euiInMemoryTable--secretsConfigTable { 2 | .euiTableCellContent { 3 | padding: 8px 0px; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /ui/src/router/components/configuration/components/experiment_config_section/credentials/CredentialsConfigSection.scss: -------------------------------------------------------------------------------- 1 | .euiPanel--configSection.experimentCredentials { 2 | .euiDescriptionList { 3 | margin-left: 10px; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /ui/src/router/components/configuration/components/experiment_config_section/experiments/ExperimentsConfigTable.scss: -------------------------------------------------------------------------------- 1 | .euiPanel--configSection.experiments { 2 | .euiDescriptionList { 3 | margin-left: 10px; 4 | 5 | .euiDescriptionList__title { 6 | width: 170px; 7 | } 8 | 9 | .euiDescriptionList__description { 10 | width: calc(100% - 170px); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ui/src/router/components/configuration/components/nop_config_section/NopConfigViewGroup.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { RouteConfigSection } from "../route_config_section/RouteConfigSection"; 3 | 4 | export const NopConfigViewGroup = ({ nopConfig }) => { 5 | const items = [ 6 | { 7 | title: "Final Response Route", 8 | description: nopConfig.final_response_route_id, 9 | }, 10 | ]; 11 | return ; 12 | }; 13 | -------------------------------------------------------------------------------- /ui/src/router/components/configuration/components/route_config_section/RouteConfigSection.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { EuiDescriptionList } from "@elastic/eui"; 3 | import { ConfigSectionPanel } from "../../../../../components/config_section"; 4 | 5 | export const RouteConfigSection = ({ panelTitle, items }) => ( 6 | 7 | 14 | 15 | ); 16 | -------------------------------------------------------------------------------- /ui/src/router/components/configuration/components/standard_config_section/FallbackRouteConfigSection.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { RouteConfigSection } from "../route_config_section/RouteConfigSection"; 3 | 4 | export const FallbackRouteConfigSection = ({ fallbackResponseRouteId }) => { 5 | const items = [ 6 | { 7 | title: "Fallback Response Route", 8 | description: fallbackResponseRouteId, 9 | }, 10 | ]; 11 | return ; 12 | }; 13 | -------------------------------------------------------------------------------- /ui/src/router/components/form/components/Panel.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | EuiFlexGroup, 4 | EuiFlexItem, 5 | EuiPanel, 6 | EuiSpacer, 7 | EuiTitle, 8 | } from "@elastic/eui"; 9 | 10 | const PanelContent = ({ contentWidth = "75%", children }) => ( 11 | 12 | 13 | {children} 14 | 15 | 16 | ); 17 | 18 | export const Panel = ({ title, contentWidth, children }) => { 19 | return ( 20 | 21 | 22 |

{title}

23 |
24 | 25 | 26 | {children} 27 | 28 | 29 |
30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /ui/src/router/components/form/components/RouterCreationSummary.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from "react"; 2 | import { EuiSpacer } from "@elastic/eui"; 3 | 4 | export const RouterCreationSummary = ({ router }) => { 5 | return ( 6 | 7 |

8 | You're about to deploy a new Turing router {router.name} into{" "} 9 | {router.environment_name} environment. 10 |

11 | 12 | 13 |
14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /ui/src/router/components/form/components/RouterUpdateSummary.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from "react"; 2 | import { EuiSpacer } from "@elastic/eui"; 3 | 4 | export const RouterUpdateSummary = ({ router }) => { 5 | return ( 6 | 7 |

8 | You're about to deploy your changes for the Turing router{" "} 9 | {router.name}. 10 |

11 | 12 | 13 |
14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /ui/src/router/components/form/components/VersionCreationSummary.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from "react"; 2 | import { EuiSpacer } from "@elastic/eui"; 3 | 4 | export const VersionCreationSummary = ({ router }) => { 5 | return ( 6 | 7 |

8 | You're about to save your changes as a new version for the Turing 9 | router {router.name}. 10 |

11 | 12 |

13 | The new version can be deployed at any time from the router history 14 | page. 15 |

16 | 17 | 18 |
19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /ui/src/router/components/form/components/docker_config/SecretsPanel.scss: -------------------------------------------------------------------------------- 1 | .euiBasicTable--inMemoryFormTable { 2 | .euiTableRowCell > .euiTableCellContent { 3 | display: block; 4 | overflow: hidden; 5 | } 6 | } -------------------------------------------------------------------------------- /ui/src/router/components/form/components/ensembler_config/standard_ensembler/TreatmentMappingCard.scss: -------------------------------------------------------------------------------- 1 | @import "@elastic/eui/src/themes/amsterdam/colors_light"; 2 | @import "@elastic/eui/src/themes/amsterdam/globals"; 3 | 4 | .euiCard--treatmentMappingCard { 5 | .euiCard__image { 6 | background-color: $euiPageBackgroundColor; 7 | } 8 | .euiCard__content { 9 | margin-top: 0; 10 | margin-bottom: 10px; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ui/src/router/components/form/components/ensembler_config/standard_ensembler/typeOptions.js: -------------------------------------------------------------------------------- 1 | export const routingStrategyOptions = [ 2 | { 3 | inputDisplay: "Selective", 4 | value: "selective", 5 | description: `Only the required route will be activated, based on the result from the experiment engine. This option is generally more cost-efficient.`, 6 | flag: "true", 7 | }, 8 | { 9 | inputDisplay: "Exhaustive", 10 | value: "exhaustive", 11 | description: `All the routes applicable to the current request will be invoked, along with the experiment engine, in parallel. This option is generally more performant.`, 12 | flag: "false", 13 | } 14 | ]; 15 | -------------------------------------------------------------------------------- /ui/src/router/components/form/components/experiment_config/components/variables_config/VariableConfigRow.scss: -------------------------------------------------------------------------------- 1 | .euiFlexGroup--experimentVariableConfigRow { 2 | &:not(:hover) { 3 | .euiFlexItem--hasActions { 4 | opacity: 1; 5 | filter: grayscale(1); 6 | -webkit-filter: grayscale(1); 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /ui/src/router/components/form/components/experiment_config/config.js: -------------------------------------------------------------------------------- 1 | import { htmlIdGenerator } from "@elastic/eui/lib/services"; 2 | 3 | export const makeId = htmlIdGenerator(); 4 | 5 | export const initConfig = () => ({ 6 | engine: {}, // Will be used for validation 7 | client: {}, 8 | experiments: [], 9 | variables: { config: [] }, 10 | }); 11 | 12 | export const newVariableConfig = () => ({ 13 | field_source: "header", 14 | required: false, 15 | field: "", 16 | name: "", 17 | }); 18 | 19 | export const resetVariableSelection = (v) => { 20 | v.field = ""; 21 | delete v.selected; 22 | }; 23 | -------------------------------------------------------------------------------- /ui/src/router/components/form/components/experiment_config/providers/context.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const ExperimentContext = React.createContext({ 4 | clients: [], 5 | experiments: [], 6 | variables: {}, 7 | }); 8 | 9 | export default ExperimentContext; 10 | -------------------------------------------------------------------------------- /ui/src/router/components/form/components/experiment_config/typeOptions.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from "react"; 2 | 3 | const defaultExperimentEngineOptions = [ 4 | { 5 | value: "nop", 6 | inputDisplay: "None", 7 | description: ( 8 | 9 | Turing will send the original request to all of the configured routes, 10 | but response from Turing will depend on the Ensembler configuration. 11 | 12 | ), 13 | }, 14 | ]; 15 | 16 | export const getExperimentEngineOptions = (engines) => { 17 | return [ 18 | ...defaultExperimentEngineOptions, 19 | ...engines.map((item) => ({ 20 | value: item.name.toLowerCase(), 21 | inputDisplay: item.display_name || item.name, 22 | })), 23 | ]; 24 | }; 25 | -------------------------------------------------------------------------------- /ui/src/router/components/form/components/outcome_config/typeOptions.js: -------------------------------------------------------------------------------- 1 | export const resultLoggingOptions = [ 2 | { 3 | value: "nop", 4 | inputDisplay: "None", 5 | }, 6 | { 7 | value: "bigquery", 8 | inputDisplay: "BigQuery", 9 | }, 10 | { 11 | value: "kafka", 12 | inputDisplay: "Kafka", 13 | }, 14 | ]; 15 | -------------------------------------------------------------------------------- /ui/src/router/components/form/components/router_config/route_card/RouteCard.scss: -------------------------------------------------------------------------------- 1 | .euiCard.euiCard--routeCard { 2 | .euiCard__description { 3 | display: none; 4 | } 5 | 6 | .euiFlexGroup--removeButton { 7 | margin-bottom: -12px; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /ui/src/router/components/form/components/router_config/rule_card/RuleCard.scss: -------------------------------------------------------------------------------- 1 | .euiFlexGroup--trafficRulesRow { 2 | .euiFlexItem--content { 3 | max-width: calc(100% - 56px); 4 | } 5 | 6 | &:hover { 7 | .euiFlexItem--hasActions { 8 | opacity: 1; 9 | filter: grayscale(0); 10 | -webkit-filter: grayscale(0); 11 | } 12 | } 13 | 14 | .euiFlexItem--hasActions { 15 | opacity: 0.7; 16 | filter: grayscale(100%); 17 | -webkit-filter: grayscale(100%); 18 | 19 | &.conditions { 20 | margin-top: 12px; 21 | } 22 | 23 | &.routes { 24 | margin-top: 16px; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ui/src/router/components/form/components/router_config/typeOptions.js: -------------------------------------------------------------------------------- 1 | export const protocolTypeOptions = [ 2 | { 3 | value: "HTTP_JSON", 4 | inputDisplay: "JSON over HTTP (REST)", 5 | }, 6 | { 7 | value: "UPI_V1", 8 | inputDisplay: "Universal Prediction Interface (gRPC)", 9 | }, 10 | ]; 11 | -------------------------------------------------------------------------------- /ui/src/router/components/page_header/PageSecondaryHeader.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { EuiPanel } from "@elastic/eui"; 3 | 4 | import "./PageSecondaryHeader.scss"; 5 | 6 | export const PageSecondaryHeader = (props) => { 7 | return {props.children}; 8 | }; 9 | -------------------------------------------------------------------------------- /ui/src/router/components/page_header/PageSecondaryHeader.scss: -------------------------------------------------------------------------------- 1 | @import "@elastic/eui/src/themes/amsterdam/colors_light"; 2 | @import "@elastic/eui/src/themes/amsterdam/globals"; 3 | 4 | .euiPanel--subHeader { 5 | padding: $euiSize $euiSizeL; 6 | 7 | .euiDescriptionList__title, 8 | .euiDescriptionList__description { 9 | line-height: 1.7rem !important; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ui/src/router/components/request_field_source/FieldSourceFormLabel.scss: -------------------------------------------------------------------------------- 1 | .fieldSourceLabel.euiButtonEmpty, 2 | .fieldSourceDropdown.euiContextMenu { 3 | width: 96px; 4 | } 5 | 6 | .fieldSourceLabel.euiFormLabel { 7 | width: 64px; 8 | } 9 | -------------------------------------------------------------------------------- /ui/src/router/details/components/modals/useRouterModal.js: -------------------------------------------------------------------------------- 1 | import { useCallback, useState } from "react"; 2 | 3 | export const useRouterModal = (closeModalRef) => { 4 | const [router, setRouter] = useState(); 5 | 6 | const openModal = useCallback((onSubmit) => { 7 | return (router) => { 8 | setRouter(router); 9 | onSubmit(); 10 | }; 11 | }, []); 12 | 13 | const closeModal = useCallback(() => { 14 | setRouter(undefined); 15 | closeModalRef.current(); 16 | }, [closeModalRef]); 17 | 18 | return [router, openModal, closeModal]; 19 | }; 20 | -------------------------------------------------------------------------------- /ui/src/router/details/components/router_details_header/RouterDetailsPageHeader.scss: -------------------------------------------------------------------------------- 1 | .euiFlexItem--endpoint { 2 | max-width: calc(60% - 24px); 3 | 4 | .euiToolTipAnchor { 5 | display: block; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ui/src/router/history/HistoryView.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useParams } from "react-router-dom"; 3 | import { EuiFlexGroup, EuiFlexItem } from "@elastic/eui"; 4 | import { RouterActivityLogView } from "../activity_log/RouterActivityLogView"; 5 | import { ListRouterVersionsView } from "../versions/list/ListRouterVersionsView"; 6 | 7 | export const HistoryView = ({ router }) => { 8 | const { projectId } = useParams(); 9 | return ( 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /ui/src/router/versions/components/modals/useVersionModal.js: -------------------------------------------------------------------------------- 1 | import { useCallback, useState } from "react"; 2 | 3 | export const useVersionModal = (closeModalRef) => { 4 | const [version, setVersion] = useState(); 5 | 6 | const openModal = useCallback((onSubmit) => { 7 | return (version) => { 8 | setVersion(version); 9 | onSubmit(); 10 | }; 11 | }, []); 12 | 13 | const closeModal = useCallback(() => { 14 | setVersion(undefined); 15 | closeModalRef.current(); 16 | }, [closeModalRef]); 17 | 18 | return [version, openModal, closeModal]; 19 | }; 20 | -------------------------------------------------------------------------------- /ui/src/router/versions/list/ListRouterVersionsView.scss: -------------------------------------------------------------------------------- 1 | .euiFlexGroup--actionButtons { 2 | margin: 2px; 3 | } 4 | -------------------------------------------------------------------------------- /ui/src/services/alerts/TuringAlerts.js: -------------------------------------------------------------------------------- 1 | const objectAssignDeep = require(`object-assign-deep`); 2 | 3 | export class TuringAlerts { 4 | constructor() { 5 | this.team = undefined; 6 | this.alerts = { 7 | throughput: undefined, 8 | latency95p: undefined, 9 | error_rate: undefined, 10 | cpu_util: undefined, 11 | memory_util: undefined, 12 | }; 13 | } 14 | 15 | static fromJson(json) { 16 | return objectAssignDeep(new TuringAlerts(), json); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ui/src/services/ensembler/EnsemblerType.js: -------------------------------------------------------------------------------- 1 | import { EnumValue, Enum } from "../enum/Enum"; 2 | 3 | export const EnsemblerType = Enum({ 4 | PYFUNC: EnumValue("pyfunc", { 5 | label: "Pyfunc", 6 | }), 7 | }); 8 | -------------------------------------------------------------------------------- /ui/src/services/ensembler/NopEnsembler.js: -------------------------------------------------------------------------------- 1 | import { Ensembler } from "./Ensembler"; 2 | 3 | const objectAssignDeep = require(`object-assign-deep`); 4 | 5 | export class NopEnsembler extends Ensembler { 6 | constructor() { 7 | super("nop"); 8 | 9 | this.nop_config = NopEnsembler.newConfig(); 10 | this.toJSON = this.toJSON.bind(this); 11 | } 12 | 13 | static fromJson(json = {}) { 14 | const ensembler = new NopEnsembler(); 15 | ensembler.nop_config = objectAssignDeep({}, json.nop_config); 16 | return ensembler; 17 | } 18 | 19 | static newConfig() { 20 | return { 21 | final_response_route_id: "", 22 | }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /ui/src/services/ensembler/index.js: -------------------------------------------------------------------------------- 1 | export * from "./Ensembler"; 2 | export * from "./DockerEnsembler"; 3 | export * from "./NopEnsembler"; 4 | export * from "./StandardEnsembler"; 5 | export * from "./PyFuncEnsembler"; 6 | -------------------------------------------------------------------------------- /ui/src/services/enum/Enum.js: -------------------------------------------------------------------------------- 1 | export const EnumValue = (name, props) => 2 | Object.freeze({ 3 | ...props, 4 | toJSON: () => name, 5 | toString: () => name, 6 | valueOf: () => name, 7 | }); 8 | 9 | export const Enum = (values) => { 10 | const allValues = Object.values(values); 11 | 12 | return Object.freeze({ 13 | ...values, 14 | values: allValues, 15 | fromValue: (name) => allValues.find((s) => name === s.toString()), 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /ui/src/services/experiment_engine/BaseExperimentEngine.js: -------------------------------------------------------------------------------- 1 | import { NopExperimentEngine } from "./index"; 2 | import { ExperimentEngine } from "./index"; 3 | 4 | export class BaseExperimentEngine { 5 | constructor(type) { 6 | this.type = type; 7 | this.toJSON = this.toJSON.bind(this); 8 | } 9 | 10 | static fromJson(json = {}) { 11 | if (!json.type || json.type === "nop") { 12 | return new NopExperimentEngine(); 13 | } 14 | return ExperimentEngine.fromJson(json.type, json); 15 | } 16 | 17 | toJSON() { 18 | if (this.type === "nop") { 19 | return { type: this.type }; 20 | } 21 | return { type: this.type, config: this.config }; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ui/src/services/experiment_engine/NopExperimentEngine.js: -------------------------------------------------------------------------------- 1 | import { BaseExperimentEngine } from "./index"; 2 | 3 | export class NopExperimentEngine extends BaseExperimentEngine { 4 | constructor() { 5 | super("nop"); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ui/src/services/experiment_engine/index.js: -------------------------------------------------------------------------------- 1 | export * from "./BaseExperimentEngine"; 2 | export * from "./ExperimentEngine"; 3 | export * from "./NopExperimentEngine.js"; 4 | -------------------------------------------------------------------------------- /ui/src/services/job/SinkType.js: -------------------------------------------------------------------------------- 1 | import { Enum, EnumValue } from "../enum/Enum"; 2 | 3 | export const SinkType = Enum({ 4 | BQ: EnumValue("BQ", { 5 | label: "Google BigQuery", 6 | }), 7 | }); 8 | -------------------------------------------------------------------------------- /ui/src/services/job/SourceType.js: -------------------------------------------------------------------------------- 1 | import { Enum, EnumValue } from "../enum/Enum"; 2 | 3 | export const SourceType = Enum({ 4 | BQ: EnumValue("BQ", { 5 | label: "Google BigQuery", 6 | }), 7 | }); 8 | -------------------------------------------------------------------------------- /ui/src/services/logs/LogEntry.js: -------------------------------------------------------------------------------- 1 | const moment = require("moment"); 2 | 3 | export class LogEntry { 4 | static fromJson(json) { 5 | return Object.assign(new LogEntry(), json); 6 | } 7 | 8 | constructor() { 9 | this.timestamp = ""; 10 | this.pod_name = ""; 11 | this.text_payload = ""; 12 | this.json_payload = null; 13 | } 14 | 15 | localTimestamp() { 16 | return moment 17 | .utc(this.timestamp) 18 | .local() 19 | .format("YYYY-MM-DDTHH:mm:ss.SSSZ"); 20 | } 21 | 22 | toString() { 23 | return `${this.localTimestamp()} - ${ 24 | this.json_payload ? JSON.stringify(this.json_payload) : this.text_payload 25 | }`; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ui/src/services/status/Status.js: -------------------------------------------------------------------------------- 1 | import { EnumValue, Enum } from "../enum/Enum"; 2 | 3 | export const Status = Enum({ 4 | DEPLOYED: EnumValue("deployed", { 5 | label: "Deployed", 6 | color: "success", 7 | iconType: "check", 8 | }), 9 | FAILED: EnumValue("failed", { 10 | label: "Failed", 11 | color: "danger", 12 | iconType: "cross", 13 | }), 14 | PENDING: EnumValue("pending", { 15 | label: "Updating", 16 | color: "warning", 17 | iconType: "clock", 18 | }), 19 | UNDEPLOYED: EnumValue("undeployed", { 20 | label: "Not Deployed", 21 | color: "default", 22 | }), 23 | }); 24 | -------------------------------------------------------------------------------- /ui/src/utils/gcp.js: -------------------------------------------------------------------------------- 1 | export const getBigQueryConsoleUrl = (table) => { 2 | if (table) { 3 | const parts = table.split("."); 4 | if (parts.length === 3) { 5 | const [project, dataset, table] = parts; 6 | return `https://console.cloud.google.com/bigquery?project=${project}&p=${project}&d=${dataset}&t=${table}&page=table`; 7 | } 8 | } 9 | return undefined; 10 | }; 11 | 12 | export const getGCSDashboardUrl = (gcsBucketUri) => { 13 | const uri = gcsBucketUri.replace("gs://", ""); 14 | return `https://console.cloud.google.com/storage/browser/${uri}`; 15 | }; 16 | -------------------------------------------------------------------------------- /ui/src/utils/object.js: -------------------------------------------------------------------------------- 1 | export const isEmpty = (obj) => !obj || !Object.keys(obj).length; 2 | 3 | // Creates a copy of original object and removes specific keys from it 4 | export const stripKeys = (obj, keys, deep = true) => { 5 | if (obj !== Object(obj)) return obj; 6 | 7 | if (Array.isArray(obj)) 8 | return deep ? obj.map((item) => stripKeys(item, keys, deep)) : obj; 9 | 10 | return Object.keys(obj) 11 | .filter((k) => !keys.includes(k)) 12 | .reduce( 13 | (acc, x) => 14 | Object.assign(acc, { 15 | [x]: deep ? stripKeys(obj[x], keys, deep) : obj[x], 16 | }), 17 | {} 18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /ui/src/utils/validation.js: -------------------------------------------------------------------------------- 1 | const urlRegex = { 2 | HTTP_JSON: 3 | /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/, 4 | UPI_V1: /^([a-zA-Z0-9_-]{1,256})(\.[a-zA-Z0-9-]{1,256})*(:\d+)$/, 5 | }; 6 | 7 | export const isValidUrl = (value, protocol) => { 8 | return value.match(urlRegex[protocol]) !== null; 9 | }; 10 | --------------------------------------------------------------------------------