Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@

package org.apache.cloudstack.response;

import java.text.DecimalFormat;
import java.text.ParseException;

import org.apache.cloudstack.api.response.HostResponse;
import org.apache.cloudstack.outofbandmanagement.OutOfBandManagement;

import com.cloud.serializer.Param;
import com.cloud.utils.exception.CloudRuntimeException;
import com.google.gson.annotations.SerializedName;

public class HostMetricsResponse extends HostResponse {
Expand Down Expand Up @@ -118,7 +122,7 @@ public void setCpuTotal(final Integer cpuNumber, final Long cpuSpeed) {

public void setCpuUsed(final String cpuUsed, final Integer cpuNumber, final Long cpuSpeed) {
if (cpuUsed != null && cpuNumber != null && cpuSpeed != null) {
this.cpuUsed = String.format("%.2f Ghz", Double.valueOf(cpuUsed.replace("%", "")) * cpuNumber * cpuSpeed / (100.0 * 1000.0));
this.cpuUsed = String.format("%.2f Ghz", parseCPU(cpuUsed) * cpuNumber * cpuSpeed / (100.0 * 1000.0));
}
}

Expand All @@ -130,10 +134,14 @@ public void setLoadAverage(final Double loadAverage) {

public void setCpuAllocated(final String cpuAllocated, final Integer cpuNumber, final Long cpuSpeed) {
if (cpuAllocated != null && cpuNumber != null && cpuSpeed != null) {
this.cpuAllocated = String.format("%.2f Ghz", Double.valueOf(cpuAllocated.replace("%", "")) * cpuNumber * cpuSpeed / (100.0 * 1000.0));
this.cpuAllocated = String.format("%.2f Ghz", parseCPU(cpuAllocated) * cpuNumber * cpuSpeed / (100.0 * 1000.0));
}
}

public String getCpuAllocatedGhz() {
return cpuAllocated;
}

public void setMemTotal(final Long memTotal) {
if (memTotal != null) {
this.memTotal = String.format("%.2f GB", memTotal / (1024.0 * 1024.0 * 1024.0));
Expand Down Expand Up @@ -166,25 +174,25 @@ public void setNetworkWrite(final Long networkWriteKbs) {

public void setCpuUsageThreshold(final String cpuUsed, final Double threshold) {
if (cpuUsed != null && threshold != null) {
this.cpuThresholdExceeded = Double.valueOf(cpuUsed.replace("%", "")) > (100.0 * threshold);
this.cpuThresholdExceeded = parseCPU(cpuUsed) > (100.0 * threshold);
}
}

public void setCpuUsageDisableThreshold(final String cpuUsed, final Float threshold) {
if (cpuUsed != null && threshold != null) {
this.cpuDisableThresholdExceeded = Double.valueOf(cpuUsed.replace("%", "")) > (100.0 * threshold);
this.cpuDisableThresholdExceeded = parseCPU(cpuUsed) > (100.0 * threshold);
}
}

public void setCpuAllocatedThreshold(final String cpuAllocated, final Double threshold) {
if (cpuAllocated != null && threshold != null) {
this.cpuAllocatedThresholdExceeded = Double.valueOf(cpuAllocated.replace("%", "")) > (100.0 * threshold );
this.cpuAllocatedThresholdExceeded = parseCPU(cpuAllocated) > (100.0 * threshold );
}
}

public void setCpuAllocatedDisableThreshold(final String cpuAllocated, final Float threshold) {
if (cpuAllocated != null && threshold != null) {
this.cpuAllocatedDisableThresholdExceeded = Double.valueOf(cpuAllocated.replace("%", "")) > (100.0 * threshold);
this.cpuAllocatedDisableThresholdExceeded = parseCPU(cpuAllocated) > (100.0 * threshold);
}
}

Expand Down Expand Up @@ -212,4 +220,13 @@ public void setMemoryAllocatedDisableThreshold(final Long memAllocated, final Lo
}
}

private Double parseCPU(String cpu) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add some unit tests for it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added test

DecimalFormat decimalFormat = new DecimalFormat("#.##");
try {
return decimalFormat.parse(cpu).doubleValue();
} catch (ParseException e) {
throw new CloudRuntimeException(e);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about logging the exception and handling the exception? Either here or in the usage of this function. For example, the character in the PR description can cause the API to still fail:

"cpuallocated": "∞%",
"cpuallocatedpercentage": "∞%",
"cpuallocatedvalue": 500,
"cpuallocatedwithoverprovisioning": "∞%",

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nvazquez as the ParseException is thrown as CloudRuntimeException, it will get logged:

INFO  [c.c.a.ApiServer] (qtp357350214-317:ctx-828b25d5) (logid:77b09f95) Timezone offset from UTC is: 0.0
ERROR [c.c.a.ApiServer] (qtp357350214-32:ctx-f4aa35d7 ctx-d7301148) (logid:b4fa39eb) unhandled exception executing api command: [Ljava.lang.String;@1a6da41c
com.cloud.utils.exception.CloudRuntimeException: Unparseable number: "abc"
	at org.apache.cloudstack.response.HostMetricsResponse.parseCPU(HostMetricsResponse.java:228)
	at org.apache.cloudstack.response.HostMetricsResponse.setCpuUsed(HostMetricsResponse.java:125)
	at org.apache.cloudstack.metrics.MetricsServiceImpl.listHostMetrics(MetricsServiceImpl.java:284)
	at org.apache.cloudstack.api.ListHostsMetricsCmd.execute(ListHostsMetricsCmd.java:50)
	at com.cloud.api.ApiDispatcher.dispatch(ApiDispatcher.java:156)
	at com.cloud.api.ApiServer.queueCommand(ApiServer.java:764)
	at com.cloud.api.ApiServer.handleRequest(ApiServer.java:588)
	at com.cloud.api.ApiServlet.processRequestInContext(ApiServlet.java:321)
	at com.cloud.api.ApiServlet$1.run(ApiServlet.java:134)
	at org.apache.cloudstack.managed.context.impl.DefaultManagedContext$1.call(DefaultManagedContext.java:55)
	at org.apache.cloudstack.managed.context.impl.DefaultManagedContext.callWithContext(DefaultManagedContext.java:102)
	at org.apache.cloudstack.managed.context.impl.DefaultManagedContext.runWithContext(DefaultManagedContext.java:52)
	at com.cloud.api.ApiServlet.processRequest(ApiServlet.java:131)
	at com.cloud.api.ApiServlet.doGet(ApiServlet.java:93)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:687)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
	at org.eclipse.jetty.servlet.ServletHolder$NotAsyncServlet.service(ServletHolder.java:1386)
	at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:755)
	at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1617)
	at org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter.doFilter(WebSocketUpgradeFilter.java:226)
	at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1604)
	at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:545)
	at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
	at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:590)
	at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)
	at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:235)
	at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1610)
	at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:233)
	at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1300)
	at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:188)
	at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:485)
	at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1580)
	at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:186)
	at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1215)
	at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
	at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:221)
	at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:146)
	at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127)
	at org.eclipse.jetty.server.Server.handle(Server.java:500)
	at org.eclipse.jetty.server.HttpChannel.lambda$handle$1(HttpChannel.java:383)
	at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:547)
	at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:375)
	at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:273)
	at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:311)
	at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:103)
	at org.eclipse.jetty.io.ChannelEndPoint$2.run(ChannelEndPoint.java:117)
	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.runTask(EatWhatYouKill.java:336)
	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:313)
	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:171)
	at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.produce(EatWhatYouKill.java:135)
	at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:806)
	at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:938)
	at java.base/java.lang.Thread.run(Thread.java:829)
