Get list of all cloud agents

Answered

I am writing a plugin and would like users to be able to select an agent to run there build on. I am currently using:
<jsp:useBean id="serverTC" type="jetbrains.buildServer.serverSide.SBuildServer" scope="request"/>

...
<props:selectProperty name="${settings.buildAgentPropertyName}" id="${settings.buildAgentPropertyName}"
multiple="false">
<props:option value="{null}"><c:out value="<Any agent>"/></props:option>
<props:option value="${settings.upstreamBuildAgentMagicString}"><c:out value="<Same as upstream build>"/></props:option>
<c:forEach var="buildAgent" items="${serverTC.buildAgentManager.registeredAgents}">
<props:option value="${buildAgent.name}"><c:out
value="${buildAgent.name} > ${buildAgent.hostName}"/></props:option>
</c:forEach>
<c:forEach var="buildAgent" items="${serverTC.buildAgentManager.unregisteredAgents}">
<props:option value="${buildAgent.name}"><c:out
value="${buildAgent.name} > ${buildAgent.hostName}"/></props:option>
</c:forEach>
</props:selectProperty>
...

In my JSP. This works, but only returns the physical agents, not the cloud agents. I have looked through the javadoc and cannot see a why to get access to the list of cloud agents, is there something I'm missing? When are they not returned from BuildAgentManager.getRegisteredAgents (or getUnregisteredAgents)?

Thanks,
Tim

20 comments

Hi Tim,

(answer copied from email support)
This actually returns connected agents, both cloud and non-cloud ones.
Every cloud image produces its own AgentType object. And all cloud instances started from this images share the same AgentType.
As far as I understand, you want to show all VM sources from vmware plugin, one record per source. Actually, this issue is already addressed in https://youtrack.jetbrains.com/issue/TW-10349.
Please give it a try (you need to download an EAP) and let me know if it fits your needs or not.

0

Hi Alina,

I have installed the TeamCity 10 EAP, but I can't find the relevant jar files in the repository, or the javadoc for TeamCity 10. As such it is hard to figure out what I need to do to get a list of Agent Images from a Cloud Image.

Note: This would be similar to the list provided in Administration > Server Administration > Agent Cloud > [My cloud image] > Agent Images.

Cheers,
Tim

 

0

Hi Tim, the jars you need are cloud-server.jar, cloud-interface.jar and cloud-shared.jar

To get the list of agents from Cloud image you need first to get the list of instances (jetbrains.buildServer.clouds.CloudImage#getInstances), then for every cloud instance you need to find a corresponding BuildAgent instance: jetbrains.buildServer.clouds.server.instances.RunningAgentsTracker#findAgentByInstance

0

Hi Sergey,

That's really helpful, thanks!

I am still slightly blocked because I cnnot seem to get the jar files from the maven repo. When I go to:

http://repository.jetbrains.com/all/org/jetbrains/
there is no teamcity section anymore.

Not sure if I'm doing something wrong?

I can start a new thread if it helps?

Cheers,

Tim

 

0

Hi Sergey,

Thanks for the updated location. I'm not able to get the cloud-shared and cloud-interface jars (cloud-server isn't available from that site).

I am finding it difficult to get a handle to the VMwareCloudImage, not sure if this is in cloud-serer. is there a method to get a list of CloudImages?

Cheers,

Tim

0

Tim, cloud-server.jar is a closed API, which means we make changes to it much more frequently than in open-api, including breaking changes.

VMwareCloudImage is a part of Vmware plugin, which is available (so far) from https://github.com/JetBrains/teamcity-vmware-plugin.

To get the list of cloud images you need to get the CloudClient for example by calling jetbrains.buildServer.clouds.server.CloudManager#getClient, then call jetbrains.buildServer.clouds.CloudClient#getImages to get the list of its images.

0

Hi Sergey,

I couldn't see how to get an instance of cloud manager (it's abstract and there is not VMWareCloudManager in the cloud-wmware-server.jar from VMware plugin).
As this is delving into closed APIs I'm beginning to wondering if it would be best for me to request two feature to be added to the public API:

1 - A method to list all CloudImages for every CloudClient (all 'Agent image's added in each 'Agent Cloud Profile' in Administration -> AgentCloud).

2 - The ability to trigger a build with a cloudInstance and have that build added to the queue.*, like:
      buildCustomizer.createPromotion().addToQueue(agent, triggeredBy);
