Michael Pasternak has uploaded a new change for review. Change subject: restapi: user cannnot take a vm from pool via restapi #864438 ......................................................................
restapi: user cannnot take a vm from pool via restapi #864438 This patch implements new action /allocatevm in vmpool [1] for user to take vm from the pool, also this patches introduces new generic and type independent resolver pattern callback on the action, in this case BE action on vmpool returns vm id which is later transformed to vm ref in api action response. [1] POST /api/vmpools/{vmpool:id}/allocatevm https://bugzilla.redhat.com/show_bug.cgi?id=864438 Change-Id: Icdd917a5f443bd7e8d06b582ae4559bef90608e6 Signed-off-by: Michael Pasternak <mpast...@redhat.com> --- M backend/manager/modules/restapi/interface/definition/src/main/java/org/ovirt/engine/api/resource/VmPoolResource.java M backend/manager/modules/restapi/interface/definition/src/main/resources/rsdl_metadata_v-3.1.yaml M backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/AbstractBackendActionableResource.java M backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/AbstractBackendResource.java M backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/BackendVmPoolResource.java A backend/manager/modules/restapi/jaxrs/src/test/java/org/ovirt/engine/api/restapi/resource/BackendVmPoolResourceTest.java 6 files changed, 369 insertions(+), 2 deletions(-) git pull ssh://gerrit.ovirt.org:29418/ovirt-engine refs/changes/88/8488/1 diff --git a/backend/manager/modules/restapi/interface/definition/src/main/java/org/ovirt/engine/api/resource/VmPoolResource.java b/backend/manager/modules/restapi/interface/definition/src/main/java/org/ovirt/engine/api/resource/VmPoolResource.java index 130d43a..e90ef66 100644 --- a/backend/manager/modules/restapi/interface/definition/src/main/java/org/ovirt/engine/api/resource/VmPoolResource.java +++ b/backend/manager/modules/restapi/interface/definition/src/main/java/org/ovirt/engine/api/resource/VmPoolResource.java @@ -16,13 +16,31 @@ package org.ovirt.engine.api.resource; +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; import javax.ws.rs.Path; +import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.core.Response; + +import org.jboss.resteasy.annotations.providers.jaxb.Formatted; +import org.ovirt.engine.api.model.Action; +import org.ovirt.engine.api.model.Actionable; import org.ovirt.engine.api.model.VmPool; @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, MediaType.APPLICATION_X_YAML}) -public interface VmPoolResource extends UpdatableResource<VmPool> { +public interface VmPoolResource extends UpdatableResource<VmPool>, AsynchronouslyCreatedResource { + + @Path("{action: (allocatevm)}/{oid}") + public ActionResource getActionSubresource(@PathParam("action")String action, @PathParam("oid")String oid); + + @POST + @Formatted + @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, MediaType.APPLICATION_X_YAML}) + @Actionable + @Path("allocatevm") + public Response allocatevm(Action action); @Path("permissions") public AssignedPermissionsResource getPermissionsResource(); diff --git a/backend/manager/modules/restapi/interface/definition/src/main/resources/rsdl_metadata_v-3.1.yaml b/backend/manager/modules/restapi/interface/definition/src/main/resources/rsdl_metadata_v-3.1.yaml index dfc4143..a6a9fd3 100644 --- a/backend/manager/modules/restapi/interface/definition/src/main/resources/rsdl_metadata_v-3.1.yaml +++ b/backend/manager/modules/restapi/interface/definition/src/main/resources/rsdl_metadata_v-3.1.yaml @@ -2435,6 +2435,17 @@ headers: Content-Type: {value: application/xml|json, required: true} Correlation-Id: {value: 'any string', required: false} +- name: /api/vmpools/{vmpool:id}/allocatevm|rel=allocatevm + request: + body: + parameterType: Action + signatures: + - mandatoryArguments: {} + optionalArguments: {action.async: 'xs:boolean'} + urlparams: {} + headers: + Content-Type: {value: application/xml|json, required: true} + Correlation-Id: {value: 'any string', required: false} - name: /api/vmpools|rel=add request: body: diff --git a/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/AbstractBackendActionableResource.java b/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/AbstractBackendActionableResource.java index 22b6cb2..7bd25a9 100644 --- a/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/AbstractBackendActionableResource.java +++ b/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/AbstractBackendActionableResource.java @@ -1,5 +1,6 @@ package org.ovirt.engine.api.restapi.resource; +import java.lang.reflect.Method; import java.net.URI; import javax.ws.rs.WebApplicationException; @@ -32,6 +33,39 @@ super(id, modelType, entityType, subCollections); } + protected Response doAction(final VdcActionType task, final VdcActionParametersBase params, final Action action, AbstractBackendResource.PollingType pollingType, EntityResolver entityResolver) { + awaitGrace(action); + try { + VdcReturnValueBase actionResult = doAction(task, params); + if (actionResult.getHasAsyncTasks()) { + if (expectBlocking(action)) { + Object model = resolveCreated(actionResult, entityResolver, null); + CreationStatus status = awaitCompletion(actionResult, pollingType); + return actionStatus(status, action, model); + } else { + return actionAsync(actionResult, action); + } + } else { + Object model = resolveCreated(actionResult, entityResolver, null); + return actionSuccess(action, model); + } + } catch (Exception e) { + return handleError(e, action); + } + } + + protected Object resolveCreated(VdcReturnValueBase result, EntityResolver entityResolver, Class<? extends BaseResource> suggestedParentType) { + try { + return entityResolver.resolve((Guid)result.getActionReturnValue()); + } catch (Exception e) { + // we tolerate a failure in the entity resolution + // as the substantive action (entity creation) has + // already succeeded + e.printStackTrace(); + return null; + } + } + protected Response doAction(final VdcActionType task, final VdcActionParametersBase params, final Action action, AbstractBackendResource.PollingType pollingType) { awaitGrace(action); try { @@ -49,6 +83,21 @@ } catch (Exception e) { return handleError(e, action); } + } + + /** + * Perform an action, managing asynchrony and returning an appropriate + * response with entity returned by backend in action body. + * + * @param uriInfo wraps the URI for the current request + * @param task the backend task + * @param params the task parameters + * @param action action representation + * @param entityResolver backend response resolver + * @return + */ + protected Response doAction(final VdcActionType task, final VdcActionParametersBase params, final Action action, EntityResolver entityResolver) { + return doAction(task, params, action, PollingType.VDSM_TASKS, entityResolver); } /** @@ -129,11 +178,40 @@ return Response.ok().entity(action).build(); } + private Response actionSuccess(Action action, Object result) { + setActionItem(action, result); + action.setStatus(StatusUtils.create(CreationStatus.COMPLETE)); + return Response.ok().entity(action).build(); + } + + private void setActionItem(Action action, Object result) { + String name = result.getClass().getSimpleName().toLowerCase(); + for (Method m : action.getClass().getMethods()) { + if (m.getName().startsWith("set") && m.getName().replace("set", "").toLowerCase().equals(name)) { + try { + m.invoke(action, result); + break; + } catch (Exception e) { + // should not happen + LOG.error("Resource to action asignment failure.", e); + e.printStackTrace(); + break; + } + } + } + } + protected Response actionStatus(CreationStatus status, Action action) { action.setStatus(StatusUtils.create(status)); return Response.ok().entity(action).build(); } + protected Response actionStatus(CreationStatus status, Action action, Object result) { + setActionItem(action, result); + action.setStatus(StatusUtils.create(status)); + return Response.ok().entity(action).build(); + } + protected Response actionAsync(VdcReturnValueBase actionResult, Action action) { action.setAsync(true); diff --git a/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/AbstractBackendResource.java b/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/AbstractBackendResource.java index 62457a6..b0f385e 100644 --- a/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/AbstractBackendResource.java +++ b/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/AbstractBackendResource.java @@ -373,6 +373,19 @@ } } + protected abstract class EntityResolver { + + public abstract Object lookupEntity(Guid id) throws BackendFailureException; + + public Object resolve(Guid id) throws BackendFailureException { + Object entity = lookupEntity(id); + if (entity == null) { + throw new EntityNotFoundException(id.toString()); + } + return entity; + } + } + protected class QueryIdResolver extends EntityIdResolver { private VdcQueryType query; diff --git a/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/BackendVmPoolResource.java b/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/BackendVmPoolResource.java index feb1d28..5e5ba5e 100644 --- a/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/BackendVmPoolResource.java +++ b/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/BackendVmPoolResource.java @@ -3,29 +3,39 @@ import java.util.List; +import javax.ws.rs.core.Response; + +import org.ovirt.engine.api.common.util.LinkHelper; +import org.ovirt.engine.api.model.Action; import org.ovirt.engine.api.model.VmPool; +import org.ovirt.engine.api.resource.ActionResource; import org.ovirt.engine.api.resource.AssignedPermissionsResource; +import org.ovirt.engine.api.resource.CreationResource; import org.ovirt.engine.api.resource.VmPoolResource; import org.ovirt.engine.core.common.VdcObjectType; import org.ovirt.engine.core.common.action.AddVmPoolWithVmsParameters; import org.ovirt.engine.core.common.action.VdcActionParametersBase; import org.ovirt.engine.core.common.action.VdcActionType; +import org.ovirt.engine.core.common.action.VmPoolUserParameters; import org.ovirt.engine.core.common.businessentities.DiskImage; import org.ovirt.engine.core.common.businessentities.VM; import org.ovirt.engine.core.common.businessentities.vm_pools; import org.ovirt.engine.core.common.businessentities.VmTemplate; import org.ovirt.engine.core.common.interfaces.SearchType; import org.ovirt.engine.core.common.queries.GetPermissionsForObjectParameters; +import org.ovirt.engine.core.common.queries.GetVmByVmIdParameters; import org.ovirt.engine.core.common.queries.GetVmPoolByIdParameters; import org.ovirt.engine.core.common.queries.GetVmTemplatesDisksParameters; import org.ovirt.engine.core.common.queries.GetVmTemplateParameters; +import org.ovirt.engine.core.common.queries.VdcQueryParametersBase; import org.ovirt.engine.core.common.queries.VdcQueryType; +import org.ovirt.engine.core.common.users.VdcUser; import org.ovirt.engine.core.compat.Guid; import static org.ovirt.engine.api.restapi.resource.BackendVmPoolsResource.SUB_COLLECTION; public class BackendVmPoolResource - extends AbstractBackendSubResource<VmPool, vm_pools> + extends AbstractBackendActionableResource<VmPool, vm_pools> implements VmPoolResource { private BackendVmPoolsResource parent; @@ -118,4 +128,47 @@ return parameters; } } + + @Override + public Response allocatevm(Action action) { + return doAction(VdcActionType.AttachUserToVmFromPoolAndRun, + new VmPoolUserParameters(guid, getCurrent().get(VdcUser.class), false), + action, + new VmQueryIdResolver(VdcQueryType.GetVmByVmId, + GetVmByVmIdParameters.class)); + + } + + protected class VmQueryIdResolver extends EntityResolver { + + private VdcQueryType query; + private Class<? extends VdcQueryParametersBase> queryParamsClass; + + public VmQueryIdResolver(VdcQueryType query, Class<? extends VdcQueryParametersBase> queryParamsClass) { + this.query = query; + this.queryParamsClass = queryParamsClass; + } + + public org.ovirt.engine.api.model.VM lookupEntity(Guid id) throws BackendFailureException { + VM vm = doGetEntity(VM.class, + query, getQueryParams(queryParamsClass, id), id.toString()); + org.ovirt.engine.api.model.VM model = new org.ovirt.engine.api.model.VM(); + model.setId(vm.getId().toString()); + return LinkHelper.addLinks(getUriInfo(), model); + } + } + + @Override + public CreationResource getCreationSubresource(String ids) { + return inject(new BackendCreationResource(ids)); + } + + @Override + public ActionResource getActionSubresource(String action, String ids) { + return inject(new BackendActionResource(action, ids)); + } + + public BackendVmPoolsResource getParent() { + return parent; + } } diff --git a/backend/manager/modules/restapi/jaxrs/src/test/java/org/ovirt/engine/api/restapi/resource/BackendVmPoolResourceTest.java b/backend/manager/modules/restapi/jaxrs/src/test/java/org/ovirt/engine/api/restapi/resource/BackendVmPoolResourceTest.java new file mode 100644 index 0000000..e10d2c1 --- /dev/null +++ b/backend/manager/modules/restapi/jaxrs/src/test/java/org/ovirt/engine/api/restapi/resource/BackendVmPoolResourceTest.java @@ -0,0 +1,194 @@ + +package org.ovirt.engine.api.restapi.resource; + +import static org.easymock.EasyMock.expect; +import java.util.ArrayList; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; + +import org.junit.Test; + +import org.ovirt.engine.api.model.Action; +import org.ovirt.engine.api.model.VmPool; +import org.ovirt.engine.core.common.action.VdcActionParametersBase; +import org.ovirt.engine.core.common.action.VdcActionType; +import org.ovirt.engine.core.common.action.VmPoolUserParameters; +import org.ovirt.engine.core.common.businessentities.AsyncTaskStatus; +import org.ovirt.engine.core.common.businessentities.VM; +import org.ovirt.engine.core.common.businessentities.VmPoolType; +import org.ovirt.engine.core.common.businessentities.vm_pools; +import org.ovirt.engine.core.common.queries.GetVmByVmIdParameters; +import org.ovirt.engine.core.common.queries.GetVmPoolByIdParameters; +import org.ovirt.engine.core.common.queries.VdcQueryType; +import org.ovirt.engine.core.common.users.VdcUser; +import org.ovirt.engine.core.compat.Guid; + +public class BackendVmPoolResourceTest + extends AbstractBackendSubResourceTest<VmPool, vm_pools, BackendVmPoolResource> { + + public BackendVmPoolResourceTest() { + super(new BackendVmPoolResource(GUIDS[0].toString(), new BackendVmPoolsResource())); + } + + @Override + protected void init() { + super.init(); + resource.getParent().backend = backend; + resource.getParent().sessionHelper = sessionHelper; + resource.getParent().mappingLocator = resource.mappingLocator; + resource.getParent().httpHeaders = httpHeaders; + } + + @Test + public void testBadGuid() throws Exception { + control.replay(); + try { + new BackendVmPoolResource("foo", new BackendVmPoolsResource()); + fail("expected WebApplicationException"); + } catch (WebApplicationException wae) { + verifyNotFoundException(wae); + } + } + + @Test + public void testGetNotFound() throws Exception { + setUriInfo(setUpBasicUriExpectations()); + setUpGetEntityExpectations(1, true); + control.replay(); + try { + resource.get(); + fail("expected WebApplicationException"); + } catch (WebApplicationException wae) { + verifyNotFoundException(wae); + } + } + + @Test + public void testGet() throws Exception { + setUriInfo(setUpBasicUriExpectations()); + setUpGetEntityExpectations(1); + control.replay(); + verifyModel(resource.get(), 0); + } + + @Test + public void testAllocateVm() throws Exception { + setUpGetVmExpectations(1); + setUpGetUserExpectations(); + setUriInfo(setUpActionExpectations(VdcActionType.AttachUserToVmFromPoolAndRun, + VmPoolUserParameters.class, + new String[] { "VmPoolId", "IsInternal" }, + new Object[] { GUIDS[0], Boolean.FALSE }, + GUIDS[0])); + + verifyTestAllocateVmActionResponse(resource.allocatevm(new Action())); + } + + + private void setUpGetUserExpectations() { + VdcUser user = new VdcUser(); + user.setUserId(GUIDS[0]); + expect(sessionHelper.getCurrent().get(VdcUser.class)).andReturn(user).anyTimes(); + } + + private void setUpGetVmExpectations(int times) throws Exception { + while (times-- > 0) { + setUpGetEntityExpectations(VdcQueryType.GetVmByVmId, + GetVmByVmIdParameters.class, + new String[] { "Id" }, + new Object[] { GUIDS[0] }, + getVmEntity()); + } + } + + private VM getVmEntity() { + return getVmEntity(0); + } + + protected VM getVmEntity(int index) { + return setUpVmEntityExpectations( + control.createMock(VM.class), + index); + } + + private VM setUpVmEntityExpectations(VM entity, int index) { + expect(entity.getId()).andReturn(GUIDS[index]).anyTimes(); + + return entity; + } + + protected void setUpGetEntityExpectations(int times) throws Exception { + setUpGetEntityExpectations(times, false); + } + + protected void setUpGetEntityExpectations(int times, boolean notFound) throws Exception { + setUpGetEntityExpectations(times, notFound, getEntity(0)); + } + + protected void setUpGetEntityExpectations(int times, boolean notFound, org.ovirt.engine.core.common.businessentities.vm_pools entity) throws Exception { + + while (times-- > 0) { + setUpGetEntityExpectations(VdcQueryType.GetVmPoolById, + GetVmPoolByIdParameters.class, + new String[] { "PoolId" }, + new Object[] { GUIDS[0] }, + notFound ? null : entity); + } + } + + protected UriInfo setUpActionExpectations(VdcActionType task, + Class<? extends VdcActionParametersBase> clz, + String[] names, + Object[] values, + Object taskReturn) { + return setUpActionExpectations(task, clz, names, values, true, true, taskReturn, null, true); + } + + protected UriInfo setUpActionExpectations(VdcActionType task, + Class<? extends VdcActionParametersBase> clz, + String[] names, + Object[] values, + ArrayList<Guid> asyncTasks, + ArrayList<AsyncTaskStatus> asyncStatuses) { + String uri = "vmpools/" + GUIDS[0] + "/action"; + return setUpActionExpectations(task, clz, names, values, true, true, null, asyncTasks, asyncStatuses, null, null, uri, true); + } + + private void verifyTestAllocateVmActionResponse(Response r) throws Exception { + assertNotNull(r.getEntity()); + assertNotNull(((org.ovirt.engine.api.model.Action)r.getEntity()).getVm()); + assertNotNull(((org.ovirt.engine.api.model.Action)r.getEntity()).getVm().getId()); + assertEquals((((org.ovirt.engine.api.model.Action)r.getEntity()).getVm()).getId(), GUIDS[0].toString()); + + verifyActionResponse(r, "vmpools/" + GUIDS[0], false); + } + + protected void verifyModel(VmPool model, int index) { + super.verifyModel(model, index); + verifyModelSpecific(model, index); + } + + static void verifyModelSpecific(VmPool model, int index) { + assertNotNull(model.getCluster()); + assertNotNull(model.getCluster().getId()); + } + + @Override + protected vm_pools getEntity(int index) { + return setUpEntityExpectations( + control.createMock(vm_pools.class), + index); + } + + private vm_pools setUpEntityExpectations(vm_pools entity, int index) { + expect(entity.getvm_pool_id()).andReturn(GUIDS[index]).anyTimes(); + expect(entity.getvds_group_id()).andReturn(GUIDS[2]).anyTimes(); + expect(entity.getvm_pool_name()).andReturn(NAMES[index]).anyTimes(); + expect(entity.getvm_pool_type()).andReturn(VmPoolType.Automatic).anyTimes(); + expect(entity.getvm_pool_description()).andReturn(DESCRIPTIONS[index]).anyTimes(); + + return entity; + } +} -- To view, visit http://gerrit.ovirt.org/8488 To unsubscribe, visit http://gerrit.ovirt.org/settings Gerrit-MessageType: newchange Gerrit-Change-Id: Icdd917a5f443bd7e8d06b582ae4559bef90608e6 Gerrit-PatchSet: 1 Gerrit-Project: ovirt-engine Gerrit-Branch: master Gerrit-Owner: Michael Pasternak <mpast...@redhat.com> _______________________________________________ Engine-patches mailing list Engine-patches@ovirt.org http://lists.ovirt.org/mailman/listinfo/engine-patches