Caused by: java.text.ParseException: Unparseable number: "abc"
	at java.base/java.text.NumberFormat.parse(NumberFormat.java:431)
	at org.apache.cloudstack.response.HostMetricsResponse.parseCPU(HostMetricsResponse.java:226)
	at org.apache.cloudstack.response.HostMetricsResponse.setCpuUsed(HostMetricsResponse.java:125)
	... 51 more

And API will fail in this case:

(local) 🐝 > list hostsmetrics  id=fc75a8b3-263d-4231-8635-45715edea15a
πŸ™ˆ Error: (HTTP 530, error code 0) Unparseable number: "abc"

PR description shows API output. With the change (similar to main branch), listHostsMetrics API won't fail when there is 0 cpu for host or locale issue and it will just report ∞% or NaN Ghz as values.
Change for not showing these invalid values can considered in a separate PR.

}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.apache.cloudstack.response;

import org.junit.Assert;
import org.junit.Test;

import com.cloud.utils.exception.CloudRuntimeException;

public class HostMetricsResponseTest {

@Test
public void testSetCpuAllocatedWithZeroCpu() {
final HostMetricsResponse hostResponse = new HostMetricsResponse();
hostResponse.setCpuAllocated("50.25%", 0, 1000L);
Assert.assertEquals("0.00 Ghz", hostResponse.getCpuAllocatedGhz());
}

@Test
public void testSetCpuAllocatedWithInfiniteCpuAllocated() {
final HostMetricsResponse hostResponse = new HostMetricsResponse();
hostResponse.setCpuAllocated("∞%", 10, 1000L);
Assert.assertEquals("Infinity Ghz", hostResponse.getCpuAllocatedGhz());
}

@Test(expected = CloudRuntimeException.class)
public void testSetCpuAllocatedWithInvalidCpu() {
final HostMetricsResponse hostResponse = new HostMetricsResponse();
hostResponse.setCpuAllocated("abc", 10, 1000L);
}

@Test
public void testSetCpuAllocatedWithValidCpu() {
final HostMetricsResponse hostResponse = new HostMetricsResponse();
hostResponse.setCpuAllocated("50.25%", 10, 1000L);
Assert.assertEquals("5.03 Ghz", hostResponse.getCpuAllocatedGhz());
}

}