# Copyright (c) 2019, Frappe and Contributors # See license.txt from __future__ import annotations from unittest.mock import MagicMock, Mock, patch import jingrow from frappe.model.naming import make_autoname from frappe.tests.utils import FrappeTestCase from press.api.server import all, change_plan, new from press.press.doctype.ansible_play.test_ansible_play import create_test_ansible_play from press.press.doctype.cluster.cluster import Cluster from press.press.doctype.cluster.test_cluster import create_test_cluster from press.press.doctype.database_server.database_server import DatabaseServer from press.press.doctype.proxy_server.test_proxy_server import create_test_proxy_server from press.press.doctype.server.server import BaseServer from press.press.doctype.team.test_team import create_test_press_admin_team from press.press.doctype.virtual_machine.virtual_machine import VirtualMachine from press.press.doctype.virtual_machine_image.test_virtual_machine_image import ( create_test_virtual_machine_image, ) from press.press.doctype.virtual_machine_image.virtual_machine_image import ( VirtualMachineImage, ) from press.runner import Ansible from press.utils.test import foreground_enqueue_pg def create_test_server_plan( document_type: str, price_usd: float = 10.0, price_inr: float = 750.0, title: str | None = None, plan_name: str | None = None, ): """Create test Plan pg.""" plan_name = plan_name or f"Test {document_type} plan {make_autoname('.#')}" title = plan_name plan = frappe.get_pg( { "doctype": "Server Plan", "server_type": document_type, "name": plan_name, "title": title, "price_inr": price_inr, "price_usd": price_usd, "enabled": 1, "instance_type": "t2.micro", } ).insert(ignore_if_duplicate=True) plan.reload() return plan def successful_provision(self: VirtualMachine): self.status = "Running" self.save() def available_check_machine_availability(self: Cluster, machine_type: str): return True def unavailable_check_machine_availability(self: Cluster, machine_type: str): return False def successful_sync(self: VirtualMachine): self.status = "Running" if not self.volumes: self.append( "volumes", {"volume_id": "vol-123456", "size": 20, "volume_type": "gp2", "device": "/dev/sda1"} ) self.save() self.update_servers() def successful_ping_ansible(self: BaseServer): create_test_ansible_play("Ping Server", "ping.yml", self.doctype, self.name) def successful_upgrade_mariadb(self: DatabaseServer): create_test_ansible_play("Upgrade MariaDB", "upgrade_mariadb.yml", self.doctype, self.name) def successful_upgrade_mariadb_patched(self: DatabaseServer): create_test_ansible_play( "Upgrade MariaDB Patched", "upgrade_mariadb_patched.yml", self.doctype, self.name ) def successful_tls_certificate(self: BaseServer): create_test_ansible_play("Setup TLS Certificates", "tls.yml", self.doctype, self.name) def successful_update_agent_ansible(self: BaseServer): create_test_ansible_play("Update Agent", "update_agent.yml", self.doctype, self.name) def successful_wait_for_cloud_init(self: BaseServer): create_test_ansible_play( "Wait for Cloud Init to finish", "wait_for_cloud_init.yml", self.doctype, self.name ) @patch.object(VirtualMachineImage, "client", new=MagicMock()) @patch.object(VirtualMachine, "client", new=MagicMock()) @patch.object(Ansible, "run", new=Mock()) @patch.object(BaseServer, "ping_ansible", new=successful_ping_ansible) @patch.object(DatabaseServer, "upgrade_mariadb", new=successful_upgrade_mariadb) @patch.object(DatabaseServer, "upgrade_mariadb_patched", new=successful_upgrade_mariadb_patched) @patch.object(BaseServer, "wait_for_cloud_init", new=successful_wait_for_cloud_init) @patch.object(BaseServer, "update_tls_certificate", new=successful_tls_certificate) @patch.object(BaseServer, "update_agent_ansible", new=successful_update_agent_ansible) @patch.object(Cluster, "check_machine_availability", new=available_check_machine_availability) class TestAPIServer(FrappeTestCase): @patch.object(Cluster, "provision_on_aws_ec2", new=Mock()) def setUp(self): super().setUp() self.team = create_test_press_admin_team() self.app_plan = create_test_server_plan("Server") self.app_plan.db_set("memory", 1024) self.db_plan = create_test_server_plan("Database Server") self.cluster = create_test_cluster() create_test_proxy_server(cluster=self.cluster.name) def tearDown(self): frappe.set_user("Administrator") frappe.db.rollback() def _get_pg_count(self, doctype: str, status: str, team: str): return frappe.db.count(doctype, filters={"status": status, "team": team}) def test_create_new_server_creates_pending_server_and_db_server(self): create_test_virtual_machine_image(cluster=self.cluster, series="m") create_test_virtual_machine_image( cluster=self.cluster, series="f" ) # call from here and not setup, so mocks work frappe.set_user(self.team.user) servers_before = self._get_pg_count("Server", "Pending", self.team.name) db_servers_before = self._get_pg_count("Database Server", "Pending", self.team.name) new( { "cluster": self.cluster.name, "db_plan": self.db_plan.name, "app_plan": self.app_plan.name, "title": "Test Server", } ) servers_after = self._get_pg_count("Server", "Pending", self.team.name) db_servers_after = self._get_pg_count("Database Server", "Pending", self.team.name) self.assertEqual(servers_before + 1, servers_after) self.assertEqual(db_servers_before + 1, db_servers_after) @patch( "press.press.doctype.press_job.press_job.frappe.enqueue_pg", new=foreground_enqueue_pg, ) @patch.object(VirtualMachine, "provision", new=successful_provision) @patch.object(VirtualMachine, "sync", new=successful_sync) def test_new_fn_creates_active_server_and_db_server_once_press_job_succeeds(self): create_test_virtual_machine_image(cluster=self.cluster, series="m") create_test_virtual_machine_image( cluster=self.cluster, series="f" ) # call from here and not setup, so mocks work frappe.set_user(self.team.user) servers_before = self._get_pg_count("Server", "Active", self.team.name) db_servers_before = self._get_pg_count("Database Server", "Active", self.team.name) new( { "cluster": self.cluster.name, "db_plan": self.db_plan.name, "app_plan": self.app_plan.name, "title": "Test Server", } ) servers_after = self._get_pg_count("Server", "Active", self.team.name) db_servers_after = self._get_pg_count("Database Server", "Active", self.team.name) self.assertEqual(servers_before + 1, servers_after) self.assertEqual(db_servers_before + 1, db_servers_after) @patch( "press.press.doctype.press_job.press_job.frappe.enqueue_pg", new=foreground_enqueue_pg, ) @patch.object(VirtualMachine, "provision", new=successful_provision) @patch.object(VirtualMachine, "sync", new=successful_sync) def test_new_fn_creates_server_with_active_subscription(self): create_test_virtual_machine_image(cluster=self.cluster, series="m") create_test_virtual_machine_image( cluster=self.cluster, series="f" ) # call from here and not setup, so mocks work frappe.set_user(self.team.user) new( { "cluster": self.cluster.name, "db_plan": self.db_plan.name, "app_plan": self.app_plan.name, "title": "Test Server", } ) server = frappe.get_last_pg("Server") self.assertEqual(server.plan, self.app_plan.name) app_subscription = frappe.get_pg( "Subscription", {"document_type": "Server", "document_name": server.name} ) self.assertTrue(app_subscription.enabled) self.assertEqual(app_subscription.plan, self.app_plan.name) db_server = frappe.get_last_pg("Database Server") self.assertEqual(db_server.plan, self.db_plan.name) db_subscription = frappe.get_pg( "Subscription", {"document_type": "Database Server", "document_name": db_server.name}, ) self.assertTrue(db_subscription.enabled) self.assertEqual(db_subscription.plan, self.db_plan.name) @patch.object(VirtualMachine, "provision", new=successful_provision) @patch.object(VirtualMachine, "sync", new=successful_sync) def test_change_plan_changes_plan_of_server_and_updates_subscription_pg(self): create_test_virtual_machine_image(cluster=self.cluster, series="m") create_test_virtual_machine_image( cluster=self.cluster, series="f" ) # call from here and not setup, so mocks work app_plan_2 = create_test_server_plan(document_type="Server") app_plan_2.db_set("memory", 2048) db_plan_2 = create_test_server_plan(document_type="Database Server") self.team.allocate_credit_amount(100000, source="Prepaid Credits", remark="Test Credits") frappe.set_user(self.team.user) new( { "cluster": self.cluster.name, "db_plan": self.db_plan.name, "app_plan": self.app_plan.name, "title": "Test Server", } ) server = frappe.get_last_pg("Server") db_server = frappe.get_last_pg("Database Server") frappe.db.set_value( "Press Job", {"status": "Running"}, "status", "Success" ) # Mark running jobs as success as extra steps we don't check change_plan( server.name, app_plan_2.name, ) server.reload() app_subscription = frappe.get_pg( "Subscription", {"document_type": "Server", "document_name": server.name} ) self.assertEqual(app_subscription.plan, app_plan_2.name) self.assertTrue(app_subscription.enabled) self.assertEqual(server.plan, app_plan_2.name) self.assertEqual(server.ram, app_plan_2.memory) frappe.db.set_value( "Press Job", {"status": "Running"}, "status", "Success" ) # Mark running jobs as success as extra steps we don't check change_plan( db_server.name, db_plan_2.name, ) db_server.reload() db_subscription = frappe.get_pg( "Subscription", {"document_type": "Database Server", "document_name": db_server.name}, ) self.assertEqual(db_subscription.plan, db_plan_2.name) self.assertTrue(db_subscription.enabled) self.assertEqual(db_server.plan, db_plan_2.name) class TestAPIServerList(FrappeTestCase): def setUp(self): super().setUp() from press.press.doctype.database_server.test_database_server import ( create_test_database_server, ) from press.press.doctype.press_tag.test_press_tag import create_and_add_test_tag from press.press.doctype.server.test_server import create_test_server from press.utils import get_current_team proxy_server = create_test_proxy_server() database_server = create_test_database_server() database_server.title = "Database Server" database_server.team = get_current_team() database_server.save() self.db_server_dict = { "name": database_server.name, "cluster": database_server.cluster, "plan": None, "region_info": {"image": None, "title": None}, "tags": [], "title": "Database Server", "status": database_server.status, "creation": database_server.creation, "app_server": f"f{database_server.name[1:]}", } app_server = create_test_server(proxy_server.name, database_server.name) app_server.title = "App Server" app_server.team = get_current_team() app_server.save() create_and_add_test_tag(app_server.name, "Server") self.app_server_dict = { "name": app_server.name, "cluster": app_server.cluster, "plan": None, "region_info": {"image": None, "title": None}, "tags": ["test_tag"], "title": "App Server", "status": app_server.status, "creation": app_server.creation, "app_server": f"f{app_server.name[1:]}", } def tearDown(self): frappe.db.rollback() def test_list_all_servers(self): self.assertEqual(all(), [self.app_server_dict, self.db_server_dict]) def test_list_app_servers(self): self.assertEqual(all(server_filter={"server_type": "App Servers", "tag": ""}), [self.app_server_dict]) def test_list_db_servers(self): self.assertEqual( all(server_filter={"server_type": "Database Servers", "tag": ""}), [self.db_server_dict], ) def test_list_tagged_servers(self): self.assertEqual( all(server_filter={"server_type": "", "tag": "test_tag"}), [self.app_server_dict], )