You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@continuum.apache.org by ct...@apache.org on 2011/08/26 16:32:02 UTC
svn commit: r1162127 - in /continuum/trunk:
continuum-api/src/main/java/org/apache/continuum/builder/distributed/manager/
continuum-buildagent/continuum-buildagent-core/src/main/java/org/apache/continuum/buildagent/
continuum-core/src/main/java/org/apa...
Author: ctan
Date: Fri Aug 26 14:32:02 2011
New Revision: 1162127
URL: http://svn.apache.org/viewvc?rev=1162127&view=rev
Log:
[CONTINUUM-2656] cancel all current builds of project when clicking cancel button on project build
Modified:
continuum/trunk/continuum-api/src/main/java/org/apache/continuum/builder/distributed/manager/DistributedBuildManager.java
continuum/trunk/continuum-buildagent/continuum-buildagent-core/src/main/java/org/apache/continuum/buildagent/ContinuumBuildAgentServiceImpl.java
continuum/trunk/continuum-core/src/main/java/org/apache/continuum/builder/distributed/manager/DefaultDistributedBuildManager.java
continuum/trunk/continuum-core/src/test/java/org/apache/continuum/builder/distributed/manager/DefaultDistributedBuildManagerTest.java
continuum/trunk/continuum-webapp/src/main/java/org/apache/maven/continuum/web/action/CancelBuildAction.java
Modified: continuum/trunk/continuum-api/src/main/java/org/apache/continuum/builder/distributed/manager/DistributedBuildManager.java
URL: http://svn.apache.org/viewvc/continuum/trunk/continuum-api/src/main/java/org/apache/continuum/builder/distributed/manager/DistributedBuildManager.java?rev=1162127&r1=1162126&r2=1162127&view=diff
==============================================================================
--- continuum/trunk/continuum-api/src/main/java/org/apache/continuum/builder/distributed/manager/DistributedBuildManager.java (original)
+++ continuum/trunk/continuum-api/src/main/java/org/apache/continuum/builder/distributed/manager/DistributedBuildManager.java Fri Aug 26 14:32:02 2011
@@ -118,4 +118,10 @@ public interface DistributedBuildManager
List<ProjectRunSummary> getCurrentRuns();
void removeCurrentRun( int projectId, int buildDefinitionId );
+
+ void cancelBuild( int projectId )
+ throws ContinuumException;
+
+ void cancelGroupBuild( int projectGroupId )
+ throws ContinuumException;
}
Modified: continuum/trunk/continuum-buildagent/continuum-buildagent-core/src/main/java/org/apache/continuum/buildagent/ContinuumBuildAgentServiceImpl.java
URL: http://svn.apache.org/viewvc/continuum/trunk/continuum-buildagent/continuum-buildagent-core/src/main/java/org/apache/continuum/buildagent/ContinuumBuildAgentServiceImpl.java?rev=1162127&r1=1162126&r2=1162127&view=diff
==============================================================================
--- continuum/trunk/continuum-buildagent/continuum-buildagent-core/src/main/java/org/apache/continuum/buildagent/ContinuumBuildAgentServiceImpl.java (original)
+++ continuum/trunk/continuum-buildagent/continuum-buildagent-core/src/main/java/org/apache/continuum/buildagent/ContinuumBuildAgentServiceImpl.java Fri Aug 26 14:32:02 2011
@@ -1067,7 +1067,7 @@ public class ContinuumBuildAgentServiceI
return;
}
}
-
+
try
{
purgeManager.executeDirectoryPurge( directoryType, daysOlder, retentionCount, deleteAll );
Modified: continuum/trunk/continuum-core/src/main/java/org/apache/continuum/builder/distributed/manager/DefaultDistributedBuildManager.java
URL: http://svn.apache.org/viewvc/continuum/trunk/continuum-core/src/main/java/org/apache/continuum/builder/distributed/manager/DefaultDistributedBuildManager.java?rev=1162127&r1=1162126&r2=1162127&view=diff
==============================================================================
--- continuum/trunk/continuum-core/src/main/java/org/apache/continuum/builder/distributed/manager/DefaultDistributedBuildManager.java (original)
+++ continuum/trunk/continuum-core/src/main/java/org/apache/continuum/builder/distributed/manager/DefaultDistributedBuildManager.java Fri Aug 26 14:32:02 2011
@@ -23,6 +23,7 @@ import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -37,6 +38,7 @@ import org.apache.continuum.configuratio
import org.apache.continuum.dao.BuildDefinitionDao;
import org.apache.continuum.dao.BuildResultDao;
import org.apache.continuum.dao.ProjectDao;
+import org.apache.continuum.dao.ProjectScmRootDao;
import org.apache.continuum.distributed.transport.slave.SlaveBuildAgentTransportClient;
import org.apache.continuum.distributed.transport.slave.SlaveBuildAgentTransportService;
import org.apache.continuum.model.project.ProjectRunSummary;
@@ -109,6 +111,11 @@ public class DefaultDistributedBuildMana
/**
* @plexus.requirement
*/
+ private ProjectScmRootDao projectScmRootDao;
+
+ /**
+ * @plexus.requirement
+ */
private DistributedBuildUtil distributedBuildUtil;
private PlexusContainer container;
@@ -730,6 +737,140 @@ public class DefaultDistributedBuildMana
}
}
+ public void cancelGroupBuild( int projectGroupId )
+ throws ContinuumException
+ {
+ log.debug( "Cancelling all builds of project group {}", projectGroupId );
+
+ List<ProjectRunSummary> runsToDelete = new ArrayList<ProjectRunSummary>();
+
+ synchronized( currentRuns )
+ {
+ for ( ProjectRunSummary run : currentRuns )
+ {
+ if ( run.getProjectGroupId() == projectGroupId )
+ {
+ cancelCurrentRun( run, runsToDelete );
+ }
+ }
+
+ if ( runsToDelete.size() > 0 )
+ {
+ currentRuns.removeAll( runsToDelete );
+ }
+ }
+ }
+
+ public void cancelBuild( int projectId )
+ throws ContinuumException
+ {
+ log.debug( "Cancelling all builds of project {}", projectId );
+
+ List<ProjectRunSummary> runsToDelete = new ArrayList<ProjectRunSummary>();
+
+ synchronized( currentRuns )
+ {
+ for ( ProjectRunSummary run : currentRuns )
+ {
+ if ( run.getProjectId() == projectId )
+ {
+ cancelCurrentRun( run, runsToDelete );
+ }
+ }
+
+ if ( runsToDelete.size() > 0 )
+ {
+ currentRuns.removeAll( runsToDelete );
+ }
+ }
+ }
+
+ private void cancelCurrentRun( ProjectRunSummary run, List<ProjectRunSummary> runsToDelete )
+ throws ContinuumException
+ {
+ int projectId = run.getProjectId();
+ int buildDefinitionId = run.getBuildDefinitionId();
+ String buildAgentUrl = run.getBuildAgentUrl();
+
+ // try to remove from any queue first
+ removeFromPrepareBuildQueue( buildAgentUrl, run.getProjectGroupId(), run.getProjectScmRootId() );
+ removeFromBuildQueue( buildAgentUrl, projectId, buildDefinitionId );
+
+ if ( isProjectCurrentlyPreparingBuild( projectId, buildDefinitionId ) )
+ {
+ log.debug( "Unable to cancel build of projectId={}, buildDefinitionId={} in build agent{}. Project is currently doing scm update." );
+ return;
+ }
+ else if ( isProjectCurrentlyBuilding( projectId, buildDefinitionId ) )
+ {
+ log.debug( "Cancel build of projectId={}, buildDefinitionId={} in build agent {}",
+ new Object[] { projectId, buildDefinitionId, buildAgentUrl } );
+ cancelDistributedBuild( buildAgentUrl );
+ runsToDelete.add( run );
+ }
+ else
+ {
+ try
+ {
+ ProjectScmRoot scmRoot = projectScmRootDao.getProjectScmRoot( run.getProjectScmRootId() );
+
+ if ( scmRoot != null && scmRoot.getState() == ContinuumProjectState.UPDATING )
+ {
+ // no longer updating, but state was not updated.
+ scmRoot.setState( ContinuumProjectState.ERROR );
+ scmRoot.setError( "Problem encountered while returning scm update result to master by build agent '" + buildAgentUrl + "'. \n" +
+ "Make sure build agent is configured properly. Check the logs for more information." );
+ projectScmRootDao.updateProjectScmRoot( scmRoot );
+
+ log.debug( "projectId={}, buildDefinitionId={} is not updating anymore. Problem encountered while return scm update result by build agent {}. Stopping the build.",
+ new Object[] { projectId, buildDefinitionId, buildAgentUrl } );
+ runsToDelete.add( run );
+ }
+ else if ( scmRoot != null && scmRoot.getState() == ContinuumProjectState.ERROR )
+ {
+ log.debug( "projectId={}, buildDefinitionId={} is not updating anymore. Problem encountered while return scm update result by build agent {}. Stopping the build.",
+ new Object[] { projectId, buildDefinitionId, buildAgentUrl } );
+ runsToDelete.add( run );
+ }
+ else
+ {
+ Project project = projectDao.getProject( projectId );
+ BuildDefinition buildDefinition = buildDefinitionDao.getBuildDefinition( buildDefinitionId );
+
+ // no longer building, but state was not updated
+ BuildResult buildResult = new BuildResult();
+ buildResult.setBuildDefinition( buildDefinition );
+ buildResult.setBuildUrl( run.getBuildAgentUrl() );
+ buildResult.setTrigger( run.getTrigger() );
+ buildResult.setUsername( run.getTriggeredBy() );
+ buildResult.setState( ContinuumProjectState.ERROR );
+ buildResult.setSuccess( false );
+ buildResult.setStartTime( new Date().getTime() );
+ buildResult.setEndTime( new Date().getTime() );
+ buildResult.setExitCode( 1 );
+ buildResult.setError( "Problem encountered while returning build result to master by build agent '" + buildAgentUrl + "'. \n" +
+ "Make sure build agent is configured properly. Check the logs for more information." );
+ buildResultDao.addBuildResult( project, buildResult );
+
+ project.setState( ContinuumProjectState.ERROR );
+ project.setLatestBuildId( buildResult.getId() );
+ projectDao.updateProject( project );
+
+ log.debug( "projectId={}, buildDefinitionId={} is not building anymore. Problem encountered while return build result by build agent {}. Stopping the build.",
+ new Object[] { projectId, buildDefinitionId, buildAgentUrl } );
+
+ // create a build result
+ runsToDelete.add( run );
+ }
+ }
+ catch ( Exception e )
+ {
+ log.error( "Unable to end build for projectId={}, buildDefinitionId={} : {}",
+ new Object[] { projectId, buildDefinitionId, e.getMessage() } );
+ }
+ }
+ }
+
public Map<String, Object> getBuildResult( int projectId )
throws ContinuumException
{
@@ -1918,4 +2059,13 @@ public class DefaultDistributedBuildMana
this.container = container;
}
+ public void setCurrentRuns( List<ProjectRunSummary> currentRuns )
+ {
+ this.currentRuns = currentRuns;
+ }
+
+ public void setProjectScmRootDao( ProjectScmRootDao projectScmRootDao )
+ {
+ this.projectScmRootDao = projectScmRootDao;
+ }
}
Modified: continuum/trunk/continuum-core/src/test/java/org/apache/continuum/builder/distributed/manager/DefaultDistributedBuildManagerTest.java
URL: http://svn.apache.org/viewvc/continuum/trunk/continuum-core/src/test/java/org/apache/continuum/builder/distributed/manager/DefaultDistributedBuildManagerTest.java?rev=1162127&r1=1162126&r2=1162127&view=diff
==============================================================================
--- continuum/trunk/continuum-core/src/test/java/org/apache/continuum/builder/distributed/manager/DefaultDistributedBuildManagerTest.java (original)
+++ continuum/trunk/continuum-core/src/test/java/org/apache/continuum/builder/distributed/manager/DefaultDistributedBuildManagerTest.java Fri Aug 26 14:32:02 2011
@@ -34,7 +34,9 @@ import org.apache.continuum.configuratio
import org.apache.continuum.dao.BuildDefinitionDao;
import org.apache.continuum.dao.BuildResultDao;
import org.apache.continuum.dao.ProjectDao;
+import org.apache.continuum.dao.ProjectScmRootDao;
import org.apache.continuum.distributed.transport.slave.SlaveBuildAgentTransportService;
+import org.apache.continuum.model.project.ProjectRunSummary;
import org.apache.continuum.model.project.ProjectScmRoot;
import org.apache.continuum.taskqueue.BuildProjectTask;
import org.apache.continuum.taskqueue.OverallDistributedBuildQueue;
@@ -42,9 +44,11 @@ import org.apache.continuum.taskqueue.Pr
import org.apache.continuum.utils.build.BuildTrigger;
import org.apache.maven.continuum.configuration.ConfigurationService;
import org.apache.maven.continuum.model.project.BuildDefinition;
+import org.apache.maven.continuum.model.project.BuildResult;
import org.apache.maven.continuum.model.project.Project;
import org.apache.maven.continuum.model.project.ProjectGroup;
import org.apache.maven.continuum.model.system.Profile;
+import org.apache.maven.continuum.project.ContinuumProjectState;
import org.codehaus.plexus.spring.PlexusInSpringTestCase;
import org.codehaus.plexus.taskqueue.Task;
import org.codehaus.plexus.taskqueue.TaskQueue;
@@ -78,6 +82,8 @@ public class DefaultDistributedBuildMana
private ProjectDao projectDao;
+ private ProjectScmRootDao projectScmRootDao;
+
private ConfigurationService configurationService;
private List<BuildAgentConfiguration> buildAgents;
@@ -123,6 +129,10 @@ public class DefaultDistributedBuildMana
distributedBuildManager.setProjectDao( projectDao );
distributedBuildManagerStub.setProjectDao( projectDao );
+ projectScmRootDao = context.mock( ProjectScmRootDao.class );
+ distributedBuildManager.setProjectScmRootDao( projectScmRootDao );
+ distributedBuildManagerStub.setProjectScmRootDao( projectScmRootDao );
+
distributedBuildManagerStub.setContainer( getContainer() );
configurationService = context.mock( ConfigurationService.class );
@@ -370,6 +380,117 @@ public class DefaultDistributedBuildMana
context.assertIsSatisfied();
}
+ public void testCancelBuildStuckUpdate()
+ throws Exception
+ {
+ distributedBuildManagerStub.setCurrentRuns( getCurrentRuns() );
+
+ recordOfStuckScm();
+
+ distributedBuildManagerStub.cancelBuild( 1 );
+ }
+
+ public void testCancelBuildStuckBuild()
+ throws Exception
+ {
+ distributedBuildManagerStub.setCurrentRuns( getCurrentRuns() );
+
+ recordOfStuckBuild();
+
+ distributedBuildManagerStub.cancelBuild( 1 );
+ }
+
+ private void recordOfStuckBuild()
+ throws Exception
+ {
+ context.checking( new Expectations()
+ {
+ {
+ exactly( 4 ).of( configurationService ).getBuildAgents();
+ will( returnValue( buildAgents ) );
+
+ exactly( 2 ).of( projectScmRootDao ).getProjectScmRoot( 1 );
+ will( returnValue( getScmRoot( ContinuumProjectState.OK ) ) );
+
+ Project proj1 = getProject( 1, ContinuumProjectState.BUILDING );
+ one( projectDao ).getProject( 1 );
+ will( returnValue( proj1 ) );
+
+ one( buildDefinitionDao ).getBuildDefinition( 1 );
+ will( returnValue( new BuildDefinition() ) );
+
+ one( buildResultDao ).addBuildResult( with( any( Project.class ) ), with( any( BuildResult.class ) ) );
+ one( projectDao ).updateProject( proj1 );
+
+ one( projectDao ).getProject( 2 );
+ will( returnValue( getProject( 2, ContinuumProjectState.OK ) ) );
+ }
+ });
+ }
+
+ private void recordOfStuckScm()
+ throws Exception
+ {
+ context.checking( new Expectations()
+ {
+ {
+ exactly( 4 ).of( configurationService ).getBuildAgents();
+ will( returnValue( buildAgents ) );
+
+ ProjectScmRoot scmRootUpdating = getScmRoot( ContinuumProjectState.UPDATING );
+ one( projectScmRootDao ).getProjectScmRoot( 1 );
+ will( returnValue( scmRootUpdating ) );
+
+ one( projectScmRootDao ).updateProjectScmRoot( scmRootUpdating );
+
+ one( projectScmRootDao ).getProjectScmRoot( 1 );
+ will( returnValue( getScmRoot( ContinuumProjectState.ERROR ) ) );
+ }
+ });
+ }
+
+ private List<ProjectRunSummary> getCurrentRuns()
+ {
+ List<ProjectRunSummary> runs = new ArrayList<ProjectRunSummary>();
+
+ ProjectRunSummary run1 = new ProjectRunSummary();
+ run1.setProjectId( 1 );
+ run1.setBuildDefinitionId( 1 );
+ run1.setProjectGroupId( 1 );
+ run1.setProjectScmRootId( 1 );
+ run1.setTrigger( 1 );
+ run1.setTriggeredBy( "user" );
+ run1.setBuildAgentUrl( "http://localhost:8181/continuum-buildagent/xmlrpc" );
+ runs.add( run1 );
+
+ ProjectRunSummary run2 = new ProjectRunSummary();
+ run2.setProjectId( 2 );
+ run2.setBuildDefinitionId( 2 );
+ run2.setProjectGroupId( 1 );
+ run2.setProjectScmRootId( 1 );
+ run2.setTrigger( 1 );
+ run2.setTriggeredBy( "user" );
+ run2.setBuildAgentUrl( "http://localhost:8181/continuum-buildagent/xmlrpc" );
+ runs.add( run2 );
+
+ return runs;
+ }
+
+ private ProjectScmRoot getScmRoot( int state )
+ {
+ ProjectScmRoot scmRoot = new ProjectScmRoot();
+ scmRoot.setState( state );
+ return scmRoot;
+ }
+
+ private Project getProject( int projectId, int state )
+ {
+ Project project = new Project();
+ project.setId( projectId );
+ project.setState( state );
+ return project;
+ }
+
private Map<String, OverallDistributedBuildQueue> getMockOverallDistributedBuildQueues( int size )
{
Map<String, OverallDistributedBuildQueue> overallDistributedBuildQueues =
Modified: continuum/trunk/continuum-webapp/src/main/java/org/apache/maven/continuum/web/action/CancelBuildAction.java
URL: http://svn.apache.org/viewvc/continuum/trunk/continuum-webapp/src/main/java/org/apache/maven/continuum/web/action/CancelBuildAction.java?rev=1162127&r1=1162126&r2=1162127&view=diff
==============================================================================
--- continuum/trunk/continuum-webapp/src/main/java/org/apache/maven/continuum/web/action/CancelBuildAction.java (original)
+++ continuum/trunk/continuum-webapp/src/main/java/org/apache/maven/continuum/web/action/CancelBuildAction.java Fri Aug 26 14:32:02 2011
@@ -64,9 +64,16 @@ public class CancelBuildAction
{
checkBuildProjectInGroupAuthorization( getProjectGroupName() );
- BuildsManager buildsManager = getContinuum().getBuildsManager();
+ if ( getContinuum().getConfiguration().isDistributedBuildEnabled() )
+ {
+ getContinuum().getDistributedBuildManager().cancelBuild( projectId );
+ }
+ else
+ {
+ BuildsManager buildsManager = getContinuum().getBuildsManager();
- buildsManager.cancelBuild( projectId );
+ buildsManager.cancelBuild( projectId );
+ }
AuditLog event = new AuditLog( "Project id=" + projectId, AuditLogConstants.CANCEL_BUILD );
event.setCategory( AuditLogConstants.PROJECT );
@@ -99,29 +106,44 @@ public class CancelBuildAction
projectsId = ArrayUtils.add( projectsId, projectId );
}
- BuildsManager parallelBuildsManager = getContinuum().getBuildsManager();
- parallelBuildsManager.removeProjectsFromBuildQueue( projectsId );
-
- try
+ if ( getContinuum().getConfiguration().isDistributedBuildEnabled() )
{
- // now we must check if the current build is one of this
- int index = ArrayUtils.indexOf( projectsId, getCurrentProjectIdBuilding() );
- if ( index > 0 )
- {
- int projId = projectsId[index];
- getContinuum().getBuildsManager().cancelBuild( projId );
-
- AuditLog event = new AuditLog( "Project id=" + projId, AuditLogConstants.CANCEL_BUILD );
+ for ( int i = 0; i < projectsId.length; i++ )
+ {
+ getContinuum().getDistributedBuildManager().cancelBuild( projectsId[i] );
+
+ AuditLog event = new AuditLog( "Project id=" + projectsId[i], AuditLogConstants.CANCEL_BUILD );
event.setCategory( AuditLogConstants.PROJECT );
event.setCurrentUser( getPrincipal() );
event.log();
}
-
}
- catch ( BuildManagerException e )
+ else
{
- logger.error( e.getMessage() );
- throw new ContinuumException( e.getMessage(), e );
+ BuildsManager parallelBuildsManager = getContinuum().getBuildsManager();
+ parallelBuildsManager.removeProjectsFromBuildQueue( projectsId );
+
+ try
+ {
+ // now we must check if the current build is one of this
+ int index = ArrayUtils.indexOf( projectsId, getCurrentProjectIdBuilding() );
+ if ( index > 0 )
+ {
+ int projId = projectsId[index];
+ getContinuum().getBuildsManager().cancelBuild( projId );
+
+ AuditLog event = new AuditLog( "Project id=" + projId, AuditLogConstants.CANCEL_BUILD );
+ event.setCategory( AuditLogConstants.PROJECT );
+ event.setCurrentUser( getPrincipal() );
+ event.log();
+ }
+
+ }
+ catch ( BuildManagerException e )
+ {
+ logger.error( e.getMessage() );
+ throw new ContinuumException( e.getMessage(), e );
+ }
}
return SUCCESS;
@@ -139,38 +161,52 @@ public class CancelBuildAction
return REQUIRES_AUTHORIZATION;
}
- BuildsManager buildsManager = getContinuum().getBuildsManager();
+ if ( getContinuum().getConfiguration().isDistributedBuildEnabled() )
+ {
+ getContinuum().getDistributedBuildManager().cancelGroupBuild( projectGroupId );
- List<ProjectScmRoot> scmRoots = getContinuum().getProjectScmRootByProjectGroup( projectGroupId );
+ AuditLog event = new AuditLog( "Project Group id=" + projectGroupId, AuditLogConstants.CANCEL_BUILD );
+ event.setCategory( AuditLogConstants.PROJECT_GROUP );
+ event.setCurrentUser( getPrincipal() );
+ event.log();
- if ( scmRoots != null )
+ return SUCCESS;
+ }
+ else
{
- for ( ProjectScmRoot scmRoot : scmRoots )
+ BuildsManager buildsManager = getContinuum().getBuildsManager();
+
+ List<ProjectScmRoot> scmRoots = getContinuum().getProjectScmRootByProjectGroup( projectGroupId );
+
+ if ( scmRoots != null )
{
- try
- {
- buildsManager.removeProjectGroupFromPrepareBuildQueue( projectGroupId,
- scmRoot.getScmRootAddress() );
- //taskQueueManager.removeFromPrepareBuildQueue( projectGroupId, scmRoot.getScmRootAddress() );
- }
- catch ( BuildManagerException e )
+ for ( ProjectScmRoot scmRoot : scmRoots )
{
- throw new ContinuumException( "Unable to cancel group build", e );
+ try
+ {
+ buildsManager.removeProjectGroupFromPrepareBuildQueue( projectGroupId,
+ scmRoot.getScmRootAddress() );
+ //taskQueueManager.removeFromPrepareBuildQueue( projectGroupId, scmRoot.getScmRootAddress() );
+ }
+ catch ( BuildManagerException e )
+ {
+ throw new ContinuumException( "Unable to cancel group build", e );
+ }
}
}
+ Collection<Project> projects = getContinuum().getProjectsInGroup( projectGroupId );
+
+ List<String> projectIds = new ArrayList<String>();
+
+ for ( Project project : projects )
+ {
+ projectIds.add( Integer.toString( project.getId() ) );
+ }
+
+ setSelectedProjects( projectIds );
+
+ return cancelBuilds();
}
- Collection<Project> projects = getContinuum().getProjectsInGroup( projectGroupId );
-
- List<String> projectIds = new ArrayList<String>();
-
- for ( Project project : projects )
- {
- projectIds.add( Integer.toString( project.getId() ) );
- }
-
- setSelectedProjects( projectIds );
-
- return cancelBuilds();
}
public void setProjectId( int projectId )