but:
      buildCustomizer.createPromotion().addToQueue(cloudImage, triggeredBy);

*The closest I have is tying a configuration and VM Image together using  system.agent.name in the Agent Requirements. However this means I need a job configuration foreach VM Image (we currently have 24) which is too much maintenance when paramenters change between releases.

That would give me the functionality I want/need (being able to trigger a build to run on a CloudImage) without needing to interact with the closed API.

Just wondered if TC would be likely to add these features if requested?
If not I can continue looking into the closed APIs.
Cheers,

Tim

 

0

Tim, CloudManager is a core interface and is a spring bean. You can reference it in your beans.

Basically, #1 you are asking for can be done using:

cloudManager.listProfiles().stream().map(p->cloudManager.getClient(p))...

#2 is currently (10.0) implemented in a different way addToQueue(agent, triggeredBy) is just a helper. Underlying call is anyway BuildTypeEx#addToQueue(jetbrains.buildServer.serverSide.SAgentRestrictor, jetbrains.buildServer.serverSide.BuildPromotionEx, java.lang.String) and the SAgentRestrictor is the way to restrict the build from running on certain agents or agent types.

Actually, what you are looking for is very close to how it is implemented in 10.0. Please consider looking at EAP.

0

Hi Sergey,
I have TC10 EAP2 installed on my Dev machine. I am happy to write this for TC10 if it will be easier? We are planning to upgrade to 10 when it is released.
I will have a look when I get back to work, currently on holiday until next week.
Thanks for your help,
Tim

0

In TC10 you can choose the cloud image to run the build on. It can be either existing instance or a new one. Here's how it looks like in run custom build dialog:

0

Hi Sergey,
I looked the specify cloud agent feature you mentioned above. It doesn't look like it will trigger a build against a non-running cloud instance (I get no [Cloud] entries even though I have some added to vmware). It also will only allow me to launch one job at a time. What I'm looking for is the ability launch multiple instances of the same job on different Cloud Instances.

I looked at well using CloudManager as a bean but couldn't figure out how to lod it into my my jsp file. I tried:
<jsp:useBean id="cloudManager" class="jetbrains.buildServer.clouds.server.CloudManager" scope="request"/>
This resulted in an exception caused by:
Caused by: org.apache.jasper.JasperException: /plugins/parameterizedBuildTrigger/editParameterizedTriggerParams.jsp (line: 7, column: 0) The value for the useBean class attribute jetbrains.buildServer.clouds.server.CloudManager is invalid.

I also tried adding:
<bean id="cloudManager" class="jetbrains.buildServer.clouds.server.CloudManager"/>
To the beans defined in build-server-plugin-....xml
But this didn't work as CloudManager doesn't have a default (no-arg) constructor.

Do have an an example of defining CloudManager as a bean in jsp you could share?

Thanks,
Tim

 

 

0

Hi Sergey,

Thanks for your pointers. Firstly I have used 'SecuredCloudManager' when autowiring as the 'CloudManagerFacade' is not an autowire candidate:
<bean class="jetbrains.buildServer.clouds.server.impl.CloudManagerFacade" name="internalCloudManagerFacade" autowire-candidate="false"/>

*edited*

I have created a wrapper as follows:
public class CloudManagerProvider {
    private static final Logger logger =
            Logger.getLogger(Loggers.SERVER_CATEGORY + CloudManagerProvider.class);
    @NotNull
    private SecuredCloudManager cloudManager;

    public void setCloudManager(SecuredCloudManager cloudManager)
    {
        this.cloudManager = cloudManager;
    }
    @NotNull
    public Collection<CloudImage> getCloudImages()
    {
        ArrayList<CloudImage> cloudImages = new ArrayList<CloudImage>();
        for (CloudProfile profile : cloudManager.listProfiles())
        {
            CloudClient client = cloudManager.getClient(profile.getProfileId());
            cloudImages.addAll(client.getImages());
        }
        return cloudImages;
    }

}

With bean definition:

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns="http://www.springframework.org/schema/beans"
             xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd"
             default-autowire="constructor">
        <bean id="mysecuredCloudManager" class="jetbrains.buildServer.clouds.server.impl.SecuredCloudManager" autowire-candidate="false">
            <constructor-arg ref="internalCloudManagerFacade" index="1"/>
        </bean>
        <bean id="cloudManagerProvider" class="com.snsystems.teamcity.plugins.parameterizedBuildTrigger.CloudManagerProvider" scope="request"

        <property name="cloudManager">
                <ref bean="mysecuredCloudManager"/>
            </property>
        </bean>

