def retrieve(self, request, *args, **kwargs): """ Retrieves the deployment status for a given betatest instance. This API will check for provisioning appservers or changes in settings that need to be deployed and return a status code to the frontend. """ application = self.get_object() instance = application.instance undeployed_changes = build_instance_config_diff(application) deployed_changes = None deployment_type = None if not instance or not instance.get_latest_deployment(): deployment_status = DeploymentState.preparing else: deployment = instance.get_latest_deployment() deployment_status = deployment.status() if deployment_status == DeploymentState.healthy and undeployed_changes: deployment_status = DeploymentState.changes_pending deployment_type = deployment.type deployed_changes = deployment.changes data = { 'undeployed_changes': undeployed_changes, 'deployed_changes': deployed_changes, 'status': deployment_status.name, 'deployment_type': deployment_type, } return Response( status=status.HTTP_200_OK, data=OpenEdXInstanceDeploymentStatusSerializer(data).data)
def create_new_deployment( instance_ref_id: int, mark_active_on_success: bool = False, num_attempts: int = 1, success_tag: Optional[str] = None, failure_tag: Optional[str] = None, creator: Optional[int] = None, deployment_type: Optional[DeploymentType] = DeploymentType.unknown, ) -> Optional[int]: """ Create a new Deployment for an existing instance. :param instance_ref_id: ID of an InstanceReference (instance.ref.pk) :param mark_active_on_success: Optionally mark the new AppServer as active when the provisioning completes. :param num_attempts: Optionally retry up to 'num_attempts' times. :param success_tag: Optionally tag the instance with 'success_tag' when the deployment succeeds. :param failure_tag: Optionally tag the instance with 'failure_tag' when the deployment fails. :param creator: Optionally associate deployment with this user. :param deployment_type: Optionally specify a deployment type. :return: The ID of the new deployment. """ logger.info('Retrieving instance: ID=%s', instance_ref_id) instance = OpenEdXInstance.objects.get(ref_set__pk=instance_ref_id) changes = None if instance.betatestapplication_set.exists(): changes = build_instance_config_diff( instance.betatestapplication_set.get()) creator_profile = creator and UserProfile.objects.get(user_id=creator) deployment = OpenEdXDeployment.objects.create( instance_id=instance_ref_id, creator=creator_profile, type=deployment_type, changes=changes, ) logger.info('Spawning servers for deployment %s [%s]', deployment, deployment.id) # Launch configured number of appservers for instance appserver_spawn_tasks = spawn_appserver.map( (instance_ref_id, mark_active_on_success, False, num_attempts, success_tag, failure_tag, deployment.id) for _ in range(instance.openedx_appserver_count)) appserver_ids = appserver_spawn_tasks.get(blocking=True) if not all(appserver_ids): return False # If this deployment is to be marked active on success, others should be deactivated automatically if mark_active_on_success: other_appservers = instance.appserver_set.filter( _is_active=True).exclude(pk__in=appserver_ids) for appserver_to_deactivate in other_appservers: logger.info('Deactivating %s [%s]', appserver_to_deactivate, appserver_to_deactivate.id) appserver_to_deactivate.make_active(active=False) return deployment.pk
def list(self, request, *args, **kwargs): """ Returns Open edX deployments information in form of status notifications. If there is no deployments at all, this endpoint returns blank notification, that deployment is preparing. """ application = self.get_application() deployments = self.get_queryset(application) undeployed_changes = build_instance_config_diff(application) notifications = [] for deployment in deployments: deployment_status = deployment.status() notifications.append({ "deployed_changes": deployment.changes or [], "status": deployment_status.name, "date": deployment.created, }) if not notifications: notifications.append({ "deployed_changes": [], "status": DeploymentState.preparing.name, "date": application.created, }) # Configuration diff relates only to last created deployment, so if # last deployment is healthy and diff is not empty, # we manually change its status. last_notification = notifications[0] if last_notification['status'] == DeploymentState.healthy.name: undeployed_changes = build_instance_config_diff(application) if undeployed_changes: last_notification['status'] = DeploymentState.changes_pending return Response( status=status.HTTP_200_OK, data=OpenEdXInstanceDeploymentNotificationSerializer( notifications, many=True ).data )
def test_get_deployment_notifications(self, mock_create_new_deployment, mock_consul): """ Ensures that notification contain deployment's deployed changes and that status of last healthy deployment with undeployed changes is 'changes_pending'. """ instance = self._setup_user_instance() first_deployment = make_test_deployment( instance, appserver_states=[Status.Running], active=True) second_deployment = make_test_deployment( instance, appserver_states=[Status.Running], active=True) changes = build_instance_config_diff(self.instance_config) first_deployment.changes = changes first_deployment.save() url = reverse("api:v2:notifications-list") response = self.client.get(url) self.assertEqual(response.status_code, 200) self.assertEqual( dict(response.data[0]), { "deployed_changes": [], "status": "changes_pending", "date": second_deployment.created.isoformat().replace("+00:00", "Z"), }, response.data) self.assertEqual( dict(response.data[1]), { # simulate postgres json field serialization and deserialization "deployed_changes": json.loads(json.dumps(changes)), "status": "healthy", "date": first_deployment.created.isoformat().replace("+00:00", "Z"), }, response.data)