</beans>

Which I consume using:
<jsp:useBean id="cloudManagerProvider" class="com.snsystems.teamcity.plugins.parameterizedBuildTrigger.CloudManagerProvider" scope="request"/>
...
..
                <props:selectProperty name="${settings.buildAgentPropertyName}" id="${settings.buildAgentPropertyName}"
                                      multiple="false">
                    <c:forEach var="cloudImage" items="${cloudManagerProvider.cloudImages}">
                        <props:option value="${cloudImage.name}"><c:out
                                value="[Cloud] ${cloudImage.name}"/></props:option>
                    </c:forEach>
                </props:selectProperty>

The issue I'm having is the bean is being created, but the jsp cannot access it (using type). If I use class (as above) then I get an instance of the bean, but its a new instance, so the cloudManager property isn't set. is there a way to allow the JSP file to access the bean I defined in my 'build-server-plugin-parameterizedBuildTrigger.xml' file?

Sorry for constantly asking for help. I think once I've got the list I should be sorted.

Thanks,
Tim

0

Hi Again,

I have a look at the AgentRestrictor as well and not sure that is useful to me (unless I'm doing something wrong). For an agent it's fine:

AgentRestrictorFactory agentRestrictorFactory = new AgentRestrictorFactoryImpl();

SBuildAgent agent = getBuildAgent(agentName);
return agentRestrictorFactory.createFor(AgentRestrictorType.SINGLE_AGENT, agent.getId());

But when I come to use it for a CloudImage:

CloudImage image = getCloudImage(agentName);

return agentRestrictorFactory .createFor(AgentRestrictorType.CLOUD_IMAGE, image.getId());  <-- error here

The problem is that CloudImages (and CloudInstances) use Strings for their IDs, and agentRestrictorFactory .createFor expects the ID to be an int.

Cheers,
Tim

 

0

Hi, answering to your questions:

1) Concerning beans:

Jsp cannot autowire spring beans it will always create a new instance if you use bean by class. You need to use have a controller and put the bean into request properties. Also, it's usually a bad practice to use bean inside jsp directly, because it can impact the rendering. It's recommended to do all necessary calculations in the controller and put the result (java bean)  to the model, so you can use them in jsp from request scope.

2) Concerning restrictor for cloud image:

a) It's better to use restrictor that implements both AgentRestrictor and jetbrains.buildServer.serverSide.agentTypes.AgentTypeRestrictor (introduced in 10.0)

b) to get the agent type id from CloudImage you need to use jetbrains.buildServer.serverSide.agentTypes.AgentTypeKey object

So, you need the cloud code, cloud profile id and cloud image id. Cloud agents contain PROFILE_ID in the parameters.

Then you can call jetbrains.buildServer.serverSide.agentTypes.AgentTypeStorage#getOrCreateAgentTypeId or jetbrains.buildServer.serverSide.agentTypes.AgentTypeStorage#findAgentTypeByKey to get the AgentType (or agentTypeId).

You can autowire AgentTypeStorage, it's a bean.

0

Hi Sergey,

I found this was difficult to test, with mocks, as one of the classes used required a class that wasn't in my classpath. When I removed the test the implementation i had didn't load up the VM Images. I looked at my current workaround and I realised I could do what I wanted with dynamically adding a requirement before the build runs:


Requirement agentNameRequirement;
String agentName = context.getTriggerDescriptor().getProperties().get(constants.getBuildAgentPropertyName());
if(agentName != null && !agentName.isEmpty() )
{
    if( agentName.equals(constants.getUpstreamBuildAgentMagicString()))
    {
        agentName = getLastFinishedInWatchedConfig(context).getAgent().getName();
        logger.debug("Using same agent as upstream build - "+agentName+".");
    }
    agentNameRequirement  = requirementFactory.createRequirement(constants.getBuildAgentRequirementProperty(), agentName, RequirementType.CONTAINS); // requirementFacotry is an autowired bean of type RequirementFactoryImpl
}
if (agentNameRequirement != null)
{
    logger.debug("Adding requirement " + requirement.getPropertyName() + " " + requirement.getType().toString() + " " + requirement.getPropertyValue());
    buildTypeToTrigger.addRequirement(requirement);
}
long nextBuildNumber = buildTypeToTrigger.getBuildNumbers().getBuildCounter() +1;


String triggeredBy = buildTypeToTrigger.getFullName() + ", #" + Long.toString(nextBuildNumber) +
        " (est.). Triggered by a " + constants.getTriggerDisplayName() + ".";

BuildCustomizer buildCustomizer = buildCustomizerFactory.createBuildCustomizer(buildTypeToTrigger, null);

logger.debug("triggering Build");
buildCustomizer.createPromotion().addToQueue(triggeredBy);

 

This adds a build to the queue with the AgentName as a requirement (on "system.agent.name"). This launches VM agents when required.

Thanks for your help, I think i would have struggled a lot more without it :).
Please let me know if you see potential issues with this solution.

Thanks,
Tim

0

actually, I doubt that it will work, because "system.agent.name" is unknown before the started agent actually comes to server and "system.agent.name" for the cloud image is actually the name of the last agent from that image that came to server.

Basically, to start a new cloud instance using just the requirements you need to do add the requirement that none of the existing agents meet, but the cloud image does. Not sure, if this is possible, because we copy parameters from the arrived agent to cloud image, so they should be similar.

0

Ok, yeah it was a bad idea. Adding the requirements dynamically meant that after a while build configs filled up with requirements.

I have re-implmented the AgentRestrictor to the point where I was having issues mocking.

The problem I have is I cannot mock BuildTypeEx because I get:
java.lang.NoClassDefFoundError: jetbrains/buildServer/serverSide/identifiers/EntityId

Do you know which jar provides jetbrains/buildServer/serverSide/identifiers/EntityId?

Without it I am struggling to write UnitTests for the Cloud specific code.

Cheers,
Tim

0

Hi Sergey,

I have implemented the job triggering on specified cloud images with agent Restrictors. Looking over the code the issue I was having with jobs not triggering was not to do with the agent restrictors.

I also managed to work out how to pass an array of CloudImages to the jsp page. I think the problem I was having before was that I registered the controller to the .jsp page location, not to the .html location (as I don't have an html page), which meant the variable wasn't available in the jsp page. When I registered to the html page it the cloudClients variable was available.

Thanks you all you help on this!

Tim

To anyone interested in the code to provide the list in JSP (the reason for the post):

public class ParameterizedTriggerWebController extends BaseController {
protected final WebControllerManager controllerManager;
protected final PluginDescriptor pluginDescriptor;
protected final SecuredCloudManager cloudManager;
protected ParameterizedBuildTriggerConstants _constants;

public ParameterizedTriggerWebController(SBuildServer server,
WebControllerManager controllerManager,
final PluginDescriptor pluginDescriptor,
final SecuredCloudManager cloudManager) {
super(server);
this.controllerManager = controllerManager;
this.pluginDescriptor = pluginDescriptor;
this.cloudManager = cloudManager;
controllerManager.registerController(pluginDescriptor.getPluginResourcesPath(getConstants().getEditParametersPage()), this);
}

ParameterizedBuildTriggerConstants getConstants()
{
if (_constants == null){
_constants = new ParameterizedBuildTriggerConstants();
}
return _constants;
}

protected ArrayList<CloudClient> getClientList()
{
ArrayList<CloudClient> clientList = new ArrayList<CloudClient>();
for(CloudProfile profile : cloudManager.listProfiles()) {
clientList.add(cloudManager.getClient(profile.getProfileId()));
}
return clientList;
}

@Override
protected ModelAndView doHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
ModelAndView mv = new ModelAndView(pluginDescriptor.getPluginResourcesPath(getConstants().getEditParametersScript()));
mv.getModel().put("cloudClients", getClientList());
return mv;
}
}


Then use the cloudClients passed but the Web Controller in the jsp as a bean:

<jsp:useBean id="cloudClients" type="java.util.List<jetbrains.buildServer.clouds.CloudClient>" scope="request"/>
<c:forEach var="cloudClient" items="${cloudClients}">
<c:forEach var="cloudImage" items="${cloudClient.images}">
<props:option value="${cloudImage.name}"><c:out
value="${cloudImage.name}"/></props:option>
</c:forEach>
</c:forEach>

 

0

Please sign in to leave a comment.