diff --git a/api/src/main/java/com/cloud/event/EventTypes.java b/api/src/main/java/com/cloud/event/EventTypes.java index 5947334016c..72e623f139a 100644 --- a/api/src/main/java/com/cloud/event/EventTypes.java +++ b/api/src/main/java/com/cloud/event/EventTypes.java @@ -493,6 +493,10 @@ public class EventTypes { public static final String EVENT_TAGS_CREATE = "CREATE_TAGS"; public static final String EVENT_TAGS_DELETE = "DELETE_TAGS"; + // resource icon related events + public static final String EVENT_RESOURCE_ICON_UPLOAD = "UPLOAD.RESOURCE.ICON"; + public static final String EVENT_RESOURCE_ICON_DELETE = "DELETE.RESOURCE.ICON"; + // meta data related events public static final String EVENT_RESOURCE_DETAILS_CREATE = "CREATE_RESOURCE_DETAILS"; public static final String EVENT_RESOURCE_DETAILS_DELETE = "DELETE_RESOURCE_DETAILS"; diff --git a/api/src/main/java/com/cloud/server/ResourceIcon.java b/api/src/main/java/com/cloud/server/ResourceIcon.java new file mode 100644 index 00000000000..a5ac3ee6519 --- /dev/null +++ b/api/src/main/java/com/cloud/server/ResourceIcon.java @@ -0,0 +1,32 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.server; + +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + +public interface ResourceIcon extends Identity, InternalIdentity { + long getResourceId(); + + void setResourceId(long resourceId); + + ResourceTag.ResourceObjectType getResourceType(); + + String getResourceUuid(); + + String getIcon(); +} diff --git a/api/src/main/java/com/cloud/server/ResourceIconManager.java b/api/src/main/java/com/cloud/server/ResourceIconManager.java new file mode 100644 index 00000000000..e5111d9160b --- /dev/null +++ b/api/src/main/java/com/cloud/server/ResourceIconManager.java @@ -0,0 +1,28 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.server; + +import java.util.List; + +public interface ResourceIconManager { + + boolean uploadResourceIcon(List resourceIds, ResourceTag.ResourceObjectType resourceType, String base64Image); + + boolean deleteResourceIcon(List resourceIds, ResourceTag.ResourceObjectType resourceType); + + ResourceIcon getByResourceTypeAndUuid(ResourceTag.ResourceObjectType type, String resourceId); +} diff --git a/api/src/main/java/com/cloud/server/ResourceManagerUtil.java b/api/src/main/java/com/cloud/server/ResourceManagerUtil.java new file mode 100644 index 00000000000..9a3b51a70d5 --- /dev/null +++ b/api/src/main/java/com/cloud/server/ResourceManagerUtil.java @@ -0,0 +1,24 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.server; + +public interface ResourceManagerUtil { + long getResourceId(String resourceId, ResourceTag.ResourceObjectType resourceType); + String getUuid(String resourceId, ResourceTag.ResourceObjectType resourceType); + ResourceTag.ResourceObjectType getResourceType(String resourceTypeStr); + void checkResourceAccessible(Long accountId, Long domainId, String exceptionMessage); +} diff --git a/api/src/main/java/com/cloud/server/ResourceTag.java b/api/src/main/java/com/cloud/server/ResourceTag.java index fb07762639a..ba5ab8ad0ff 100644 --- a/api/src/main/java/com/cloud/server/ResourceTag.java +++ b/api/src/main/java/com/cloud/server/ResourceTag.java @@ -24,13 +24,13 @@ public interface ResourceTag extends ControlledEntity, Identity, InternalIdentit // FIXME - extract enum to another interface as its used both by resourceTags and resourceMetaData code public enum ResourceObjectType { - UserVm(true, true), - Template(true, true), - ISO(true, false), + UserVm(true, true, true), + Template(true, true, true), + ISO(true, false, true), Volume(true, true), Snapshot(true, false), Backup(true, false), - Network(true, true), + Network(true, true, true), Nic(false, true), LoadBalancer(true, true), PortForwardingRule(true, true), @@ -38,14 +38,14 @@ public interface ResourceTag extends ControlledEntity, Identity, InternalIdentit SecurityGroup(true, false), SecurityGroupRule(true, false), PublicIpAddress(true, true), - Project(true, false), - Account(true, false), - Vpc(true, true), + Project(true, false, true), + Account(true, false, true), + Vpc(true, true, true), NetworkACL(true, true), StaticRoute(true, false), VMSnapshot(true, false), RemoteAccessVpn(true, true), - Zone(false, true), + Zone(false, true, true), ServiceOffering(false, true), Storage(false, true), PrivateGateway(false, true), @@ -53,7 +53,7 @@ public interface ResourceTag extends ControlledEntity, Identity, InternalIdentit VpnGateway(false, true), CustomerGateway(false, true), VpnConnection(false, true), - User(true, true), + User(true, true, true), DiskOffering(false, true), AutoScaleVmProfile(false, true), AutoScaleVmGroup(false, true), @@ -62,7 +62,8 @@ public interface ResourceTag extends ControlledEntity, Identity, InternalIdentit SnapshotPolicy(true, true), GuestOs(false, true), NetworkOffering(false, true), - VpcOffering(true, false); + VpcOffering(true, false), + Domain(false, false, true); ResourceObjectType(boolean resourceTagsSupport, boolean resourceMetadataSupport) { @@ -70,8 +71,14 @@ public interface ResourceTag extends ControlledEntity, Identity, InternalIdentit metadataSupport = resourceMetadataSupport; } + ResourceObjectType(boolean resourceTagsSupport, boolean resourceMetadataSupport, boolean resourceIconSupport) { + this(resourceTagsSupport, resourceMetadataSupport); + this.resourceIconSupport = resourceIconSupport; + } + private final boolean resourceTagsSupport; private final boolean metadataSupport; + private boolean resourceIconSupport; public boolean resourceTagsSupport() { return resourceTagsSupport; @@ -80,6 +87,10 @@ public interface ResourceTag extends ControlledEntity, Identity, InternalIdentit public boolean resourceMetadataSupport() { return metadataSupport; } + + public boolean resourceIconSupport() { + return resourceIconSupport; + } } /** diff --git a/api/src/main/java/com/cloud/server/TaggedResourceService.java b/api/src/main/java/com/cloud/server/TaggedResourceService.java index 84f7eb0a6f0..27fa21978aa 100644 --- a/api/src/main/java/com/cloud/server/TaggedResourceService.java +++ b/api/src/main/java/com/cloud/server/TaggedResourceService.java @@ -43,18 +43,6 @@ public interface TaggedResourceService { List listByResourceTypeAndId(ResourceObjectType type, long resourceId); - //FIXME - the methods below should be extracted to its separate manager/service responsible just for retrieving object details - ResourceObjectType getResourceType(String resourceTypeStr); - - /** - * @param resourceId - * @param resourceType - * @return - */ - String getUuid(String resourceId, ResourceObjectType resourceType); - - public long getResourceId(String resourceId, ResourceObjectType resourceType); - /** * Retrieves tags from resource. * @param type diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index f930800b9f8..35c1f87745b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -37,6 +37,7 @@ public class ApiConstants { public static final String BACKUP_ID = "backupid"; public static final String BACKUP_OFFERING_NAME = "backupofferingname"; public static final String BACKUP_OFFERING_ID = "backupofferingid"; + public static final String BASE64_IMAGE = "base64image"; public static final String BITS = "bits"; public static final String BOOTABLE = "bootable"; public static final String BIND_DN = "binddn"; @@ -333,6 +334,7 @@ public class ApiConstants { public static final String SESSIONKEY = "sessionkey"; public static final String SHOW_CAPACITIES = "showcapacities"; public static final String SHOW_REMOVED = "showremoved"; + public static final String SHOW_RESOURCE_ICON = "showicon"; public static final String SHOW_UNIQUE = "showunique"; public static final String SIGNATURE = "signature"; public static final String SIGNATURE_VERSION = "signatureversion"; @@ -747,6 +749,7 @@ public class ApiConstants { public static final String ACCESS_TYPE = "accesstype"; public static final String RESOURCE_DETAILS = "resourcedetails"; + public static final String RESOURCE_ICON = "icon"; public static final String EXPUNGE = "expunge"; public static final String FOR_DISPLAY = "fordisplay"; public static final String PASSIVE = "passive"; diff --git a/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java b/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java index c897aad4d4b..6575f1e6062 100644 --- a/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java @@ -29,6 +29,11 @@ import java.util.regex.Pattern; import javax.inject.Inject; +import com.cloud.server.ManagementService; +import com.cloud.server.ResourceIconManager; +import com.cloud.server.ResourceManagerUtil; +import com.cloud.server.ResourceMetaDataService; +import com.cloud.server.TaggedResourceService; import org.apache.cloudstack.acl.ProjectRoleService; import org.apache.cloudstack.acl.RoleService; import org.apache.cloudstack.acl.RoleType; @@ -67,9 +72,6 @@ import com.cloud.network.vpn.RemoteAccessVpnService; import com.cloud.network.vpn.Site2SiteVpnService; import com.cloud.projects.ProjectService; import com.cloud.resource.ResourceService; -import com.cloud.server.ManagementService; -import com.cloud.server.ResourceMetaDataService; -import com.cloud.server.TaggedResourceService; import com.cloud.storage.DataStoreProviderApiService; import com.cloud.storage.StorageService; import com.cloud.storage.VolumeApiService; @@ -164,6 +166,8 @@ public abstract class BaseCmd { @Inject public TaggedResourceService _taggedResourceService; @Inject + public ResourceManagerUtil resourceManagerUtil; + @Inject public ResourceMetaDataService _resourceMetaDataService; @Inject public VpcService _vpcService; @@ -201,6 +205,8 @@ public abstract class BaseCmd { public UUIDManager _uuidMgr; @Inject public AnnotationService annotationService; + @Inject + public ResourceIconManager resourceIconManager; public abstract void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException; diff --git a/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java b/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java index 3f0d978b161..03f0a3c8369 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java +++ b/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java @@ -22,6 +22,8 @@ import java.util.List; import java.util.Map; import java.util.Set; +import com.cloud.server.ResourceIcon; +import org.apache.cloudstack.api.response.ResourceIconResponse; import org.apache.cloudstack.api.response.RouterHealthCheckResultResponse; import com.cloud.resource.RollingMaintenanceManager; import org.apache.cloudstack.api.response.RollingMaintenanceResponse; @@ -270,7 +272,7 @@ public interface ResponseGenerator { PodResponse createPodResponse(Pod pod, Boolean showCapacities); - ZoneResponse createZoneResponse(ResponseView view, DataCenter dataCenter, Boolean showCapacities); + ZoneResponse createZoneResponse(ResponseView view, DataCenter dataCenter, Boolean showCapacities, Boolean showResourceIcon); VolumeResponse createVolumeResponse(ResponseView view, Volume volume); @@ -487,4 +489,6 @@ public interface ResponseGenerator { RollingMaintenanceResponse createRollingMaintenanceResponse(Boolean success, String details, List hostsUpdated, List hostsSkipped); + ResourceIconResponse createResourceIconResponse(ResourceIcon resourceIcon); + } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/ListDomainChildrenCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/ListDomainChildrenCmd.java index cf35295358b..fb6c5be56ac 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/ListDomainChildrenCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/ListDomainChildrenCmd.java @@ -19,6 +19,9 @@ package org.apache.cloudstack.api.command.admin.domain; import java.util.ArrayList; import java.util.List; +import com.cloud.server.ResourceIcon; +import com.cloud.server.ResourceTag; +import org.apache.cloudstack.api.response.ResourceIconResponse; import org.apache.log4j.Logger; import org.apache.cloudstack.api.APICommand; @@ -58,6 +61,10 @@ public class ListDomainChildrenCmd extends BaseListCmd { description = "If set to false, list only resources belonging to the command's caller; if set to true - list resources that the caller is authorized to see. Default value is false") private Boolean listAll; + @Parameter(name = ApiConstants.SHOW_RESOURCE_ICON, type = CommandType.BOOLEAN, + description = "flag to display the resource icon for domains") + private Boolean showIcon; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -78,6 +85,10 @@ public class ListDomainChildrenCmd extends BaseListCmd { return recursive == null ? false : recursive; } + public Boolean getShowIcon() { + return showIcon != null ? showIcon : false; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -100,6 +111,20 @@ public class ListDomainChildrenCmd extends BaseListCmd { response.setResponses(domainResponses, result.second()); response.setResponseName(getCommandName()); + if (response != null && response.getCount() > 0 && getShowIcon()) { + updateDomainResponse(response.getResponses()); + } this.setResponseObject(response); } + + private void updateDomainResponse(List response) { + for (DomainResponse domainResponse : response) { + ResourceIcon resourceIcon = resourceIconManager.getByResourceTypeAndUuid(ResourceTag.ResourceObjectType.Domain, domainResponse.getId()); + if (resourceIcon == null) { + continue; + } + ResourceIconResponse iconResponse = _responseGenerator.createResourceIconResponse(resourceIcon); + domainResponse.setResourceIconResponse(iconResponse); + } + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/ListDomainsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/ListDomainsCmd.java index 5e4cda33a42..8b6661f27ff 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/ListDomainsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/ListDomainsCmd.java @@ -20,6 +20,9 @@ import java.util.ArrayList; import java.util.EnumSet; import java.util.List; +import com.cloud.server.ResourceIcon; +import com.cloud.server.ResourceTag; +import org.apache.cloudstack.api.response.ResourceIconResponse; import org.apache.log4j.Logger; import org.apache.cloudstack.api.APICommand; @@ -66,6 +69,10 @@ public class ListDomainsCmd extends BaseListCmd implements UserCmd { description = "comma separated list of domain details requested, value can be a list of [ all, resource, min]") private List viewDetails; + @Parameter(name = ApiConstants.SHOW_RESOURCE_ICON, type = CommandType.BOOLEAN, + description = "flag to display the resource icon for domains") + private Boolean showIcon; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -105,6 +112,10 @@ public class ListDomainsCmd extends BaseListCmd implements UserCmd { return dv; } + public Boolean getShowIcon() { + return showIcon != null ? showIcon : false; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -119,5 +130,19 @@ public class ListDomainsCmd extends BaseListCmd implements UserCmd { ListResponse response = _queryService.searchForDomains(this); response.setResponseName(getCommandName()); this.setResponseObject(response); + if (response != null && response.getCount() > 0 && getShowIcon()) { + updateDomainResponse(response.getResponses()); + } + } + + private void updateDomainResponse(List response) { + for (DomainResponse domainResponse : response) { + ResourceIcon resourceIcon = resourceIconManager.getByResourceTypeAndUuid(ResourceTag.ResourceObjectType.Domain, domainResponse.getId()); + if (resourceIcon == null) { + continue; + } + ResourceIconResponse iconResponse = _responseGenerator.createResourceIconResponse(resourceIcon); + domainResponse.setResourceIconResponse(iconResponse); + } } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/icon/DeleteResourceIconCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/icon/DeleteResourceIconCmd.java new file mode 100644 index 00000000000..de45839bb09 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/icon/DeleteResourceIconCmd.java @@ -0,0 +1,104 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.admin.resource.icon; + +import com.cloud.server.ResourceIcon; +import com.cloud.server.ResourceTag; +import com.cloud.user.Account; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.log4j.Logger; + +import java.util.List; + +@APICommand(name = "deleteResourceIcon", description = "deletes the resource icon from the specified resource(s)", + responseObject = SuccessResponse.class, since = "4.16.0.0", entityType = {ResourceIcon.class}, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + authorized = {RoleType.Admin, RoleType.DomainAdmin, RoleType.ResourceAdmin, RoleType.User}) +public class DeleteResourceIconCmd extends BaseCmd { + public static final Logger s_logger = Logger.getLogger(DeleteResourceIconCmd.class.getName()); + + private static final String s_name = "deleteresourceiconresponse"; + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.RESOURCE_IDS, + type = BaseCmd.CommandType.LIST, + required = true, + collectionType = BaseCmd.CommandType.STRING, + description = "list of resources to upload the icon/image for") + private List resourceIds; + + @Parameter(name = ApiConstants.RESOURCE_TYPE, type = BaseCmd.CommandType.STRING, required = true, description = "type of the resource") + private String resourceType; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public List getResourceIds() { + return resourceIds; + } + + public ResourceTag.ResourceObjectType getResourceType() { + return resourceManagerUtil.getResourceType(resourceType); + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() { + try { + boolean result = resourceIconManager.deleteResourceIcon(getResourceIds(), getResourceType()); + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete resource image"); + } + + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getLocalizedMessage()); + } + + } + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + Account account = CallContext.current().getCallingAccount();// Let's give the caller here for event logging. + if (account != null) { + return account.getAccountId(); + } + + return Account.ACCOUNT_ID_SYSTEM; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/icon/ListResourceIconCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/icon/ListResourceIconCmd.java new file mode 100644 index 00000000000..bc8eb009072 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/icon/ListResourceIconCmd.java @@ -0,0 +1,86 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.admin.resource.icon; + +import com.cloud.server.ResourceIcon; +import com.cloud.server.ResourceTag; +import com.cloud.user.Account; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.ResourceIconResponse; +import org.apache.log4j.Logger; + +import java.util.List; + +@APICommand(name = "listResourceIcon", description = "Lists the resource icon for the specified resource(s)", + responseObject = ResourceIconResponse.class, since = "4.16.0.0", entityType = {ResourceIcon.class}, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + authorized = {RoleType.Admin, RoleType.DomainAdmin, RoleType.ResourceAdmin, RoleType.User}) +public class ListResourceIconCmd extends BaseCmd { + public static final Logger s_logger = Logger.getLogger(ListResourceIconCmd.class.getName()); + + private static final String s_name = "listresourceiconresponse"; + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + @Parameter(name = ApiConstants.RESOURCE_IDS, + type = BaseCmd.CommandType.LIST, + required = true, + collectionType = BaseCmd.CommandType.STRING, + description = "list of resources to upload the icon/image for") + private List resourceIds; + + @Parameter(name = ApiConstants.RESOURCE_TYPE, type = BaseCmd.CommandType.STRING, required = true, description = "type of the resource") + private String resourceType; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public List getResourceIds() { + return resourceIds; + } + + public ResourceTag.ResourceObjectType getResourceType() { + return resourceManagerUtil.getResourceType(resourceType); + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() { + ListResponse response = _queryService.listResourceIcons(this); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/icon/UploadResourceIconCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/icon/UploadResourceIconCmd.java new file mode 100644 index 00000000000..3075bf3a1ab --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/icon/UploadResourceIconCmd.java @@ -0,0 +1,144 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.admin.resource.icon; + +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.server.ResourceIcon; +import com.cloud.server.ResourceTag; +import com.cloud.user.Account; +import org.apache.cloudstack.acl.RoleType; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.commons.lang3.StringUtils; +import org.apache.log4j.Logger; + +import java.awt.image.BufferedImage; + +import javax.imageio.ImageIO; +import java.io.ByteArrayInputStream; +import java.util.Base64; +import java.util.List; + + +@APICommand(name = "uploadResourceIcon", description = "Uploads an icon for the specified resource(s)", + responseObject = SuccessResponse.class, since = "4.16.0.0", entityType = {ResourceIcon.class}, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + authorized = {RoleType.Admin, RoleType.DomainAdmin, RoleType.ResourceAdmin, RoleType.User}) +public class UploadResourceIconCmd extends BaseCmd { + public static final Logger LOGGER = Logger.getLogger(UploadResourceIconCmd.class.getName()); + + private static final String s_name = "uploadresourceiconresponse"; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.RESOURCE_IDS, + type = BaseCmd.CommandType.LIST, + required = true, + collectionType = BaseCmd.CommandType.STRING, + description = "list of resources to upload the icon/image for") + private List resourceIds; + + @Parameter(name = ApiConstants.RESOURCE_TYPE, type = BaseCmd.CommandType.STRING, required = true, description = "type of the resource") + private String resourceType; + + @Parameter(name = ApiConstants.BASE64_IMAGE, type = BaseCmd.CommandType.STRING, required = true, + description = "Base64 string representation of the resource icon/image", length = 2097152) + private String image; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public List getResourceIds() { + return resourceIds; + } + + public ResourceTag.ResourceObjectType getResourceType() { + return resourceManagerUtil.getResourceType(resourceType); + } + + public String getImage() { + if (StringUtils.isEmpty(image)) { + throw new InvalidParameterValueException("No image provided for resource icon"); + } + return image; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() { + try { + if (!imageValidator(getImage())) { + throw new InvalidParameterValueException("Invalid image uploaded"); + } + boolean result = resourceIconManager.uploadResourceIcon(getResourceIds(), getResourceType(), getImage()); + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to upload resource image"); + } + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getLocalizedMessage()); + } + } + + private boolean imageValidator (String base64Image) { + BufferedImage image = null; + byte[] imageByte; + try { + imageByte = Base64.getDecoder().decode(base64Image); + ByteArrayInputStream bis = new ByteArrayInputStream(imageByte); + image = ImageIO.read(bis); + bis.close(); + if (image == null) { + return false; + } + } catch (Exception e) { + LOGGER.warn("Data uploaded not a valid image"); + return false; + } + return true; + } + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + Account account = CallContext.current().getCallingAccount();// Let's give the caller here for event logging. + if (account != null) { + return account.getAccountId(); + } + + return Account.ACCOUNT_ID_SYSTEM; + } + +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/ListUsersCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/ListUsersCmd.java index c0c2b24814a..a1adee7909f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/ListUsersCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/ListUsersCmd.java @@ -16,6 +16,9 @@ // under the License. package org.apache.cloudstack.api.command.admin.user; +import com.cloud.server.ResourceIcon; +import com.cloud.server.ResourceTag; +import org.apache.cloudstack.api.response.ResourceIconResponse; import org.apache.log4j.Logger; import org.apache.cloudstack.api.APICommand; @@ -25,6 +28,8 @@ import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.api.response.UserResponse; +import java.util.List; + @APICommand(name = "listUsers", description = "Lists user accounts", responseObject = UserResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = true) public class ListUsersCmd extends BaseListAccountResourcesCmd { @@ -50,6 +55,10 @@ public class ListUsersCmd extends BaseListAccountResourcesCmd { @Parameter(name = ApiConstants.USERNAME, type = CommandType.STRING, description = "List user by the username") private String username; + @Parameter(name = ApiConstants.SHOW_RESOURCE_ICON, type = CommandType.BOOLEAN, + description = "flag to display the resource icon for users") + private Boolean showIcon; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -70,6 +79,10 @@ public class ListUsersCmd extends BaseListAccountResourcesCmd { return username; } + public Boolean getShowIcon() { + return showIcon != null ? showIcon : false; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -84,5 +97,22 @@ public class ListUsersCmd extends BaseListAccountResourcesCmd { ListResponse response = _queryService.searchForUsers(this); response.setResponseName(getCommandName()); this.setResponseObject(response); + if (response != null && response.getCount() > 0 && getShowIcon()) { + updateUserResponse(response.getResponses()); + } + } + + private void updateUserResponse(List response) { + for (UserResponse userResponse : response) { + ResourceIcon resourceIcon = resourceIconManager.getByResourceTypeAndUuid(ResourceTag.ResourceObjectType.User, userResponse.getObjectId()); + if (resourceIcon == null) { + resourceIcon = resourceIconManager.getByResourceTypeAndUuid(ResourceTag.ResourceObjectType.Account, userResponse.getAccountId()); + if (resourceIcon == null) { + continue; + } + } + ResourceIconResponse iconResponse = _responseGenerator.createResourceIconResponse(resourceIcon); + userResponse.setResourceIconResponse(iconResponse); + } } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/zone/CreateZoneCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/zone/CreateZoneCmd.java index 414c058e66d..af377cd347a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/zone/CreateZoneCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/zone/CreateZoneCmd.java @@ -171,7 +171,7 @@ public class CreateZoneCmd extends BaseCmd { CallContext.current().setEventDetails("Zone Name: " + getZoneName()); DataCenter result = _configService.createZone(this); if (result != null){ - ZoneResponse response = _responseGenerator.createZoneResponse(ResponseView.Full, result, false); + ZoneResponse response = _responseGenerator.createZoneResponse(ResponseView.Full, result, false, false); response.setResponseName(getCommandName()); setResponseObject(response); } else { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/zone/UpdateZoneCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/zone/UpdateZoneCmd.java index 2f5feed803d..ac80ca59f92 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/zone/UpdateZoneCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/zone/UpdateZoneCmd.java @@ -189,7 +189,7 @@ public class UpdateZoneCmd extends BaseCmd { CallContext.current().setEventDetails("Zone Id: " + getId()); DataCenter result = _configService.editZone(this); if (result != null) { - ZoneResponse response = _responseGenerator.createZoneResponse(ResponseView.Full, result, false); + ZoneResponse response = _responseGenerator.createZoneResponse(ResponseView.Full, result, false, false); response.setResponseName(getCommandName()); setResponseObject(response); } else { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/account/ListAccountsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/account/ListAccountsCmd.java index 29f86c84a12..993384d6a3f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/account/ListAccountsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/account/ListAccountsCmd.java @@ -20,6 +20,9 @@ import java.util.ArrayList; import java.util.EnumSet; import java.util.List; +import com.cloud.server.ResourceIcon; +import com.cloud.server.ResourceTag; +import org.apache.cloudstack.api.response.ResourceIconResponse; import org.apache.log4j.Logger; import org.apache.cloudstack.api.APICommand; @@ -68,6 +71,10 @@ public class ListAccountsCmd extends BaseListDomainResourcesCmd implements UserC description = "comma separated list of account details requested, value can be a list of [ all, resource, min]") private List viewDetails; + @Parameter(name = ApiConstants.SHOW_RESOURCE_ICON, type = CommandType.BOOLEAN, + description = "flag to display the resource icon for accounts") + private Boolean showIcon; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -111,6 +118,10 @@ public class ListAccountsCmd extends BaseListDomainResourcesCmd implements UserC return dv; } + public Boolean getShowIcon() { + return showIcon != null ? showIcon : false; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -125,5 +136,19 @@ public class ListAccountsCmd extends BaseListDomainResourcesCmd implements UserC ListResponse response = _queryService.searchForAccounts(this); response.setResponseName(getCommandName()); setResponseObject(response); + if (response != null && response.getCount() > 0 && getShowIcon()) { + updateAccountResponse(response.getResponses()); + } + } + + private void updateAccountResponse(List response) { + for (AccountResponse accountResponse : response) { + ResourceIcon resourceIcon = resourceIconManager.getByResourceTypeAndUuid(ResourceTag.ResourceObjectType.Account, accountResponse.getObjectId()); + if (resourceIcon == null) { + continue; + } + ResourceIconResponse iconResponse = _responseGenerator.createResourceIconResponse(resourceIcon); + accountResponse.setResourceIconResponse(iconResponse); + } } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/iso/ListIsosCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/iso/ListIsosCmd.java index b741ae7dd5f..44bbb7168c1 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/iso/ListIsosCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/iso/ListIsosCmd.java @@ -16,6 +16,9 @@ // under the License. package org.apache.cloudstack.api.command.user.iso; +import com.cloud.server.ResourceIcon; +import com.cloud.server.ResourceTag; +import org.apache.cloudstack.api.response.ResourceIconResponse; import org.apache.log4j.Logger; import org.apache.cloudstack.api.APICommand; @@ -33,6 +36,8 @@ import org.apache.cloudstack.context.CallContext; import com.cloud.template.VirtualMachineTemplate.TemplateFilter; import com.cloud.user.Account; +import java.util.List; + @APICommand(name = "listIsos", description = "Lists all available ISO files.", responseObject = TemplateResponse.class, responseView = ResponseView.Restricted, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) public class ListIsosCmd extends BaseListTaggedResourcesCmd implements UserCmd { @@ -82,6 +87,9 @@ public class ListIsosCmd extends BaseListTaggedResourcesCmd implements UserCmd { @Parameter(name = ApiConstants.SHOW_UNIQUE, type = CommandType.BOOLEAN, description = "If set to true, list only unique isos across zones", since = "4.13.2") private Boolean showUnique; + @Parameter(name = ApiConstants.SHOW_RESOURCE_ICON, type = CommandType.BOOLEAN, description = "flag to display the resource image for the isos") + private Boolean showIcon; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -126,6 +134,10 @@ public class ListIsosCmd extends BaseListTaggedResourcesCmd implements UserCmd { return showUnique != null && showUnique; } + public Boolean getShowIcon () { + return showIcon != null ? showIcon : false; + } + public boolean listInReadyState() { Account account = CallContext.current().getCallingAccount(); // It is account specific if account is admin type and domainId and accountName are not null @@ -162,7 +174,21 @@ public class ListIsosCmd extends BaseListTaggedResourcesCmd implements UserCmd { @Override public void execute() { ListResponse response = _queryService.listIsos(this); + if (response != null && response.getCount() > 0 && getShowIcon()) { + updateIsoResponse(response.getResponses()); + } response.setResponseName(getCommandName()); setResponseObject(response); } + + private void updateIsoResponse(List response) { + for (TemplateResponse templateResponse : response) { + ResourceIcon resourceIcon = resourceIconManager.getByResourceTypeAndUuid(ResourceTag.ResourceObjectType.ISO, templateResponse.getId()); + if (resourceIcon == null) { + continue; + } + ResourceIconResponse iconResponse = _responseGenerator.createResourceIconResponse(resourceIcon); + templateResponse.setResourceIconResponse(iconResponse); + } + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/network/ListNetworksCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/network/ListNetworksCmd.java index c69fc69ef31..5149d4e7af6 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/network/ListNetworksCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/network/ListNetworksCmd.java @@ -19,7 +19,10 @@ package org.apache.cloudstack.api.command.user.network; import java.util.ArrayList; import java.util.List; +import com.cloud.server.ResourceIcon; +import com.cloud.server.ResourceTag; import org.apache.cloudstack.api.response.NetworkOfferingResponse; +import org.apache.cloudstack.api.response.ResourceIconResponse; import org.apache.log4j.Logger; import org.apache.cloudstack.acl.RoleType; @@ -93,6 +96,10 @@ public class ListNetworksCmd extends BaseListTaggedResourcesCmd implements UserC @Parameter(name = ApiConstants.NETWORK_OFFERING_ID, type = CommandType.UUID, entityType = NetworkOfferingResponse.class, description = "list networks by network offering ID") private Long networkOfferingId; + @Parameter(name = ApiConstants.SHOW_RESOURCE_ICON, type = CommandType.BOOLEAN, + description = "flag to display the resource icon for networks") + private Boolean showIcon; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -166,6 +173,11 @@ public class ListNetworksCmd extends BaseListTaggedResourcesCmd implements UserC } return super.getDisplay(); } + + public Boolean getShowIcon() { + return showIcon != null ? showIcon : false; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -186,5 +198,22 @@ public class ListNetworksCmd extends BaseListTaggedResourcesCmd implements UserC response.setResponses(networkResponses, networks.second()); response.setResponseName(getCommandName()); setResponseObject(response); + if (response != null && response.getCount() > 0 && getShowIcon()) { + updateNetworkResponse(response.getResponses()); + } + } + + private void updateNetworkResponse(List response) { + for (NetworkResponse networkResponse : response) { + ResourceIcon resourceIcon = resourceIconManager.getByResourceTypeAndUuid(ResourceTag.ResourceObjectType.Network, networkResponse.getId()); + if (resourceIcon == null) { + resourceIcon = resourceIconManager.getByResourceTypeAndUuid(ResourceTag.ResourceObjectType.Vpc, networkResponse.getVpcId()); + if (resourceIcon == null) { + continue; + } + } + ResourceIconResponse iconResponse = _responseGenerator.createResourceIconResponse(resourceIcon); + networkResponse.setResourceIconResponse(iconResponse); + } } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/project/ListProjectsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/project/ListProjectsCmd.java index 2c965512bba..f434b75559f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/project/ListProjectsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/project/ListProjectsCmd.java @@ -21,6 +21,9 @@ import java.util.EnumSet; import java.util.List; import java.util.Map; +import com.cloud.server.ResourceIcon; +import com.cloud.server.ResourceTag; +import org.apache.cloudstack.api.response.ResourceIconResponse; import org.apache.log4j.Logger; import org.apache.cloudstack.api.APICommand; @@ -72,6 +75,10 @@ public class ListProjectsCmd extends BaseListAccountResourcesCmd { description = "comma separated list of project details requested, value can be a list of [ all, resource, min]") private List viewDetails; + @Parameter(name = ApiConstants.SHOW_RESOURCE_ICON, type = CommandType.BOOLEAN, + description = "flag to display the resource icon for projects") + private Boolean showIcon; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -124,6 +131,10 @@ public class ListProjectsCmd extends BaseListAccountResourcesCmd { return dv; } + public Boolean getShowIcon() { + return showIcon != null ? showIcon : false; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -133,5 +144,19 @@ public class ListProjectsCmd extends BaseListAccountResourcesCmd { ListResponse response = _queryService.listProjects(this); response.setResponseName(getCommandName()); this.setResponseObject(response); + if (response != null && response.getCount() > 0 && getShowIcon()) { + updateProjectResponse(response.getResponses()); + } + } + + private void updateProjectResponse(List response) { + for (ProjectResponse projectResponse : response) { + ResourceIcon resourceIcon = resourceIconManager.getByResourceTypeAndUuid(ResourceTag.ResourceObjectType.Project, projectResponse.getId()); + if (resourceIcon == null) { + continue; + } + ResourceIconResponse iconResponse = _responseGenerator.createResourceIconResponse(resourceIcon); + projectResponse.setResourceIconResponse(iconResponse); + } } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/resource/ListDetailOptionsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/resource/ListDetailOptionsCmd.java index 5007e63f5c6..b7a46795638 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/resource/ListDetailOptionsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/resource/ListDetailOptionsCmd.java @@ -54,12 +54,12 @@ public class ListDetailOptionsCmd extends BaseCmd { ///////////////////////////////////////////////////// public ResourceTag.ResourceObjectType getResourceType() { - return _taggedResourceService.getResourceType(resourceType); + return resourceManagerUtil.getResourceType(resourceType); } public String getResourceId() { if (!Strings.isNullOrEmpty(resourceId)) { - return _taggedResourceService.getUuid(resourceId, getResourceType()); + return resourceManagerUtil.getUuid(resourceId, getResourceType()); } return null; } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/tag/CreateTagsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/tag/CreateTagsCmd.java index cde31cdec91..bc6817096b2 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/tag/CreateTagsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/tag/CreateTagsCmd.java @@ -68,7 +68,7 @@ public class CreateTagsCmd extends BaseAsyncCmd { ///////////////////////////////////////////////////// public ResourceObjectType getResourceType() { - return _taggedResourceService.getResourceType(resourceType); + return resourceManagerUtil.getResourceType(resourceType); } public Map getTags() { @@ -106,17 +106,17 @@ public class CreateTagsCmd extends BaseAsyncCmd { SuccessResponse response = new SuccessResponse(getCommandName()); setResponseObject(response); } else { - throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create tags"); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to upload resource icon"); } } @Override public String getEventType() { - return EventTypes.EVENT_TAGS_CREATE; + return EventTypes.EVENT_RESOURCE_ICON_UPLOAD; } @Override public String getEventDescription() { - return "creating tags"; + return "Uploading resource icon"; } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/tag/DeleteTagsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/tag/DeleteTagsCmd.java index e42cfce4d91..3fa6b678051 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/tag/DeleteTagsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/tag/DeleteTagsCmd.java @@ -66,7 +66,7 @@ public class DeleteTagsCmd extends BaseAsyncCmd { ///////////////////////////////////////////////////// public ResourceObjectType getResourceType() { - return _taggedResourceService.getResourceType(resourceType); + return resourceManagerUtil.getResourceType(resourceType); } public Map getTags() { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplatesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplatesCmd.java index 5ef66c50b98..bf3b2fbf5a4 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplatesCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplatesCmd.java @@ -17,6 +17,9 @@ package org.apache.cloudstack.api.command.user.template; import com.cloud.exception.InvalidParameterValueException; +import com.cloud.server.ResourceIcon; +import com.cloud.server.ResourceTag; +import org.apache.cloudstack.api.response.ResourceIconResponse; import org.apache.commons.collections.CollectionUtils; import org.apache.log4j.Logger; @@ -161,10 +164,17 @@ public class ListTemplatesCmd extends BaseListTaggedResourcesCmd implements User return onlyReady; } + @Parameter(name = ApiConstants.SHOW_RESOURCE_ICON, type = CommandType.BOOLEAN, description = "flag to display the resource image for the templates") + private Boolean showIcon; + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// + public Boolean getShowIcon () { + return showIcon != null ? showIcon : false; + } + @Override public String getCommandName() { return s_name; @@ -178,10 +188,24 @@ public class ListTemplatesCmd extends BaseListTaggedResourcesCmd implements User @Override public void execute() { ListResponse response = _queryService.listTemplates(this); + if (response != null && response.getCount() > 0 && getShowIcon()) { + updateTemplateResponse(response.getResponses()); + } response.setResponseName(getCommandName()); setResponseObject(response); } + private void updateTemplateResponse(List response) { + for (TemplateResponse templateResponse : response) { + ResourceIcon resourceIcon = resourceIconManager.getByResourceTypeAndUuid(ResourceTag.ResourceObjectType.Template, templateResponse.getId()); + if (resourceIcon == null) { + continue; + } + ResourceIconResponse iconResponse = _responseGenerator.createResourceIconResponse(resourceIcon); + templateResponse.setResourceIconResponse(iconResponse); + } + } + public List getIds() { return ids; } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMsCmd.java index 72839f253e1..b0ff1023c5c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMsCmd.java @@ -20,7 +20,10 @@ import java.util.ArrayList; import java.util.EnumSet; import java.util.List; +import com.cloud.server.ResourceIcon; +import com.cloud.server.ResourceTag; import org.apache.cloudstack.api.command.user.UserCmd; +import org.apache.cloudstack.api.response.ResourceIconResponse; import org.apache.cloudstack.api.response.SecurityGroupResponse; import org.apache.cloudstack.api.response.UserResponse; import org.apache.log4j.Logger; @@ -141,6 +144,10 @@ public class ListVMsCmd extends BaseListTaggedResourcesCmd implements UserCmd { @Parameter(name = ApiConstants.HA_ENABLE, type = CommandType.BOOLEAN, description = "list by the High Availability offering; true if filtering VMs with HA enabled; false for VMs with HA disabled", since = "4.15") private Boolean haEnabled; + @Parameter(name = ApiConstants.SHOW_RESOURCE_ICON, type = CommandType.BOOLEAN, + description = "flag to display the resource icon for VMs", since = "4.16.0.0") + private Boolean showIcon; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -252,6 +259,11 @@ public class ListVMsCmd extends BaseListTaggedResourcesCmd implements UserCmd { } return super.getDisplay(); } + + public Boolean getShowIcon() { + return showIcon != null ? showIcon : false; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -268,7 +280,30 @@ public class ListVMsCmd extends BaseListTaggedResourcesCmd implements UserCmd { @Override public void execute() { ListResponse response = _queryService.searchForUserVMs(this); + if (response != null && response.getCount() > 0 && getShowIcon()) { + updateVMResponse(response.getResponses()); + } response.setResponseName(getCommandName()); setResponseObject(response); } + + protected void updateVMResponse(List response) { + for (UserVmResponse vmResponse : response) { + ResourceIcon resourceIcon = resourceIconManager.getByResourceTypeAndUuid(ResourceTag.ResourceObjectType.UserVm, vmResponse.getId()); + if (resourceIcon == null) { + ResourceTag.ResourceObjectType type = ResourceTag.ResourceObjectType.Template; + String uuid = vmResponse.getTemplateId(); + if (vmResponse.getIsoId() != null) { + uuid = vmResponse.getIsoId(); + type = ResourceTag.ResourceObjectType.ISO; + } + resourceIcon = resourceIconManager.getByResourceTypeAndUuid(type, uuid); + if (resourceIcon == null) { + continue; + } + } + ResourceIconResponse iconResponse = _responseGenerator.createResourceIconResponse(resourceIcon); + vmResponse.setResourceIconResponse(iconResponse); + } + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/AddResourceDetailCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/AddResourceDetailCmd.java index a8a3a75869d..d10bbb7016f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/AddResourceDetailCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/AddResourceDetailCmd.java @@ -61,7 +61,7 @@ public class AddResourceDetailCmd extends BaseAsyncCmd { } public ResourceTag.ResourceObjectType getResourceType() { - return _taggedResourceService.getResourceType(resourceType); + return resourceManagerUtil.getResourceType(resourceType); } public String getResourceId() { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ListResourceDetailsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ListResourceDetailsCmd.java index 83cb1fff901..33aa44a3422 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ListResourceDetailsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ListResourceDetailsCmd.java @@ -92,7 +92,7 @@ public class ListResourceDetailsCmd extends BaseListProjectAndAccountResourcesCm } public ResourceTag.ResourceObjectType getResourceType() { - return _taggedResourceService.getResourceType(resourceType); + return resourceManagerUtil.getResourceType(resourceType); } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/RemoveResourceDetailCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/RemoveResourceDetailCmd.java index 6fe576dc63c..703fe8a1bda 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/RemoveResourceDetailCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/RemoveResourceDetailCmd.java @@ -56,7 +56,7 @@ public class RemoveResourceDetailCmd extends BaseAsyncCmd { ///////////////////////////////////////////////////// public ResourceTag.ResourceObjectType getResourceType() { - return _taggedResourceService.getResourceType(resourceType); + return resourceManagerUtil.getResourceType(resourceType); } public String getKey() { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/ListVPCsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/ListVPCsCmd.java index adcbf8bec6d..b230603f852 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/ListVPCsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/ListVPCsCmd.java @@ -19,6 +19,8 @@ package org.apache.cloudstack.api.command.user.vpc; import java.util.ArrayList; import java.util.List; +import com.cloud.server.ResourceIcon; +import com.cloud.server.ResourceTag; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; @@ -27,6 +29,7 @@ import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ResponseObject.ResponseView; import org.apache.cloudstack.api.command.user.UserCmd; import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.ResourceIconResponse; import org.apache.cloudstack.api.response.VpcOfferingResponse; import org.apache.cloudstack.api.response.VpcResponse; import org.apache.cloudstack.api.response.ZoneResponse; @@ -76,6 +79,10 @@ public class ListVPCsCmd extends BaseListTaggedResourcesCmd implements UserCmd { @Parameter(name = ApiConstants.FOR_DISPLAY, type = CommandType.BOOLEAN, description = "list resources by display flag; only ROOT admin is eligible to pass this parameter", since = "4.4", authorized = {RoleType.Admin}) private Boolean display; + @Parameter(name = ApiConstants.SHOW_RESOURCE_ICON, type = CommandType.BOOLEAN, + description = "flag to display the resource icon for VPCs") + private Boolean showIcon; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -124,6 +131,10 @@ public class ListVPCsCmd extends BaseListTaggedResourcesCmd implements UserCmd { return super.getDisplay(); } + public Boolean getShowIcon() { + return showIcon != null ? showIcon : false; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -144,6 +155,20 @@ public class ListVPCsCmd extends BaseListTaggedResourcesCmd implements UserCmd { response.setResponses(vpcResponses, vpcs.second()); response.setResponseName(getCommandName()); setResponseObject(response); + if (response != null && response.getCount() > 0 && getShowIcon()) { + updateVpcResponse(response.getResponses()); + } + } + + private void updateVpcResponse(List response) { + for (VpcResponse vpcResponse : response) { + ResourceIcon resourceIcon = resourceIconManager.getByResourceTypeAndUuid(ResourceTag.ResourceObjectType.Vpc, vpcResponse.getId()); + if (resourceIcon == null) { + continue; + } + ResourceIconResponse iconResponse = _responseGenerator.createResourceIconResponse(resourceIcon); + vpcResponse.setResourceIconResponse(iconResponse); + } } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/AddVpnUserCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/AddVpnUserCmd.java index 9fd4c5a2dfc..0dba83b61b0 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/AddVpnUserCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/AddVpnUserCmd.java @@ -123,7 +123,7 @@ public class AddVpnUserCmd extends BaseAsyncCreateCmd { if (!_ravService.applyVpnUsers(vpnUser.getAccountId(), userName)) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to add vpn user"); } - }catch (Exception ex) { + } catch (Exception ex) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage()); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/zone/ListZonesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/zone/ListZonesCmd.java index f7e3155535f..c79afb1c972 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/zone/ListZonesCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/zone/ListZonesCmd.java @@ -65,6 +65,9 @@ public class ListZonesCmd extends BaseListCmd implements UserCmd { @Parameter(name = ApiConstants.TAGS, type = CommandType.MAP, description = "List zones by resource tags (key/value pairs)", since = "4.3") private Map tags; + @Parameter(name = ApiConstants.SHOW_RESOURCE_ICON, type = CommandType.BOOLEAN, description = "flag to display the resource image for the zones") + private Boolean showIcon; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -97,6 +100,10 @@ public class ListZonesCmd extends BaseListCmd implements UserCmd { return TaggedResources.parseKeyValueMap(tags, false); } + public Boolean getShowIcon () { + return showIcon != null ? showIcon : false; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -108,7 +115,6 @@ public class ListZonesCmd extends BaseListCmd implements UserCmd { @Override public void execute() { - ListResponse response = _queryService.listDataCenters(this); response.setResponseName(getCommandName()); setResponseObject(response); diff --git a/api/src/main/java/org/apache/cloudstack/api/response/AccountResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/AccountResponse.java index 5cd2fd314e2..770df2678f2 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/AccountResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/AccountResponse.java @@ -30,7 +30,7 @@ import com.cloud.user.Account; import com.google.gson.annotations.SerializedName; @EntityReference(value = Account.class) -public class AccountResponse extends BaseResponse implements ResourceLimitAndCountResponse { +public class AccountResponse extends BaseResponse implements ResourceLimitAndCountResponse, SetResourceIconResponse { @SerializedName(ApiConstants.ID) @Param(description = "the id of the account") private String id; @@ -263,6 +263,10 @@ public class AccountResponse extends BaseResponse implements ResourceLimitAndCou @Param(description = "the list of acl groups that account belongs to", since = "4.4") private List groups; + @SerializedName(ApiConstants.RESOURCE_ICON) + @Param(description = "Base64 string representation of the resource icon", since = "4.16.0.0") + ResourceIconResponse icon; + @Override public String getObjectId() { return id; @@ -537,4 +541,8 @@ public class AccountResponse extends BaseResponse implements ResourceLimitAndCou this.groups = groups; } + @Override + public void setResourceIconResponse(ResourceIconResponse icon) { + this.icon = icon; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/DomainResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/DomainResponse.java index 556fe04fd6f..6d5655269e9 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/DomainResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/DomainResponse.java @@ -28,7 +28,7 @@ import com.cloud.serializer.Param; import java.util.Date; @EntityReference(value = Domain.class) -public class DomainResponse extends BaseResponseWithAnnotations implements ResourceLimitAndCountResponse { +public class DomainResponse extends BaseResponseWithAnnotations implements ResourceLimitAndCountResponse, SetResourceIconResponse { @SerializedName(ApiConstants.ID) @Param(description = "the ID of the domain") private String id; @@ -175,6 +175,10 @@ public class DomainResponse extends BaseResponseWithAnnotations implements Resou @SerializedName("secondarystorageavailable") @Param(description="the total secondary storage space (in GiB) available to be used for this domain", since="4.2.0") private String secondaryStorageAvailable; + @SerializedName(ApiConstants.RESOURCE_ICON) + @Param(description = "Base64 string representation of the resource icon", since = "4.16.0.0") + ResourceIconResponse icon; + public String getId() { return this.id; } @@ -429,4 +433,9 @@ public class DomainResponse extends BaseResponseWithAnnotations implements Resou public void setVmRunning(Integer vmRunning) { // TODO Auto-generated method stub } + + @Override + public void setResourceIconResponse(ResourceIconResponse icon) { + this.icon = icon; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/NetworkResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/NetworkResponse.java index 80b1999eaca..4b41610ab02 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/NetworkResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/NetworkResponse.java @@ -33,7 +33,7 @@ import com.google.gson.annotations.SerializedName; @SuppressWarnings("unused") @EntityReference(value = {Network.class, ProjectAccount.class}) -public class NetworkResponse extends BaseResponseWithAnnotations implements ControlledEntityResponse { +public class NetworkResponse extends BaseResponseWithAnnotations implements ControlledEntityResponse, SetResourceIconResponse { @SerializedName(ApiConstants.ID) @Param(description = "the id of the network") @@ -247,6 +247,10 @@ public class NetworkResponse extends BaseResponseWithAnnotations implements Cont @Param(description = "If the network has redundant routers enabled", since = "4.11.1") private Boolean redundantRouter; + @SerializedName(ApiConstants.RESOURCE_ICON) + @Param(description = "Base64 string representation of the resource icon", since = "4.16.0.0") + ResourceIconResponse icon; + @SerializedName(ApiConstants.CREATED) @Param(description = "the date this network was created", since = "4.16.0") private Date created; @@ -271,6 +275,10 @@ public class NetworkResponse extends BaseResponseWithAnnotations implements Cont this.id = id; } + public String getId() { + return id; + } + public void setName(String name) { this.name = name; } @@ -428,6 +436,10 @@ public class NetworkResponse extends BaseResponseWithAnnotations implements Cont this.vpcId = vpcId; } + public String getVpcId() { + return vpcId; + } + public void setCanUseForDeploy(Boolean canUseForDeploy) { this.canUseForDeploy = canUseForDeploy; } @@ -496,6 +508,11 @@ public class NetworkResponse extends BaseResponseWithAnnotations implements Cont this.vpcName = vpcName; } + @Override + public void setResourceIconResponse(ResourceIconResponse icon) { + this.icon = icon; + } + public Date getCreated() { return created; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ProjectResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ProjectResponse.java index 7f14fce3007..c43dd09b127 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ProjectResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ProjectResponse.java @@ -30,7 +30,7 @@ import com.cloud.serializer.Param; import com.google.gson.annotations.SerializedName; @EntityReference(value = Project.class) -public class ProjectResponse extends BaseResponse implements ResourceLimitAndCountResponse { +public class ProjectResponse extends BaseResponse implements ResourceLimitAndCountResponse, SetResourceIconResponse { @SerializedName(ApiConstants.ID) @Param(description = "the id of the project") @@ -208,6 +208,10 @@ public class ProjectResponse extends BaseResponse implements ResourceLimitAndCou @Param(description = "the total number of virtual machines running for this project", since = "4.2.0") private Integer vmRunning; + @SerializedName(ApiConstants.RESOURCE_ICON) + @Param(description = "Base64 string representation of the resource icon", since = "4.16.0.0") + ResourceIconResponse icon; + @SerializedName(ApiConstants.CREATED) @Param(description = "the date this project was created", since = "4.16.0") private Date created; @@ -216,6 +220,10 @@ public class ProjectResponse extends BaseResponse implements ResourceLimitAndCou this.id = id; } + public String getId() { + return id; + } + public void setName(String name) { this.name = name; } @@ -427,6 +435,11 @@ public class ProjectResponse extends BaseResponse implements ResourceLimitAndCou this.owners = owners; } + @Override + public void setResourceIconResponse(ResourceIconResponse icon) { + this.icon = icon; + } + public Date getCreated() { return created; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ResourceIconResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ResourceIconResponse.java new file mode 100644 index 00000000000..403a91c8894 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/ResourceIconResponse.java @@ -0,0 +1,61 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.response; + +import com.cloud.serializer.Param; +import com.cloud.server.ResourceTag; +import com.google.gson.annotations.SerializedName; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; + +public class ResourceIconResponse extends BaseResponse { + @SerializedName(ApiConstants.RESOURCE_TYPE) + @Param(description = "resource type") + private ResourceTag.ResourceObjectType resourceType; + + @SerializedName(ApiConstants.RESOURCE_ID) + @Param(description = "id of the resource") + private String resourceId; + + @SerializedName(ApiConstants.BASE64_IMAGE) + @Param(description = "base64 representation of resource icon") + private String image; + + public ResourceTag.ResourceObjectType getResourceType() { + return resourceType; + } + + public void setResourceType(ResourceTag.ResourceObjectType resourceType) { + this.resourceType = resourceType; + } + + public String getResourceId() { + return resourceId; + } + + public void setResourceId(String resourceId) { + this.resourceId = resourceId; + } + + public String getImage() { + return image; + } + + public void setImage(String image) { + this.image = image; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/SetResourceIconResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/SetResourceIconResponse.java new file mode 100644 index 00000000000..9af1afea715 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/SetResourceIconResponse.java @@ -0,0 +1,21 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.response; + +public interface SetResourceIconResponse { + void setResourceIconResponse(ResourceIconResponse icon); +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/TemplateResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/TemplateResponse.java index 3633fa73de1..892b5b85262 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/TemplateResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/TemplateResponse.java @@ -34,7 +34,7 @@ import com.google.gson.annotations.SerializedName; @EntityReference(value = VirtualMachineTemplate.class) @SuppressWarnings("unused") -public class TemplateResponse extends BaseResponseWithTagInformation implements ControlledViewEntityResponse { +public class TemplateResponse extends BaseResponseWithTagInformation implements ControlledViewEntityResponse, SetResourceIconResponse { @SerializedName(ApiConstants.ID) @Param(description = "the template ID") private String id; @@ -223,6 +223,10 @@ public class TemplateResponse extends BaseResponseWithTagInformation implements @Param(description = "the URL which the template/iso is registered from") private String url; + @SerializedName(ApiConstants.RESOURCE_ICON) + @Param(description = "Base64 string representation of the resource icon", since = "4.16.0.0") + ResourceIconResponse icon; + public TemplateResponse() { tags = new LinkedHashSet<>(); } @@ -458,4 +462,9 @@ public class TemplateResponse extends BaseResponseWithTagInformation implements public void setUrl(String url) { this.url = url; } + + @Override + public void setResourceIconResponse(ResourceIconResponse icon) { + this.icon = icon; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/UserResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/UserResponse.java index dd10510c03c..260d1602f82 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/UserResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/UserResponse.java @@ -29,7 +29,7 @@ import com.cloud.serializer.Param; import com.cloud.user.User; @EntityReference(value = User.class) -public class UserResponse extends BaseResponse { +public class UserResponse extends BaseResponse implements SetResourceIconResponse { @SerializedName("id") @Param(description = "the user ID") private String id; @@ -115,6 +115,10 @@ public class UserResponse extends BaseResponse { @Param(description = "true if user is default, false otherwise", since = "4.2.0") private Boolean isDefault; + @SerializedName(ApiConstants.RESOURCE_ICON) + @Param(description = "Base64 string representation of the resource icon", since = "4.16.0.0") + ResourceIconResponse icon; + @Override public String getObjectId() { return this.getId(); @@ -275,4 +279,9 @@ public class UserResponse extends BaseResponse { this.userSource = User.Source.NATIVE.toString().toLowerCase(); } } + + @Override + public void setResourceIconResponse(ResourceIconResponse icon) { + this.icon = icon; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java index 11c1581f2af..3483c17f474 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java @@ -37,7 +37,7 @@ import com.google.gson.annotations.SerializedName; @SuppressWarnings("unused") @EntityReference(value = {VirtualMachine.class, UserVm.class, VirtualRouter.class}) -public class UserVmResponse extends BaseResponseWithTagInformation implements ControlledEntityResponse { +public class UserVmResponse extends BaseResponseWithTagInformation implements ControlledEntityResponse, SetResourceIconResponse { @SerializedName(ApiConstants.ID) @Param(description = "the ID of the virtual machine") private String id; @@ -328,6 +328,10 @@ public class UserVmResponse extends BaseResponseWithTagInformation implements Co @Param(description = "the total number of network traffic bytes sent") private Long bytesSent; + @SerializedName(ApiConstants.RESOURCE_ICON) + @Param(description = "Base64 string representation of the resource icon", since = "4.16.0.0") + ResourceIconResponse resourceIconResponse; + public UserVmResponse() { securityGroupList = new LinkedHashSet(); nics = new TreeSet<>(Comparator.comparingInt(x -> Integer.parseInt(x.getDeviceId()))); @@ -924,6 +928,15 @@ public class UserVmResponse extends BaseResponseWithTagInformation implements Co public void setPoolType(String poolType) { this.poolType = poolType; } + @Override + public void setResourceIconResponse(ResourceIconResponse resourceIconResponse) { + this.resourceIconResponse = resourceIconResponse; + } + + public ResourceIconResponse getResourceIconResponse() { + return resourceIconResponse; + } + public void setLastUpdated(Date lastUpdated) { this.lastUpdated = lastUpdated; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/VpcResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/VpcResponse.java index f91945f3bcf..151c3a9561b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/VpcResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/VpcResponse.java @@ -30,7 +30,7 @@ import com.google.gson.annotations.SerializedName; @EntityReference(value = Vpc.class) @SuppressWarnings("unused") -public class VpcResponse extends BaseResponseWithAnnotations implements ControlledEntityResponse { +public class VpcResponse extends BaseResponseWithAnnotations implements ControlledEntityResponse, SetResourceIconResponse { @SerializedName("id") @Param(description = "the id of the VPC") private String id; @@ -127,10 +127,18 @@ public class VpcResponse extends BaseResponseWithAnnotations implements Controll @Param(description = "if this VPC has redundant router", since = "4.6") private boolean redundantRouter; + @SerializedName(ApiConstants.RESOURCE_ICON) + @Param(description = "Base64 string representation of the resource icon", since = "4.16.0.0") + ResourceIconResponse icon; + public void setId(final String id) { this.id = id; } + public String getId() { + return id; + } + public void setName(final String name) { this.name = name; } @@ -231,4 +239,9 @@ public class VpcResponse extends BaseResponseWithAnnotations implements Controll public void setRedundantRouter(final Boolean redundantRouter) { this.redundantRouter = redundantRouter; } + + @Override + public void setResourceIconResponse(ResourceIconResponse icon) { + this.icon = icon; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ZoneResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ZoneResponse.java index 47f3b0d050c..676ca4a7dbf 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ZoneResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ZoneResponse.java @@ -32,7 +32,7 @@ import com.google.gson.annotations.SerializedName; @SuppressWarnings("unused") @EntityReference(value = DataCenter.class) -public class ZoneResponse extends BaseResponseWithAnnotations { +public class ZoneResponse extends BaseResponseWithAnnotations implements SetResourceIconResponse { @SerializedName(ApiConstants.ID) @Param(description = "Zone id") private String id; @@ -125,6 +125,10 @@ public class ZoneResponse extends BaseResponseWithAnnotations { @Param(description = "Meta data associated with the zone (key/value pairs)", since = "4.3.0") private Map resourceDetails; + @SerializedName(ApiConstants.RESOURCE_ICON) + @Param(description = "Base64 string representation of the resource icon", since = "4.16.0.0") + ResourceIconResponse resourceIconResponse; + public ZoneResponse() { tags = new LinkedHashSet(); } @@ -315,4 +319,13 @@ public class ZoneResponse extends BaseResponseWithAnnotations { public Map getResourceDetails() { return resourceDetails; } + + @Override + public void setResourceIconResponse(ResourceIconResponse resourceIconResponse) { + this.resourceIconResponse = resourceIconResponse; + } + + public ResourceIconResponse getResourceIconResponse() { + return resourceIconResponse; + } } diff --git a/api/src/main/java/org/apache/cloudstack/query/QueryService.java b/api/src/main/java/org/apache/cloudstack/query/QueryService.java index 3484de84ef4..bb418f98408 100644 --- a/api/src/main/java/org/apache/cloudstack/query/QueryService.java +++ b/api/src/main/java/org/apache/cloudstack/query/QueryService.java @@ -24,6 +24,7 @@ import org.apache.cloudstack.api.command.admin.host.ListHostTagsCmd; import org.apache.cloudstack.api.command.admin.host.ListHostsCmd; import org.apache.cloudstack.api.command.admin.internallb.ListInternalLBVMsCmd; import org.apache.cloudstack.api.command.admin.management.ListMgmtsCmd; +import org.apache.cloudstack.api.command.admin.resource.icon.ListResourceIconCmd; import org.apache.cloudstack.api.command.admin.router.GetRouterHealthCheckResultsCmd; import org.apache.cloudstack.api.command.admin.router.ListRoutersCmd; import org.apache.cloudstack.api.command.admin.storage.ListImageStoresCmd; @@ -67,6 +68,7 @@ import org.apache.cloudstack.api.response.ProjectAccountResponse; import org.apache.cloudstack.api.response.ProjectInvitationResponse; import org.apache.cloudstack.api.response.ProjectResponse; import org.apache.cloudstack.api.response.ResourceDetailResponse; +import org.apache.cloudstack.api.response.ResourceIconResponse; import org.apache.cloudstack.api.response.ResourceTagResponse; import org.apache.cloudstack.api.response.RouterHealthCheckResultResponse; import org.apache.cloudstack.api.response.SecurityGroupResponse; @@ -159,6 +161,8 @@ public interface QueryService { DetailOptionsResponse listDetailOptions(ListDetailOptionsCmd cmd); + ListResponse listResourceIcons(ListResourceIconCmd cmd); + ListResponse searchForAffinityGroups(ListAffinityGroupsCmd cmd); List listResourceDetails(ListResourceDetailsCmd cmd); diff --git a/engine/schema/src/main/java/com/cloud/resource/icon/ResourceIconVO.java b/engine/schema/src/main/java/com/cloud/resource/icon/ResourceIconVO.java new file mode 100644 index 00000000000..fb9e3935d0e --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/resource/icon/ResourceIconVO.java @@ -0,0 +1,167 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.resource.icon; + +import com.cloud.server.ResourceIcon; +import com.cloud.server.ResourceTag; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.GenerationType; +import javax.persistence.Column; +import javax.persistence.Enumerated; +import javax.persistence.EnumType; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import java.util.Date; +import java.util.UUID; + +@Entity +@Table(name = "resource_icon") +public class ResourceIconVO implements ResourceIcon { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "uuid") + private String uuid; + + @Column(name = "resource_id") + long resourceId; + + @Column(name = "resource_uuid") + private String resourceUuid; + + @Column(name = "resource_type") + @Enumerated(value = EnumType.STRING) + private ResourceTag.ResourceObjectType resourceType; + + @Column(name = "icon", length = 65535) + private String icon; + + @Column(name = "created") + @Temporal(value = TemporalType.TIMESTAMP) + private Date created = null; + + @Column(name = "updated") + @Temporal(value = TemporalType.TIMESTAMP) + Date updated; + + @Column(name = "removed") + @Temporal(value = TemporalType.TIMESTAMP) + private Date removed; + + protected ResourceIconVO() { + uuid = UUID.randomUUID().toString(); + } + + public ResourceIconVO(long resourceId, ResourceTag.ResourceObjectType resourceType, String resourceUuid, String icon) { + super(); + this.resourceId = resourceId; + this.resourceType = resourceType; + uuid = UUID.randomUUID().toString(); + this.resourceUuid = resourceUuid; + this.icon = icon; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public long getResourceId() { + return resourceId; + } + + public void setResourceId(long resourceId) { + this.resourceId = resourceId; + } + + public String getResourceUuid() { + return resourceUuid; + } + + public void setResourceUuid(String resourceUuid) { + this.resourceUuid = resourceUuid; + } + + public ResourceTag.ResourceObjectType getResourceType() { + return resourceType; + } + + public void setResourceType(ResourceTag.ResourceObjectType resourceType) { + this.resourceType = resourceType; + } + + public String getIcon() { + return icon; + } + + public void setIcon(String icon) { + this.icon = icon; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public Date getUpdated() { + return updated; + } + + public void setUpdated(Date updated) { + this.updated = updated; + } + + public Date getRemoved() { + return removed; + } + + public void setRemoved(Date removed) { + this.removed = removed; + } + + @Override + public String toString() { + return "ResourceIconVO{" + + "id=" + id + + ", uuid='" + uuid + '\'' + + ", resourceId=" + resourceId + + ", resourceUuid='" + resourceUuid + '\'' + + ", resourceType=" + resourceType + + '}'; + } +} diff --git a/engine/schema/src/main/java/com/cloud/resource/icon/dao/ResourceIconDao.java b/engine/schema/src/main/java/com/cloud/resource/icon/dao/ResourceIconDao.java new file mode 100644 index 00000000000..3724e03d9d0 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/resource/icon/dao/ResourceIconDao.java @@ -0,0 +1,31 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.resource.icon.dao; + +import com.cloud.resource.icon.ResourceIconVO; +import com.cloud.server.ResourceIcon; +import com.cloud.server.ResourceTag; +import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.api.response.ResourceIconResponse; + +import java.util.List; + +public interface ResourceIconDao extends GenericDao { + ResourceIconResponse newResourceIconResponse(ResourceIcon resourceIconVO); + ResourceIconVO findByResourceUuid(String resourceUuid, ResourceTag.ResourceObjectType resourceType); + List listResourceIcons(List resourceUuids, ResourceTag.ResourceObjectType resourceType); +} diff --git a/engine/schema/src/main/java/com/cloud/resource/icon/dao/ResourceIconDaoImpl.java b/engine/schema/src/main/java/com/cloud/resource/icon/dao/ResourceIconDaoImpl.java new file mode 100644 index 00000000000..41eba40c01f --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/resource/icon/dao/ResourceIconDaoImpl.java @@ -0,0 +1,79 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.resource.icon.dao; + +import com.cloud.resource.icon.ResourceIconVO; +import com.cloud.server.ResourceIcon; +import com.cloud.server.ResourceTag; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.response.ResourceIconResponse; +import org.apache.log4j.Logger; + +import java.util.ArrayList; +import java.util.List; + +public class ResourceIconDaoImpl extends GenericDaoBase implements ResourceIconDao { + public static final Logger s_logger = Logger.getLogger(ResourceIconDaoImpl.class); + private final SearchBuilder AllFieldsSearch; + + protected ResourceIconDaoImpl() { + AllFieldsSearch = createSearchBuilder(); + AllFieldsSearch.and("resourceId", AllFieldsSearch.entity().getResourceId(), SearchCriteria.Op.EQ); + AllFieldsSearch.and("uuid", AllFieldsSearch.entity().getResourceUuid(), SearchCriteria.Op.IN); + AllFieldsSearch.and("resourceType", AllFieldsSearch.entity().getResourceType(), SearchCriteria.Op.EQ); + AllFieldsSearch.done(); + } + + @Override + public ResourceIconResponse newResourceIconResponse(ResourceIcon resourceIcon) { + ResourceIconResponse resourceIconResponse = new ResourceIconResponse(); + resourceIconResponse.setResourceId(resourceIcon.getResourceUuid()); + resourceIconResponse.setResourceType(resourceIcon.getResourceType()); + resourceIconResponse.setImage(resourceIcon.getIcon()); + resourceIconResponse.setObjectName(ApiConstants.RESOURCE_ICON); + return resourceIconResponse; + } + + @Override + public ResourceIconVO findByResourceUuid(String resourceUuid, ResourceTag.ResourceObjectType resourceType) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("uuid", (Object[]) new String[]{resourceUuid}); + sc.setParameters("resourceType", resourceType); + return findOneBy(sc); + } + + @Override + public List listResourceIcons(List resourceUuids, ResourceTag.ResourceObjectType resourceType) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("uuid", resourceUuids.toArray()); + sc.setParameters("resourceType", resourceType); + List resourceIcons = listBy(sc); + List iconResponses = new ArrayList<>(); + for (ResourceIconVO resourceIcon : resourceIcons) { + ResourceIconResponse response = new ResourceIconResponse(); + response.setResourceId(resourceIcon.getResourceUuid()); + response.setResourceType(resourceIcon.getResourceType()); + response.setImage(resourceIcon.getIcon()); + response.setObjectName(ApiConstants.RESOURCE_ICON); + iconResponses.add(response); + } + return iconResponses; + } +} diff --git a/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml b/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml index 362054ece34..508b01c2b57 100644 --- a/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml +++ b/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml @@ -173,6 +173,7 @@ + diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41520to41600.sql b/engine/schema/src/main/resources/META-INF/db/schema-41520to41600.sql index cd595ee46a0..64c381e0e7a 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41520to41600.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41520to41600.sql @@ -697,6 +697,20 @@ CREATE VIEW `cloud`.`host_view` AS GROUP BY `host`.`id`; +CREATE TABLE `cloud`.`resource_icon` ( + `id` bigint unsigned NOT NULL auto_increment COMMENT 'id', + `uuid` varchar(40), + `icon` blob COMMENT 'Base64 version of the resource icon', + `resource_id` bigint unsigned NOT NULL, + `resource_uuid` varchar(40), + `resource_type` varchar(255), + `updated` datetime default NULL, + `created` datetime default NULL, + `removed` datetime default NULL, + PRIMARY KEY (`id`), + CONSTRAINT `uc_resource_icon__uuid` UNIQUE (`uuid`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + ALTER TABLE `cloud`.`annotations` ADD COLUMN `admins_only` tinyint(1) unsigned NOT NULL DEFAULT 1; -- Allow annotations for resource admins, domain admins and users diff --git a/plugins/metrics/src/main/java/org/apache/cloudstack/api/ListVMsMetricsCmd.java b/plugins/metrics/src/main/java/org/apache/cloudstack/api/ListVMsMetricsCmd.java index 9b8564c2c8b..947c2f99ba9 100644 --- a/plugins/metrics/src/main/java/org/apache/cloudstack/api/ListVMsMetricsCmd.java +++ b/plugins/metrics/src/main/java/org/apache/cloudstack/api/ListVMsMetricsCmd.java @@ -44,6 +44,7 @@ public class ListVMsMetricsCmd extends ListVMsCmd { @Override public void execute() { ListResponse userVms = _queryService.searchForUserVMs(this); + updateVMResponse(userVms.getResponses()); final List metricsResponses = metricsService.listVmMetrics(userVms.getResponses()); ListResponse response = new ListResponse<>(); response.setResponses(metricsResponses, userVms.getCount()); diff --git a/server/src/main/java/com/cloud/api/ApiDBUtils.java b/server/src/main/java/com/cloud/api/ApiDBUtils.java index d688c276a33..dd46751552a 100644 --- a/server/src/main/java/com/cloud/api/ApiDBUtils.java +++ b/server/src/main/java/com/cloud/api/ApiDBUtils.java @@ -27,6 +27,9 @@ import java.util.Set; import javax.annotation.PostConstruct; import javax.inject.Inject; +import com.cloud.resource.icon.ResourceIconVO; +import com.cloud.resource.icon.dao.ResourceIconDao; +import com.cloud.server.ResourceIcon; import org.apache.cloudstack.acl.Role; import org.apache.cloudstack.acl.RoleService; import org.apache.cloudstack.affinity.AffinityGroup; @@ -55,6 +58,7 @@ import org.apache.cloudstack.api.response.NetworkOfferingResponse; import org.apache.cloudstack.api.response.ProjectAccountResponse; import org.apache.cloudstack.api.response.ProjectInvitationResponse; import org.apache.cloudstack.api.response.ProjectResponse; +import org.apache.cloudstack.api.response.ResourceIconResponse; import org.apache.cloudstack.api.response.ResourceTagResponse; import org.apache.cloudstack.api.response.SecurityGroupResponse; import org.apache.cloudstack.api.response.ServiceOfferingResponse; @@ -258,6 +262,7 @@ import com.cloud.server.ResourceTag; import com.cloud.server.ResourceTag.ResourceObjectType; import com.cloud.server.StatsCollector; import com.cloud.server.TaggedResourceService; +import com.cloud.server.ResourceManagerUtil; import com.cloud.service.ServiceOfferingDetailsVO; import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; @@ -418,6 +423,7 @@ public class ApiDBUtils { static AutoScaleVmGroupDao s_asVmGroupDao; static CounterDao s_counterDao; static ResourceTagJoinDao s_tagJoinDao; + static ResourceIconDao s_resourceIconDao; static EventJoinDao s_eventJoinDao; static InstanceGroupJoinDao s_vmGroupJoinDao; static UserAccountJoinDao s_userAccountJoinDao; @@ -462,6 +468,7 @@ public class ApiDBUtils { static BackupScheduleDao s_backupScheduleDao; static BackupOfferingDao s_backupOfferingDao; static NicDao s_nicDao; + static ResourceManagerUtil s_resourceManagerUtil; @Inject private ManagementServer ms; @@ -708,6 +715,10 @@ public class ApiDBUtils { private BackupScheduleDao backupScheduleDao; @Inject private NicDao nicDao; + @Inject + private ResourceIconDao resourceIconDao; + @Inject + private ResourceManagerUtil resourceManagerUtil; @PostConstruct void init() { @@ -834,6 +845,8 @@ public class ApiDBUtils { s_backupDao = backupDao; s_backupScheduleDao = backupScheduleDao; s_backupOfferingDao = backupOfferingDao; + s_resourceIconDao = resourceIconDao; + s_resourceManagerUtil = resourceManagerUtil; } // /////////////////////////////////////////////////////////// @@ -1484,7 +1497,7 @@ public class ApiDBUtils { } public static String getUuid(String resourceId, ResourceObjectType resourceType) { - return s_taggedResourceService.getUuid(resourceId, resourceType); + return s_resourceManagerUtil.getUuid(resourceId, resourceType); } public static List listByResourceTypeAndId(ResourceObjectType type, long resourceId) { @@ -1800,6 +1813,10 @@ public class ApiDBUtils { return s_tagJoinDao.newResourceTagResponse(vsg, keyValueOnly); } + public static ResourceIconResponse newResourceIconResponse(ResourceIcon resourceIcon) { + return s_resourceIconDao.newResourceIconResponse(resourceIcon); + } + public static ResourceTagJoinVO newResourceTagView(ResourceTag sg) { return s_tagJoinDao.newResourceTagView(sg); } @@ -2001,8 +2018,8 @@ public class ApiDBUtils { return s_serviceOfferingJoinDao.newServiceOfferingView(offering); } - public static ZoneResponse newDataCenterResponse(ResponseView view, DataCenterJoinVO dc, Boolean showCapacities) { - return s_dcJoinDao.newDataCenterResponse(view, dc, showCapacities); + public static ZoneResponse newDataCenterResponse(ResponseView view, DataCenterJoinVO dc, Boolean showCapacities, Boolean showResourceImage) { + return s_dcJoinDao.newDataCenterResponse(view, dc, showCapacities, showResourceImage); } public static DataCenterJoinVO newDataCenterView(DataCenter dc) { @@ -2081,6 +2098,10 @@ public class ApiDBUtils { return s_tagJoinDao.listBy(resourceUUID, resourceType); } + public static ResourceIconVO getResourceIconByResourceUUID(String resourceUUID, ResourceObjectType resourceType) { + return s_resourceIconDao.findByResourceUuid(resourceUUID, resourceType); + } + public static BackupResponse newBackupResponse(Backup backup) { return s_backupDao.newBackupResponse(backup); } diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index 09057e64277..bb79998b306 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -33,6 +33,7 @@ import java.util.stream.Collectors; import javax.inject.Inject; +import com.cloud.server.ResourceIcon; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.affinity.AffinityGroup; @@ -113,6 +114,7 @@ import org.apache.cloudstack.api.response.ProviderResponse; import org.apache.cloudstack.api.response.RegionResponse; import org.apache.cloudstack.api.response.RemoteAccessVpnResponse; import org.apache.cloudstack.api.response.ResourceCountResponse; +import org.apache.cloudstack.api.response.ResourceIconResponse; import org.apache.cloudstack.api.response.ResourceLimitResponse; import org.apache.cloudstack.api.response.ResourceTagResponse; import org.apache.cloudstack.api.response.RollingMaintenanceHostSkippedResponse; @@ -1151,9 +1153,9 @@ public class ApiResponseHelper implements ResponseGenerator { } @Override - public ZoneResponse createZoneResponse(ResponseView view, DataCenter dataCenter, Boolean showCapacities) { + public ZoneResponse createZoneResponse(ResponseView view, DataCenter dataCenter, Boolean showCapacities, Boolean showResourceIcon) { DataCenterJoinVO vOffering = ApiDBUtils.newDataCenterView(dataCenter); - return ApiDBUtils.newDataCenterResponse(view, vOffering, showCapacities); + return ApiDBUtils.newDataCenterResponse(view, vOffering, showCapacities, showResourceIcon); } public static List getDataCenterCapacityResponse(Long zoneId) { @@ -4457,4 +4459,9 @@ public class ApiResponseHelper implements ResponseGenerator { response.setObjectName("rollingmaintenance"); return response; } + + @Override + public ResourceIconResponse createResourceIconResponse(ResourceIcon resourceIcon) { + return ApiDBUtils.newResourceIconResponse(resourceIcon); + } } diff --git a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java index 2041e9207f6..f61fa337a03 100644 --- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java +++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java @@ -30,6 +30,10 @@ import java.util.stream.Stream; import javax.inject.Inject; +import com.cloud.resource.icon.dao.ResourceIconDao; +import com.cloud.server.ResourceManagerUtil; +import com.cloud.storage.dao.VMTemplateDetailsDao; +import com.cloud.vm.VirtualMachineManager; import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.affinity.AffinityGroupDomainMapVO; import org.apache.cloudstack.affinity.AffinityGroupResponse; @@ -48,6 +52,7 @@ import org.apache.cloudstack.api.command.admin.host.ListHostsCmd; import org.apache.cloudstack.api.command.admin.internallb.ListInternalLBVMsCmd; import org.apache.cloudstack.api.command.admin.iso.ListIsosCmdByAdmin; import org.apache.cloudstack.api.command.admin.management.ListMgmtsCmd; +import org.apache.cloudstack.api.command.admin.resource.icon.ListResourceIconCmd; import org.apache.cloudstack.api.command.admin.router.GetRouterHealthCheckResultsCmd; import org.apache.cloudstack.api.command.admin.router.ListRoutersCmd; import org.apache.cloudstack.api.command.admin.storage.ListImageStoresCmd; @@ -93,6 +98,7 @@ import org.apache.cloudstack.api.response.ProjectAccountResponse; import org.apache.cloudstack.api.response.ProjectInvitationResponse; import org.apache.cloudstack.api.response.ProjectResponse; import org.apache.cloudstack.api.response.ResourceDetailResponse; +import org.apache.cloudstack.api.response.ResourceIconResponse; import org.apache.cloudstack.api.response.ResourceTagResponse; import org.apache.cloudstack.api.response.RouterHealthCheckResultResponse; import org.apache.cloudstack.api.response.SecurityGroupResponse; @@ -217,7 +223,6 @@ import com.cloud.storage.VMTemplateVO; import com.cloud.storage.Volume; import com.cloud.storage.dao.StoragePoolTagsDao; import com.cloud.storage.dao.VMTemplateDao; -import com.cloud.storage.dao.VMTemplateDetailsDao; import com.cloud.tags.ResourceTagVO; import com.cloud.tags.dao.ResourceTagDao; import com.cloud.template.VirtualMachineTemplate.State; @@ -244,7 +249,6 @@ import com.cloud.vm.DomainRouterVO; import com.cloud.vm.UserVmVO; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; -import com.cloud.vm.VirtualMachineManager; import com.cloud.vm.VmDetailConstants; import com.cloud.vm.dao.DomainRouterDao; import com.cloud.vm.dao.UserVmDao; @@ -383,6 +387,9 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q @Inject private TaggedResourceService _taggedResourceMgr; + @Inject + private ResourceManagerUtil resourceManagerUtil; + @Inject private AffinityGroupVMMapDao _affinityGroupVMMapDao; @@ -437,6 +444,9 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q @Inject private VirtualMachineManager virtualMachineManager; + @Inject + private ResourceIconDao resourceIconDao; + /* * (non-Javadoc) * @@ -3145,7 +3155,7 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q respView = ResponseView.Full; } - List dcResponses = ViewResponseHelper.createDataCenterResponse(respView, cmd.getShowCapacities(), result.first().toArray(new DataCenterJoinVO[result.first().size()])); + List dcResponses = ViewResponseHelper.createDataCenterResponse(respView, cmd.getShowCapacities(), cmd.getShowIcon(), result.first().toArray(new DataCenterJoinVO[result.first().size()])); response.setResponses(dcResponses, result.second()); return response; } @@ -3792,6 +3802,13 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q return new DetailOptionsResponse(options); } + @Override + public ListResponse listResourceIcons(ListResourceIconCmd cmd) { + ListResponse responses = new ListResponse<>(); + responses.setResponses(resourceIconDao.listResourceIcons(cmd.getResourceIds(), cmd.getResourceType())); + return responses; + } + private void fillVMOrTemplateDetailOptions(final Map> options, final HypervisorType hypervisorType) { if (options == null) { throw new CloudRuntimeException("Invalid/null detail-options response object passed"); @@ -4066,7 +4083,7 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q //Validation - 1.3 if (resourceIdStr != null) { - resourceId = _taggedResourceMgr.getResourceId(resourceIdStr, resourceType); + resourceId = resourceManagerUtil.getResourceId(resourceIdStr, resourceType); if (resourceId == null) { throw new InvalidParameterValueException("Cannot find resource with resourceId " + resourceIdStr + " and of resource type " + resourceType); } @@ -4102,7 +4119,7 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q protected ResourceDetailResponse createResourceDetailsResponse(ResourceDetail requestedDetail, ResourceTag.ResourceObjectType resourceType) { ResourceDetailResponse resourceDetailResponse = new ResourceDetailResponse(); - resourceDetailResponse.setResourceId(_taggedResourceMgr.getUuid(String.valueOf(requestedDetail.getResourceId()), resourceType)); + resourceDetailResponse.setResourceId(resourceManagerUtil.getUuid(String.valueOf(requestedDetail.getResourceId()), resourceType)); resourceDetailResponse.setName(requestedDetail.getName()); resourceDetailResponse.setValue(requestedDetail.getValue()); resourceDetailResponse.setForDisplay(requestedDetail.isDisplay()); diff --git a/server/src/main/java/com/cloud/api/query/ViewResponseHelper.java b/server/src/main/java/com/cloud/api/query/ViewResponseHelper.java index ec3397407f2..ed6f9514ab9 100644 --- a/server/src/main/java/com/cloud/api/query/ViewResponseHelper.java +++ b/server/src/main/java/com/cloud/api/query/ViewResponseHelper.java @@ -560,10 +560,10 @@ public class ViewResponseHelper { return respList; } - public static List createDataCenterResponse(ResponseView view, Boolean showCapacities, DataCenterJoinVO... dcs) { + public static List createDataCenterResponse(ResponseView view, Boolean showCapacities, Boolean showResourceImage, DataCenterJoinVO... dcs) { List respList = new ArrayList(); for (DataCenterJoinVO vt : dcs){ - respList.add(ApiDBUtils.newDataCenterResponse(view, vt, showCapacities)); + respList.add(ApiDBUtils.newDataCenterResponse(view, vt, showCapacities, showResourceImage)); } return respList; } diff --git a/server/src/main/java/com/cloud/api/query/dao/DataCenterJoinDao.java b/server/src/main/java/com/cloud/api/query/dao/DataCenterJoinDao.java index 1c3ff1b2741..a53f86495e7 100644 --- a/server/src/main/java/com/cloud/api/query/dao/DataCenterJoinDao.java +++ b/server/src/main/java/com/cloud/api/query/dao/DataCenterJoinDao.java @@ -25,7 +25,7 @@ import com.cloud.utils.db.GenericDao; public interface DataCenterJoinDao extends GenericDao { - ZoneResponse newDataCenterResponse(ResponseView view, DataCenterJoinVO dof, Boolean showCapacities); + ZoneResponse newDataCenterResponse(ResponseView view, DataCenterJoinVO dof, Boolean showCapacities, Boolean showResourceImage); DataCenterJoinVO newDataCenterView(DataCenter dof); } diff --git a/server/src/main/java/com/cloud/api/query/dao/DataCenterJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/DataCenterJoinDaoImpl.java index c777e65f171..3762484f176 100644 --- a/server/src/main/java/com/cloud/api/query/dao/DataCenterJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/DataCenterJoinDaoImpl.java @@ -20,6 +20,8 @@ import java.util.List; import javax.inject.Inject; +import com.cloud.resource.icon.ResourceIconVO; +import org.apache.cloudstack.api.response.ResourceIconResponse; import org.apache.cloudstack.annotation.AnnotationService; import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.context.CallContext; @@ -61,7 +63,7 @@ public class DataCenterJoinDaoImpl extends GenericDaoBase> s_daoMap = new HashMap>(); @@ -189,7 +192,7 @@ public class ResourceMetaDataManagerImpl extends ManagerBase implements Resource } DetailDaoHelper newDetailDaoHelper = new DetailDaoHelper(resourceType); - newDetailDaoHelper.addDetail(_taggedResourceMgr.getResourceId(resourceId, resourceType), key, value, forDisplay); + newDetailDaoHelper.addDetail(resourceManagerUtil.getResourceId(resourceId, resourceType), key, value, forDisplay); } return true; @@ -201,7 +204,7 @@ public class ResourceMetaDataManagerImpl extends ManagerBase implements Resource @DB @ActionEvent(eventType = EventTypes.EVENT_RESOURCE_DETAILS_DELETE, eventDescription = "deleting resource meta data") public boolean deleteResourceMetaData(String resourceId, ResourceObjectType resourceType, String key) { - long id = _taggedResourceMgr.getResourceId(resourceId, resourceType); + long id = resourceManagerUtil.getResourceId(resourceId, resourceType); DetailDaoHelper newDetailDaoHelper = new DetailDaoHelper(resourceType); if (key != null) { diff --git a/server/src/main/java/com/cloud/resourceicon/ResourceIconManagerImpl.java b/server/src/main/java/com/cloud/resourceicon/ResourceIconManagerImpl.java new file mode 100644 index 00000000000..137f655459b --- /dev/null +++ b/server/src/main/java/com/cloud/resourceicon/ResourceIconManagerImpl.java @@ -0,0 +1,230 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.resourceicon; + +import com.cloud.domain.PartOf; +import com.cloud.event.ActionEvent; +import com.cloud.event.EventTypes; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.metadata.ResourceMetaDataManagerImpl; +import com.cloud.network.security.SecurityGroupRuleVO; +import com.cloud.network.security.SecurityGroupVO; +import com.cloud.network.vpc.NetworkACLItemVO; +import com.cloud.network.vpc.NetworkACLVO; +import com.cloud.network.vpc.VpcVO; +import com.cloud.projects.ProjectVO; +import com.cloud.resource.icon.dao.ResourceIconDao; +import com.cloud.server.ResourceIcon; +import com.cloud.server.ResourceIconManager; + +import com.cloud.server.ResourceManagerUtil; +import com.cloud.server.ResourceTag; +import com.cloud.resource.icon.ResourceIconVO; +import com.cloud.storage.SnapshotPolicyVO; +import com.cloud.storage.VolumeVO; +import com.cloud.tags.ResourceManagerUtilImpl; +import com.cloud.user.Account; +import com.cloud.user.AccountService; +import com.cloud.user.AccountVO; +import com.cloud.user.OwnedBy; +import com.cloud.user.dao.AccountDao; +import com.cloud.utils.Pair; +import com.cloud.utils.component.ManagerBase; +import com.cloud.utils.db.DB; +import com.cloud.utils.db.EntityManager; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallbackNoReturn; +import com.cloud.utils.db.TransactionStatus; +import com.cloud.utils.exception.CloudRuntimeException; +import org.apache.cloudstack.context.CallContext; +import org.apache.log4j.Logger; + +import javax.inject.Inject; +import javax.persistence.EntityExistsException; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; + +public class ResourceIconManagerImpl extends ManagerBase implements ResourceIconManager { + public static final Logger s_logger = Logger.getLogger(ResourceMetaDataManagerImpl.class); + + @Inject + AccountService accountService; + @Inject + ResourceManagerUtil resourceManagerUtil; + @Inject + ResourceIconDao resourceIconDao; + @Inject + EntityManager entityMgr; + @Inject + AccountDao accountDao; + + private Pair getAccountDomain(long resourceId, ResourceTag.ResourceObjectType resourceType) { + Class clazz = ResourceManagerUtilImpl.s_typeMap.get(resourceType); + + Object entity = entityMgr.findById(clazz, resourceId); + Long accountId = null; + Long domainId = null; + + // if the resource type is a security group rule, get the accountId and domainId from the security group itself + if (resourceType == ResourceTag.ResourceObjectType.SecurityGroupRule) { + SecurityGroupRuleVO rule = (SecurityGroupRuleVO)entity; + Object SecurityGroup = entityMgr.findById(ResourceManagerUtilImpl.s_typeMap.get(ResourceTag.ResourceObjectType.SecurityGroup), rule.getSecurityGroupId()); + + accountId = ((SecurityGroupVO)SecurityGroup).getAccountId(); + domainId = ((SecurityGroupVO)SecurityGroup).getDomainId(); + } + + if (resourceType == ResourceTag.ResourceObjectType.Account) { + AccountVO account = (AccountVO)entity; + accountId = account.getId(); + domainId = account.getDomainId(); + } + + // if the resource type is network acl, get the accountId and domainId from VPC following: NetworkACLItem -> NetworkACL -> VPC + if (resourceType == ResourceTag.ResourceObjectType.NetworkACL) { + NetworkACLItemVO aclItem = (NetworkACLItemVO)entity; + Object networkACL = entityMgr.findById(ResourceManagerUtilImpl.s_typeMap.get(ResourceTag.ResourceObjectType.NetworkACLList), aclItem.getAclId()); + Long vpcId = ((NetworkACLVO)networkACL).getVpcId(); + + if (vpcId != null && vpcId != 0) { + Object vpc = entityMgr.findById(ResourceManagerUtilImpl.s_typeMap.get(ResourceTag.ResourceObjectType.Vpc), vpcId); + + accountId = ((VpcVO)vpc).getAccountId(); + domainId = ((VpcVO)vpc).getDomainId(); + } + } + + if (resourceType == ResourceTag.ResourceObjectType.Project) { + accountId = ((ProjectVO)entity).getProjectAccountId(); + } + + if (resourceType == ResourceTag.ResourceObjectType.SnapshotPolicy) { + accountId = entityMgr.findById(VolumeVO.class, ((SnapshotPolicyVO)entity).getVolumeId()).getAccountId(); + } + + if (entity instanceof OwnedBy) { + accountId = ((OwnedBy)entity).getAccountId(); + } + + if (entity instanceof PartOf) { + domainId = ((PartOf)entity).getDomainId(); + } + + if (accountId == null) { + accountId = Account.ACCOUNT_ID_SYSTEM; + } + + if ((domainId == null) || ((accountId != null) && (domainId.longValue() == -1))) { + domainId = accountDao.getDomainIdForGivenAccountId(accountId); + } + return new Pair<>(accountId, domainId); + } + + @Override + @DB + @ActionEvent(eventType = EventTypes.EVENT_RESOURCE_ICON_UPLOAD, eventDescription = "uploading resource icon") + public boolean uploadResourceIcon(List resourceIds, ResourceTag.ResourceObjectType resourceType, String base64Image) { + final Account caller = CallContext.current().getCallingAccount(); + + Transaction.execute(new TransactionCallbackNoReturn() { + @Override + public void doInTransactionWithoutResult(TransactionStatus status) { + for (String resourceId : resourceIds) { + if (!resourceType.resourceIconSupport()) { + throw new InvalidParameterValueException("The resource type " + resourceType + " doesn't support resource icons"); + } + + if (base64Image == null) { + throw new InvalidParameterValueException("No icon provided to be uploaded for resource: " + resourceId); + } + + long id = resourceManagerUtil.getResourceId(resourceId, resourceType); + String resourceUuid = resourceManagerUtil.getUuid(resourceId, resourceType); + ResourceIconVO existingResourceIcon = resourceIconDao.findByResourceUuid(resourceUuid, resourceType); + ResourceIconVO resourceIcon = null; + Pair accountDomainPair = getAccountDomain(id, resourceType); + Long domainId = accountDomainPair.second(); + Long accountId = accountDomainPair.first(); + resourceManagerUtil.checkResourceAccessible(accountId, domainId, String.format("Account ' %s ' doesn't have permissions to upload icon for resource ' %s ", caller, id)); + + if (existingResourceIcon == null) { + resourceIcon = new ResourceIconVO(id, resourceType, resourceUuid, base64Image); + } else { + resourceIcon = existingResourceIcon; + resourceIcon.setIcon(base64Image); + resourceIcon.setUpdated(new Date()); + } + try { + resourceIconDao.persist(resourceIcon); + } catch (EntityExistsException e) { + throw new CloudRuntimeException(String.format("Image already uploaded for resource type: %s with id %s", resourceType.toString(), resourceId),e); + } + } + } + }); + + return true; + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_RESOURCE_ICON_DELETE, eventDescription = "deleting resource icon") + public boolean deleteResourceIcon(List resourceIds, ResourceTag.ResourceObjectType resourceType) { + Account caller = CallContext.current().getCallingAccount(); + List resourceIcons = searchResourceIcons(resourceIds, resourceType); + if (resourceIcons.isEmpty()) { + s_logger.debug("No resource Icon(s) uploaded for the specified resources"); + return false; + } + Transaction.execute(new TransactionCallbackNoReturn() { + @Override + public void doInTransactionWithoutResult(TransactionStatus status) { + for (ResourceIcon resourceIcon : resourceIcons) { + String resourceId = resourceIcon.getResourceUuid(); + long id = resourceManagerUtil.getResourceId(resourceId, resourceType); + Pair accountDomainPair = getAccountDomain(id, resourceType); + Long domainId = accountDomainPair.second(); + Long accountId = accountDomainPair.first(); + resourceManagerUtil.checkResourceAccessible(accountId, domainId, String.format("Account ' %s ' doesn't have permissions to upload icon for resource ' %s ", caller, id)); + resourceIconDao.remove(resourceIcon.getId()); + s_logger.debug("Removed icon for resources (" + + String.join(", ", resourceIds) + ")"); + } + } + }); + return true; + } + + @Override + public ResourceIcon getByResourceTypeAndUuid(ResourceTag.ResourceObjectType type, String resourceId) { + return resourceIconDao.findByResourceUuid(resourceId, type); + } + + private List searchResourceIcons(List resourceIds, ResourceTag.ResourceObjectType resourceType) { + List resourceUuids = resourceIds.stream().map(resourceId -> resourceManagerUtil.getUuid(resourceId, resourceType)).collect(Collectors.toList()); + SearchBuilder sb = resourceIconDao.createSearchBuilder(); + sb.and("resourceUuid", sb.entity().getResourceUuid(), SearchCriteria.Op.IN); + sb.and("resourceType", sb.entity().getResourceType(), SearchCriteria.Op.EQ); + + SearchCriteria sc = sb.create(); + sc.setParameters("resourceUuid", resourceUuids.toArray()); + sc.setParameters("resourceType", resourceType); + return resourceIconDao.search(sc, null); + } +} diff --git a/server/src/main/java/com/cloud/server/ManagementServerImpl.java b/server/src/main/java/com/cloud/server/ManagementServerImpl.java index 9f419e0d897..f7cd7de3337 100644 --- a/server/src/main/java/com/cloud/server/ManagementServerImpl.java +++ b/server/src/main/java/com/cloud/server/ManagementServerImpl.java @@ -179,6 +179,9 @@ import org.apache.cloudstack.api.command.admin.resource.ListAlertsCmd; import org.apache.cloudstack.api.command.admin.resource.ListCapacityCmd; import org.apache.cloudstack.api.command.admin.resource.StartRollingMaintenanceCmd; import org.apache.cloudstack.api.command.admin.resource.UploadCustomCertificateCmd; +import org.apache.cloudstack.api.command.admin.resource.icon.DeleteResourceIconCmd; +import org.apache.cloudstack.api.command.admin.resource.icon.ListResourceIconCmd; +import org.apache.cloudstack.api.command.admin.resource.icon.UploadResourceIconCmd; import org.apache.cloudstack.api.command.admin.router.ConfigureOvsElementCmd; import org.apache.cloudstack.api.command.admin.router.ConfigureVirtualRouterElementCmd; import org.apache.cloudstack.api.command.admin.router.CreateVirtualRouterElementCmd; @@ -3446,6 +3449,9 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe cmdList.add(GetRouterHealthCheckResultsCmd.class); cmdList.add(StartRollingMaintenanceCmd.class); cmdList.add(MigrateSecondaryStorageDataCmd.class); + cmdList.add(UploadResourceIconCmd.class); + cmdList.add(DeleteResourceIconCmd.class); + cmdList.add(ListResourceIconCmd.class); // Out-of-band management APIs for admins cmdList.add(EnableOutOfBandManagementForHostCmd.class); diff --git a/server/src/main/java/com/cloud/tags/ResourceManagerUtilImpl.java b/server/src/main/java/com/cloud/tags/ResourceManagerUtilImpl.java new file mode 100644 index 00000000000..e088b26ad96 --- /dev/null +++ b/server/src/main/java/com/cloud/tags/ResourceManagerUtilImpl.java @@ -0,0 +1,186 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.tags; + +import com.cloud.dc.DataCenterVO; +import com.cloud.domain.DomainVO; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.PermissionDeniedException; +import com.cloud.network.LBHealthCheckPolicyVO; +import com.cloud.network.as.AutoScaleVmGroupVO; +import com.cloud.network.as.AutoScaleVmProfileVO; + +import com.cloud.network.rules.FirewallRuleVO; +import com.cloud.network.rules.PortForwardingRuleVO; +import com.cloud.network.security.SecurityGroupRuleVO; +import com.cloud.network.security.SecurityGroupVO; +import com.cloud.network.dao.IPAddressVO; +import com.cloud.network.dao.LBStickinessPolicyVO; +import com.cloud.network.dao.LoadBalancerVO; +import com.cloud.network.dao.NetworkVO; +import com.cloud.network.dao.RemoteAccessVpnVO; +import com.cloud.network.dao.Site2SiteCustomerGatewayVO; +import com.cloud.network.dao.Site2SiteVpnConnectionVO; +import com.cloud.network.dao.Site2SiteVpnGatewayVO; +import com.cloud.network.vpc.NetworkACLItemVO; +import com.cloud.network.vpc.NetworkACLVO; +import com.cloud.network.vpc.StaticRouteVO; +import com.cloud.network.vpc.VpcOfferingVO; +import com.cloud.network.vpc.VpcVO; +import com.cloud.offerings.NetworkOfferingVO; +import com.cloud.projects.ProjectVO; +import com.cloud.server.ResourceManagerUtil; +import com.cloud.server.ResourceTag; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.storage.SnapshotVO; +import com.cloud.storage.VMTemplateVO; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.DiskOfferingVO; +import com.cloud.storage.SnapshotPolicyVO; + +import com.cloud.user.Account; +import com.cloud.user.AccountManager; +import com.cloud.user.AccountVO; +import com.cloud.user.DomainManager; +import com.cloud.user.UserVO; +import com.cloud.utils.db.EntityManager; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.NicVO; +import com.cloud.vm.UserVmVO; +import com.cloud.vm.snapshot.VMSnapshotVO; +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.commons.lang.StringUtils; + +import javax.inject.Inject; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +public class ResourceManagerUtilImpl implements ResourceManagerUtil { + public static final Map> s_typeMap = new HashMap<>(); + + static { + s_typeMap.put(ResourceTag.ResourceObjectType.UserVm, UserVmVO.class); + s_typeMap.put(ResourceTag.ResourceObjectType.Volume, VolumeVO.class); + s_typeMap.put(ResourceTag.ResourceObjectType.Template, VMTemplateVO.class); + s_typeMap.put(ResourceTag.ResourceObjectType.ISO, VMTemplateVO.class); + s_typeMap.put(ResourceTag.ResourceObjectType.Snapshot, SnapshotVO.class); + s_typeMap.put(ResourceTag.ResourceObjectType.Network, NetworkVO.class); + s_typeMap.put(ResourceTag.ResourceObjectType.LoadBalancer, LoadBalancerVO.class); + s_typeMap.put(ResourceTag.ResourceObjectType.PortForwardingRule, PortForwardingRuleVO.class); + s_typeMap.put(ResourceTag.ResourceObjectType.FirewallRule, FirewallRuleVO.class); + s_typeMap.put(ResourceTag.ResourceObjectType.SecurityGroup, SecurityGroupVO.class); + s_typeMap.put(ResourceTag.ResourceObjectType.SecurityGroupRule, SecurityGroupRuleVO.class); + s_typeMap.put(ResourceTag.ResourceObjectType.PublicIpAddress, IPAddressVO.class); + s_typeMap.put(ResourceTag.ResourceObjectType.Project, ProjectVO.class); + s_typeMap.put(ResourceTag.ResourceObjectType.Account, AccountVO.class); + s_typeMap.put(ResourceTag.ResourceObjectType.Vpc, VpcVO.class); + s_typeMap.put(ResourceTag.ResourceObjectType.Nic, NicVO.class); + s_typeMap.put(ResourceTag.ResourceObjectType.NetworkACL, NetworkACLItemVO.class); + s_typeMap.put(ResourceTag.ResourceObjectType.StaticRoute, StaticRouteVO.class); + s_typeMap.put(ResourceTag.ResourceObjectType.VMSnapshot, VMSnapshotVO.class); + s_typeMap.put(ResourceTag.ResourceObjectType.RemoteAccessVpn, RemoteAccessVpnVO.class); + s_typeMap.put(ResourceTag.ResourceObjectType.Zone, DataCenterVO.class); + s_typeMap.put(ResourceTag.ResourceObjectType.ServiceOffering, ServiceOfferingVO.class); + s_typeMap.put(ResourceTag.ResourceObjectType.Storage, StoragePoolVO.class); + s_typeMap.put(ResourceTag.ResourceObjectType.PrivateGateway, RemoteAccessVpnVO.class); + s_typeMap.put(ResourceTag.ResourceObjectType.NetworkACLList, NetworkACLVO.class); + s_typeMap.put(ResourceTag.ResourceObjectType.VpnGateway, Site2SiteVpnGatewayVO.class); + s_typeMap.put(ResourceTag.ResourceObjectType.CustomerGateway, Site2SiteCustomerGatewayVO.class); + s_typeMap.put(ResourceTag.ResourceObjectType.VpnConnection, Site2SiteVpnConnectionVO.class); + s_typeMap.put(ResourceTag.ResourceObjectType.User, UserVO.class); + s_typeMap.put(ResourceTag.ResourceObjectType.DiskOffering, DiskOfferingVO.class); + s_typeMap.put(ResourceTag.ResourceObjectType.AutoScaleVmProfile, AutoScaleVmProfileVO.class); + s_typeMap.put(ResourceTag.ResourceObjectType.AutoScaleVmGroup, AutoScaleVmGroupVO.class); + s_typeMap.put(ResourceTag.ResourceObjectType.LBStickinessPolicy, LBStickinessPolicyVO.class); + s_typeMap.put(ResourceTag.ResourceObjectType.LBHealthCheckPolicy, LBHealthCheckPolicyVO.class); + s_typeMap.put(ResourceTag.ResourceObjectType.SnapshotPolicy, SnapshotPolicyVO.class); + s_typeMap.put(ResourceTag.ResourceObjectType.NetworkOffering, NetworkOfferingVO.class); + s_typeMap.put(ResourceTag.ResourceObjectType.VpcOffering, VpcOfferingVO.class); + s_typeMap.put(ResourceTag.ResourceObjectType.Domain, DomainVO.class); + } + + @Inject + EntityManager entityMgr; + @Inject + AccountManager accountMgr; + @Inject + DomainManager domainMgr; + + @Override + public long getResourceId(String resourceId, ResourceTag.ResourceObjectType resourceType) { + Class clazz = s_typeMap.get(resourceType); + Object entity = entityMgr.findByUuid(clazz, resourceId); + if (entity != null) { + return ((InternalIdentity)entity).getId(); + } + if (!StringUtils.isNumeric(resourceId)) { + throw new InvalidParameterValueException("Unable to find resource by uuid " + resourceId + " and type " + resourceType); + } + entity = entityMgr.findById(clazz, resourceId); + if (entity != null) { + return ((InternalIdentity)entity).getId(); + } + throw new InvalidParameterValueException("Unable to find resource by id " + resourceId + " and type " + resourceType); + } + + @Override + public String getUuid(String resourceId, ResourceTag.ResourceObjectType resourceType) { + if (!StringUtils.isNumeric(resourceId)) { + return resourceId; + } + + Class clazz = s_typeMap.get(resourceType); + + Object entity = entityMgr.findById(clazz, resourceId); + if (entity != null && entity instanceof Identity) { + return ((Identity)entity).getUuid(); + } + + return resourceId; + } + + @Override + public ResourceTag.ResourceObjectType getResourceType(String resourceTypeStr) { + + for (ResourceTag.ResourceObjectType type : ResourceTag.ResourceObjectType.values()) { + if (type.toString().equalsIgnoreCase(resourceTypeStr)) { + return type; + } + } + throw new InvalidParameterValueException("Invalid resource type: " + resourceTypeStr); + } + + public void checkResourceAccessible(Long accountId, Long domainId, String exceptionMessage) { + Account caller = CallContext.current().getCallingAccount(); + if (Objects.equals(domainId, -1)) + { + throw new CloudRuntimeException("Invalid DomainId: -1"); + } + if (accountId != null) { + accountMgr.checkAccess(caller, null, false, accountMgr.getAccount(accountId)); + } else if (domainId != null && !accountMgr.isNormalUser(caller.getId())) { + //check permissions; + accountMgr.checkAccess(caller, domainMgr.getDomain(domainId)); + } else { + throw new PermissionDeniedException(exceptionMessage); + } + } +} diff --git a/server/src/main/java/com/cloud/tags/TaggedResourceManagerImpl.java b/server/src/main/java/com/cloud/tags/TaggedResourceManagerImpl.java index 8364185662a..db6cac0e393 100644 --- a/server/src/main/java/com/cloud/tags/TaggedResourceManagerImpl.java +++ b/server/src/main/java/com/cloud/tags/TaggedResourceManagerImpl.java @@ -17,60 +17,33 @@ package com.cloud.tags; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.stream.Collectors; import javax.inject.Inject; import javax.naming.ConfigurationException; import javax.persistence.EntityExistsException; -import org.apache.cloudstack.api.Identity; -import org.apache.cloudstack.api.InternalIdentity; +import com.cloud.server.ResourceManagerUtil; import org.apache.cloudstack.context.CallContext; -import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.commons.collections.MapUtils; -import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; -import com.cloud.dc.DataCenterVO; import com.cloud.domain.PartOf; import com.cloud.event.ActionEvent; import com.cloud.event.EventTypes; import com.cloud.exception.InvalidParameterValueException; -import com.cloud.exception.PermissionDeniedException; -import com.cloud.network.LBHealthCheckPolicyVO; -import com.cloud.network.as.AutoScaleVmGroupVO; -import com.cloud.network.as.AutoScaleVmProfileVO; -import com.cloud.network.dao.IPAddressVO; -import com.cloud.network.dao.LBStickinessPolicyVO; -import com.cloud.network.dao.LoadBalancerVO; -import com.cloud.network.dao.NetworkVO; -import com.cloud.network.dao.RemoteAccessVpnVO; -import com.cloud.network.dao.Site2SiteCustomerGatewayVO; -import com.cloud.network.dao.Site2SiteVpnConnectionVO; -import com.cloud.network.dao.Site2SiteVpnGatewayVO; -import com.cloud.network.rules.FirewallRuleVO; -import com.cloud.network.rules.PortForwardingRuleVO; import com.cloud.network.security.SecurityGroupRuleVO; import com.cloud.network.security.SecurityGroupVO; import com.cloud.network.vpc.NetworkACLItemVO; import com.cloud.network.vpc.NetworkACLVO; -import com.cloud.network.vpc.StaticRouteVO; -import com.cloud.network.vpc.VpcOfferingVO; import com.cloud.network.vpc.VpcVO; -import com.cloud.offerings.NetworkOfferingVO; import com.cloud.projects.ProjectVO; import com.cloud.server.ResourceTag; import com.cloud.server.ResourceTag.ResourceObjectType; import com.cloud.server.TaggedResourceService; -import com.cloud.service.ServiceOfferingVO; -import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.SnapshotPolicyVO; -import com.cloud.storage.SnapshotVO; -import com.cloud.storage.VMTemplateVO; import com.cloud.storage.VolumeVO; import com.cloud.tags.dao.ResourceTagDao; import com.cloud.user.Account; @@ -78,7 +51,6 @@ import com.cloud.user.AccountManager; import com.cloud.user.AccountVO; import com.cloud.user.DomainManager; import com.cloud.user.OwnedBy; -import com.cloud.user.UserVO; import com.cloud.user.dao.AccountDao; import com.cloud.utils.Pair; import com.cloud.utils.component.ManagerBase; @@ -90,54 +62,10 @@ import com.cloud.utils.db.Transaction; import com.cloud.utils.db.TransactionCallbackNoReturn; import com.cloud.utils.db.TransactionStatus; import com.cloud.utils.exception.CloudRuntimeException; -import com.cloud.vm.NicVO; -import com.cloud.vm.UserVmVO; -import com.cloud.vm.snapshot.VMSnapshotVO; public class TaggedResourceManagerImpl extends ManagerBase implements TaggedResourceService { public static final Logger s_logger = Logger.getLogger(TaggedResourceManagerImpl.class); - private static final Map> s_typeMap = new HashMap<>(); - static { - s_typeMap.put(ResourceObjectType.UserVm, UserVmVO.class); - s_typeMap.put(ResourceObjectType.Volume, VolumeVO.class); - s_typeMap.put(ResourceObjectType.Template, VMTemplateVO.class); - s_typeMap.put(ResourceObjectType.ISO, VMTemplateVO.class); - s_typeMap.put(ResourceObjectType.Snapshot, SnapshotVO.class); - s_typeMap.put(ResourceObjectType.Network, NetworkVO.class); - s_typeMap.put(ResourceObjectType.LoadBalancer, LoadBalancerVO.class); - s_typeMap.put(ResourceObjectType.PortForwardingRule, PortForwardingRuleVO.class); - s_typeMap.put(ResourceObjectType.FirewallRule, FirewallRuleVO.class); - s_typeMap.put(ResourceObjectType.SecurityGroup, SecurityGroupVO.class); - s_typeMap.put(ResourceObjectType.SecurityGroupRule, SecurityGroupRuleVO.class); - s_typeMap.put(ResourceObjectType.PublicIpAddress, IPAddressVO.class); - s_typeMap.put(ResourceObjectType.Project, ProjectVO.class); - s_typeMap.put(ResourceObjectType.Account, AccountVO.class); - s_typeMap.put(ResourceObjectType.Vpc, VpcVO.class); - s_typeMap.put(ResourceObjectType.Nic, NicVO.class); - s_typeMap.put(ResourceObjectType.NetworkACL, NetworkACLItemVO.class); - s_typeMap.put(ResourceObjectType.StaticRoute, StaticRouteVO.class); - s_typeMap.put(ResourceObjectType.VMSnapshot, VMSnapshotVO.class); - s_typeMap.put(ResourceObjectType.RemoteAccessVpn, RemoteAccessVpnVO.class); - s_typeMap.put(ResourceObjectType.Zone, DataCenterVO.class); - s_typeMap.put(ResourceObjectType.ServiceOffering, ServiceOfferingVO.class); - s_typeMap.put(ResourceObjectType.Storage, StoragePoolVO.class); - s_typeMap.put(ResourceObjectType.PrivateGateway, RemoteAccessVpnVO.class); - s_typeMap.put(ResourceObjectType.NetworkACLList, NetworkACLVO.class); - s_typeMap.put(ResourceObjectType.VpnGateway, Site2SiteVpnGatewayVO.class); - s_typeMap.put(ResourceObjectType.CustomerGateway, Site2SiteCustomerGatewayVO.class); - s_typeMap.put(ResourceObjectType.VpnConnection, Site2SiteVpnConnectionVO.class); - s_typeMap.put(ResourceObjectType.User, UserVO.class); - s_typeMap.put(ResourceObjectType.DiskOffering, DiskOfferingVO.class); - s_typeMap.put(ResourceObjectType.AutoScaleVmProfile, AutoScaleVmProfileVO.class); - s_typeMap.put(ResourceObjectType.AutoScaleVmGroup, AutoScaleVmGroupVO.class); - s_typeMap.put(ResourceObjectType.LBStickinessPolicy, LBStickinessPolicyVO.class); - s_typeMap.put(ResourceObjectType.LBHealthCheckPolicy, LBHealthCheckPolicyVO.class); - s_typeMap.put(ResourceObjectType.SnapshotPolicy, SnapshotPolicyVO.class); - s_typeMap.put(ResourceObjectType.NetworkOffering, NetworkOfferingVO.class); - s_typeMap.put(ResourceObjectType.VpcOffering, VpcOfferingVO.class); - } - @Inject EntityManager _entityMgr; @Inject @@ -148,6 +76,8 @@ public class TaggedResourceManagerImpl extends ManagerBase implements TaggedReso DomainManager _domainMgr; @Inject AccountDao _accountDao; + @Inject + ResourceManagerUtil resourceManagerUtil; @Override public boolean configure(String name, Map params) throws ConfigurationException { @@ -164,25 +94,8 @@ public class TaggedResourceManagerImpl extends ManagerBase implements TaggedReso return true; } - @Override - public long getResourceId(String resourceId, ResourceObjectType resourceType) { - Class clazz = s_typeMap.get(resourceType); - Object entity = _entityMgr.findByUuid(clazz, resourceId); - if (entity != null) { - return ((InternalIdentity)entity).getId(); - } - if (!StringUtils.isNumeric(resourceId)) { - throw new InvalidParameterValueException("Unable to find resource by uuid " + resourceId + " and type " + resourceType); - } - entity = _entityMgr.findById(clazz, resourceId); - if (entity != null) { - return ((InternalIdentity)entity).getId(); - } - throw new InvalidParameterValueException("Unable to find resource by id " + resourceId + " and type " + resourceType); - } - private Pair getAccountDomain(long resourceId, ResourceObjectType resourceType) { - Class clazz = s_typeMap.get(resourceType); + Class clazz = ResourceManagerUtilImpl.s_typeMap.get(resourceType); Object entity = _entityMgr.findById(clazz, resourceId); Long accountId = null; @@ -191,7 +104,7 @@ public class TaggedResourceManagerImpl extends ManagerBase implements TaggedReso // if the resource type is a security group rule, get the accountId and domainId from the security group itself if (resourceType == ResourceObjectType.SecurityGroupRule) { SecurityGroupRuleVO rule = (SecurityGroupRuleVO)entity; - Object SecurityGroup = _entityMgr.findById(s_typeMap.get(ResourceObjectType.SecurityGroup), rule.getSecurityGroupId()); + Object SecurityGroup = _entityMgr.findById(ResourceManagerUtilImpl.s_typeMap.get(ResourceObjectType.SecurityGroup), rule.getSecurityGroupId()); accountId = ((SecurityGroupVO)SecurityGroup).getAccountId(); domainId = ((SecurityGroupVO)SecurityGroup).getDomainId(); @@ -206,11 +119,11 @@ public class TaggedResourceManagerImpl extends ManagerBase implements TaggedReso // if the resource type is network acl, get the accountId and domainId from VPC following: NetworkACLItem -> NetworkACL -> VPC if (resourceType == ResourceObjectType.NetworkACL) { NetworkACLItemVO aclItem = (NetworkACLItemVO)entity; - Object networkACL = _entityMgr.findById(s_typeMap.get(ResourceObjectType.NetworkACLList), aclItem.getAclId()); + Object networkACL = _entityMgr.findById(ResourceManagerUtilImpl.s_typeMap.get(ResourceObjectType.NetworkACLList), aclItem.getAclId()); Long vpcId = ((NetworkACLVO)networkACL).getVpcId(); if (vpcId != null && vpcId != 0) { - Object vpc = _entityMgr.findById(s_typeMap.get(ResourceObjectType.Vpc), vpcId); + Object vpc = _entityMgr.findById(ResourceManagerUtilImpl.s_typeMap.get(ResourceObjectType.Vpc), vpcId); accountId = ((VpcVO)vpc).getAccountId(); domainId = ((VpcVO)vpc).getDomainId(); @@ -243,49 +156,6 @@ public class TaggedResourceManagerImpl extends ManagerBase implements TaggedReso return new Pair<>(accountId, domainId); } - private void checkResourceAccessible(Long accountId, Long domainId, String exceptionMessage) { - Account caller = CallContext.current().getCallingAccount(); - if (Objects.equals(domainId, -1)) - { - throw new CloudRuntimeException("Invalid DomainId: -1"); - } - if (accountId != null) { - _accountMgr.checkAccess(caller, null, false, _accountMgr.getAccount(accountId)); - } else if (domainId != null && !_accountMgr.isNormalUser(caller.getId())) { - //check permissions; - _accountMgr.checkAccess(caller, _domainMgr.getDomain(domainId)); - } else { - throw new PermissionDeniedException(exceptionMessage); - } - } - - @Override - public ResourceObjectType getResourceType(String resourceTypeStr) { - - for (ResourceObjectType type : ResourceTag.ResourceObjectType.values()) { - if (type.toString().equalsIgnoreCase(resourceTypeStr)) { - return type; - } - } - throw new InvalidParameterValueException("Invalid resource type " + resourceTypeStr); - } - - @Override - public String getUuid(String resourceId, ResourceObjectType resourceType) { - if (!StringUtils.isNumeric(resourceId)) { - return resourceId; - } - - Class clazz = s_typeMap.get(resourceType); - - Object entity = _entityMgr.findById(clazz, resourceId); - if (entity != null && entity instanceof Identity) { - return ((Identity)entity).getUuid(); - } - - return resourceId; - } - @Override @DB @ActionEvent(eventType = EventTypes.EVENT_TAGS_CREATE, eventDescription = "creating resource tags") @@ -303,14 +173,14 @@ public class TaggedResourceManagerImpl extends ManagerBase implements TaggedReso throw new InvalidParameterValueException("The resource type " + resourceType + " doesn't support resource tags"); } - long id = getResourceId(resourceId, resourceType); - String resourceUuid = getUuid(resourceId, resourceType); + long id = resourceManagerUtil.getResourceId(resourceId, resourceType); + String resourceUuid = resourceManagerUtil.getUuid(resourceId, resourceType); Pair accountDomainPair = getAccountDomain(id, resourceType); Long domainId = accountDomainPair.second(); Long accountId = accountDomainPair.first(); - checkResourceAccessible(accountId, domainId, "Account '" + caller + + resourceManagerUtil.checkResourceAccessible(accountId, domainId, "Account '" + caller + "' doesn't have permissions to create tags" + " for resource '" + id + "(" + key + ")'."); String value = tags.get(key); @@ -335,7 +205,7 @@ public class TaggedResourceManagerImpl extends ManagerBase implements TaggedReso } private List searchResourceTags(List resourceIds, ResourceObjectType resourceType) { - List resourceUuids = resourceIds.stream().map(resourceId -> getUuid(resourceId, resourceType)).collect(Collectors.toList()); + List resourceUuids = resourceIds.stream().map(resourceId -> resourceManagerUtil.getUuid(resourceId, resourceType)).collect(Collectors.toList()); SearchBuilder sb = _resourceTagDao.createSearchBuilder(); sb.and("resourceUuid", sb.entity().getResourceUuid(), SearchCriteria.Op.IN); sb.and("resourceType", sb.entity().getResourceType(), SearchCriteria.Op.EQ); diff --git a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml index 207270dd79c..d79908ecedf 100644 --- a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml +++ b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml @@ -323,4 +323,10 @@ + + + + + diff --git a/server/src/test/java/com/cloud/metadata/ResourceMetaDataManagerTest.java b/server/src/test/java/com/cloud/metadata/ResourceMetaDataManagerTest.java index d6d8953d3fb..ff5ebb3deec 100644 --- a/server/src/test/java/com/cloud/metadata/ResourceMetaDataManagerTest.java +++ b/server/src/test/java/com/cloud/metadata/ResourceMetaDataManagerTest.java @@ -27,6 +27,7 @@ import java.util.Map; import javax.naming.ConfigurationException; +import com.cloud.server.ResourceManagerUtil; import org.apache.commons.collections.map.HashedMap; import org.junit.Before; import org.mockito.Mock; @@ -49,6 +50,8 @@ public class ResourceMetaDataManagerTest { NicDetailsDao _nicDetailDao; @Mock TaggedResourceService _taggedResourceMgr; + @Mock + ResourceManagerUtil resourceManagerUtil; @Before public void setup() { @@ -70,7 +73,7 @@ public class ResourceMetaDataManagerTest { public void testResourceDetails() throws ResourceAllocationException { //when(_resourceMetaDataMgr.getResourceId(anyString(), eq(ResourceTag.TaggedResourceType.Volume))).thenReturn(1L); - doReturn(1L).when(_taggedResourceMgr).getResourceId(anyString(), eq(ResourceTag.ResourceObjectType.Volume)); + doReturn(1L).when(resourceManagerUtil).getResourceId(anyString(), eq(ResourceTag.ResourceObjectType.Volume)); // _volumeDetailDao.removeDetails(id, key); doNothing().when(_volumeDetailDao).removeDetail(anyLong(), anyString()); @@ -82,7 +85,7 @@ public class ResourceMetaDataManagerTest { // Test adding details public void testAddResourceDetails() throws ResourceAllocationException { - doReturn(1L).when(_taggedResourceMgr).getResourceId("1", ResourceTag.ResourceObjectType.Volume); + doReturn(1L).when(resourceManagerUtil).getResourceId("1", ResourceTag.ResourceObjectType.Volume); // _volumeDetailDao.removeDetails(id, key); doNothing().when(_volumeDetailDao).removeDetail(anyLong(), anyString()); diff --git a/tools/apidoc/gen_toc.py b/tools/apidoc/gen_toc.py index 7ed3e98bd54..18996028182 100644 --- a/tools/apidoc/gen_toc.py +++ b/tools/apidoc/gen_toc.py @@ -136,6 +136,7 @@ known_categories = { 'Simulator': 'simulator', 'StaticRoute': 'VPC', 'Tags': 'Resource tags', + 'Icon': 'Resource Icon', 'NiciraNvpDevice': 'Nicira NVP', 'BrocadeVcsDevice': 'Brocade VCS', 'BigSwitchBcfDevice': 'BigSwitch BCF', diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index a2e8cb3fb45..0679a9bfb57 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -556,6 +556,7 @@ "label.character": "Character", "label.chassis": "Chassis", "label.checksum": "Checksum", +"label.choose.resource.icon": "Choose Icon", "label.choose.saml.indentity": "Choose SAML identity provider", "label.cidr": "CIDR", "label.cidr.account": "CIDR or Account/Security Group", @@ -718,6 +719,7 @@ "label.delete.events": "Delete events", "label.delete.f5": "Delete F5", "label.delete.gateway": "Delete gateway", +"label.delete.icon": "Delete icon", "label.delete.instance.group": "Delete Instance Group", "label.delete.internal.lb": "Delete Internal LB", "label.delete.netscaler": "Delete NetScaler", @@ -2249,7 +2251,9 @@ "label.upgrade.router.newer.template": "Upgrade Router to Use Newer Template", "label.upload": "Upload", "label.upload.from.local": "Upload from Local", +"label.upload.icon": "Upload Icon", "label.upload.iso.from.local": "Upload ISO from Local", +"label.upload.resource.icon": "Upload Icon", "label.upload.template.from.local": "Upload Template from Local", "label.upload.volume": "Upload volume", "label.upload.volume.from.local": "Upload Volume from Local", @@ -3248,6 +3252,7 @@ "message.success.delete": "Delete success", "message.success.delete.acl.rule": "Successfully removed ACL rule", "message.success.delete.backup.schedule": "Successfully deleted Configure VM backup schedule", +"message.success.delete.icon": "Successfully deleted icon of", "message.success.delete.snapshot.policy": "Successfully deleted snapshot policy", "message.success.delete.static.route": "Successfully deleted static route", "message.success.delete.tag": "Successfully deleted tag", @@ -3285,6 +3290,7 @@ "message.success.upgrade.kubernetes": "Successfully upgraded Kubernetes cluster", "message.success.upload": "Upload Successfully", "message.success.upload.description": "This ISO file has been uploaded. Please check its status at Templates menu", +"message.success.upload.icon": "Successfully uploaded icon for ", "message.success.upload.iso.description": "This ISO file has been uploaded. Please check its status in the Images > ISOs menu", "message.success.upload.template.description": "This template file has been uploaded. Please check its status at Templates menu", "message.success.upload.volume.description": "This Volume has been uploaded. Please check its status in the Volumes menu", @@ -3386,6 +3392,7 @@ "message.volume.state.uploadinprogress": "Volume upload is in progress", "message.volume.state.uploadop": "The volume upload operation is in progress or in short the volume is on secondary storage", "message.waiting.for.builtin.templates.to.load": "Waiting for builtin templates to load...", +"message.warn.filetype": "jpg, jpeg, png, bmp and svg are the only supported image formats", "message.xstools61plus.update.failed": "Failed to update Original XS Version is 6.1+ field. Error:", "message.you.must.have.at.least.one.physical.network": "You must have at least one physical network", "message.your.cloudstack.is.ready": "Your CloudStack is ready!", diff --git a/ui/src/components/header/ProjectMenu.vue b/ui/src/components/header/ProjectMenu.vue index dd5e614b4d9..3752bb02b99 100644 --- a/ui/src/components/header/ProjectMenu.vue +++ b/ui/src/components/header/ProjectMenu.vue @@ -39,6 +39,8 @@ + + {{ project.displaytext || project.name }} @@ -49,9 +51,13 @@ import store from '@/store' import { api } from '@/api' import _ from 'lodash' +import ResourceIcon from '@/components/view/ResourceIcon' export default { name: 'ProjectMenu', + components: { + ResourceIcon + }, data () { return { projects: [], @@ -70,7 +76,7 @@ export default { const projects = [] const getNextPage = () => { this.loading = true - api('listProjects', { listAll: true, details: 'min', page: page, pageSize: 500 }).then(json => { + api('listProjects', { listAll: true, details: 'min', page: page, pageSize: 500, showIcon: true }).then(json => { if (json && json.listprojectsresponse && json.listprojectsresponse.project) { projects.push(...json.listprojectsresponse.project) } diff --git a/ui/src/components/header/UserMenu.vue b/ui/src/components/header/UserMenu.vue index 33f4bc33b00..782fab45c56 100644 --- a/ui/src/components/header/UserMenu.vue +++ b/ui/src/components/header/UserMenu.vue @@ -26,7 +26,10 @@ - + + + + {{ nickname() }} @@ -64,16 +67,36 @@ diff --git a/ui/src/components/view/SearchView.vue b/ui/src/components/view/SearchView.vue index 86ffe88cdca..ad6a48b3abf 100644 --- a/ui/src/components/view/SearchView.vue +++ b/ui/src/components/view/SearchView.vue @@ -67,7 +67,21 @@ {{ $t(opt.name) }} + :value="opt.id"> + + + + + + + + + + + + + {{ $t(opt.name) }} + import { api } from '@/api' import TooltipButton from '@/components/widgets/TooltipButton' +import ResourceIcon from '@/components/view/ResourceIcon' export default { name: 'SearchView', components: { - TooltipButton + TooltipButton, + ResourceIcon }, props: { searchFilters: { @@ -341,7 +357,7 @@ export default { }, fetchZones () { return new Promise((resolve, reject) => { - api('listZones', { listAll: true }).then(json => { + api('listZones', { listAll: true, showicon: true }).then(json => { const zones = json.listzonesresponse.zone resolve({ type: 'zoneid', @@ -354,7 +370,7 @@ export default { }, fetchDomains () { return new Promise((resolve, reject) => { - api('listDomains', { listAll: true }).then(json => { + api('listDomains', { listAll: true, showicon: true }).then(json => { const domain = json.listdomainsresponse.domain resolve({ type: 'domainid', diff --git a/ui/src/components/view/TreeView.vue b/ui/src/components/view/TreeView.vue index feb0ad6392b..9bf1908ce50 100644 --- a/ui/src/components/view/TreeView.vue +++ b/ui/src/components/view/TreeView.vue @@ -82,6 +82,7 @@ import { api } from '@/api' import DetailsTab from '@/components/view/DetailsTab' import ResourceView from '@/components/view/ResourceView' import ResourceLayout from '@/layouts/ResourceLayout' +import eventBus from '@/config/eventBus' export default { name: 'TreeView', @@ -153,6 +154,9 @@ export default { this.metaName = this.$route.meta.name this.apiList = this.$route.meta.permission[0] ? this.$route.meta.permission[0] : '' this.apiChildren = this.$route.meta.permission[1] ? this.$route.meta.permission[1] : '' + eventBus.$on('refresh-domain-icon', () => { + this.getDetailResource(this.selectedTreeKey) + }) }, watch: { loading () { @@ -225,7 +229,8 @@ export default { const params = { listAll: true, - id: treeNode.eventKey + id: treeNode.eventKey, + showicon: true } return new Promise(resolve => { @@ -341,7 +346,6 @@ export default { this.treeViewData = [] this.loadingSearch = true this.$emit('change-tree-store', {}) - api(this.apiList, params).then(json => { const listDomains = this.getResponseJsonData(json) this.treeVerticalData = this.treeVerticalData.concat(listDomains) @@ -393,11 +397,11 @@ export default { // set id to parameter params.id = selectedKey params.listAll = true + params.showicon = true params.page = 1 params.pageSize = 1 this.detailLoading = true - api(apiName, params).then(json => { const jsonResponse = this.getResponseJsonData(json) diff --git a/ui/src/components/view/UploadResourceIcon.vue b/ui/src/components/view/UploadResourceIcon.vue new file mode 100644 index 00000000000..ecf6d6668e0 --- /dev/null +++ b/ui/src/components/view/UploadResourceIcon.vue @@ -0,0 +1,314 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + + + diff --git a/ui/src/core/lazy_lib/components_use.js b/ui/src/core/lazy_lib/components_use.js index 50e2d96d0d8..a9adc5d924d 100644 --- a/ui/src/core/lazy_lib/components_use.js +++ b/ui/src/core/lazy_lib/components_use.js @@ -68,6 +68,7 @@ import { AutoComplete, Collapse } from 'ant-design-vue' +import VueCropper from 'vue-cropper' Vue.use(ConfigProvider) Vue.use(Layout) @@ -117,6 +118,7 @@ Vue.use(Calendar) Vue.use(Slider) Vue.use(AutoComplete) Vue.use(Collapse) +Vue.use(VueCropper) Vue.prototype.$confirm = Modal.confirm Vue.prototype.$message = message diff --git a/ui/src/main.js b/ui/src/main.js index adc202240ed..0635b99765e 100644 --- a/ui/src/main.js +++ b/ui/src/main.js @@ -26,7 +26,7 @@ import './core/lazy_use' import './core/ext' import './permission' // permission control import './utils/filter' // global filter -import { pollJobPlugin, notifierPlugin, toLocaleDatePlugin, configUtilPlugin, apiMetaUtilPlugin } from './utils/plugins' +import { pollJobPlugin, notifierPlugin, toLocaleDatePlugin, configUtilPlugin, apiMetaUtilPlugin, showIconPlugin, resourceTypePlugin } from './utils/plugins' import { VueAxios } from './utils/request' import './utils/directives' @@ -35,6 +35,8 @@ Vue.use(VueAxios, router) Vue.use(pollJobPlugin) Vue.use(notifierPlugin) Vue.use(toLocaleDatePlugin) +Vue.use(showIconPlugin) +Vue.use(resourceTypePlugin) fetch('config.json').then(response => response.json()).then(config => { Vue.prototype.$config = config diff --git a/ui/src/utils/plugins.js b/ui/src/utils/plugins.js index 8d196157fa0..99db8f7eb6e 100644 --- a/ui/src/utils/plugins.js +++ b/ui/src/utils/plugins.js @@ -250,6 +250,39 @@ export const configUtilPlugin = { } } +export const showIconPlugin = { + install (Vue) { + Vue.prototype.$showIcon = function (resource) { + var resourceType = this.$route.path.split('/')[1] + if (resource) { + resourceType = resource + } + if (['zone', 'template', 'iso', 'account', 'accountuser', 'vm', 'domain', 'project', 'vpc', 'guestnetwork'].includes(resourceType)) { + return true + } else { + return false + } + } + } +} + +export const resourceTypePlugin = { + install (Vue) { + Vue.prototype.$getResourceType = function () { + const type = this.$route.path.split('/')[1] + if (type === 'vm') { + return 'UserVM' + } else if (type === 'accountuser') { + return 'User' + } else if (type === 'guestnetwork') { + return 'Network' + } else { + return type + } + } + } +} + export const apiMetaUtilPlugin = { install (Vue) { Vue.prototype.$getApiParams = function () { diff --git a/ui/src/views/AutogenView.vue b/ui/src/views/AutogenView.vue index 31fc4da8fdf..c924486eff3 100644 --- a/ui/src/views/AutogenView.vue +++ b/ui/src/views/AutogenView.vue @@ -271,6 +271,44 @@ > {{ }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ opt.name || opt.description || opt.traffictype || opt.publicip }} @@ -411,6 +449,8 @@ import ListView from '@/components/view/ListView' import ResourceView from '@/components/view/ResourceView' import ActionButton from '@/components/view/ActionButton' import SearchView from '@/components/view/SearchView' +import OsLogo from '@/components/widgets/OsLogo' +import ResourceIcon from '@/components/view/ResourceIcon' import BulkActionProgress from '@/components/view/BulkActionProgress' import TooltipLabel from '@/components/widgets/TooltipLabel' @@ -425,7 +465,9 @@ export default { ActionButton, SearchView, BulkActionProgress, - TooltipLabel + TooltipLabel, + OsLogo, + ResourceIcon }, mixins: [mixinDevice], provide: function () { @@ -493,6 +535,11 @@ export default { this.fetchData() } }) + eventBus.$on('refresh-icon', () => { + if (this.$showIcon()) { + this.fetchData() + } + }) eventBus.$on('async-job-complete', (action) => { if (this.$route.path.includes('/vm/')) { if (action && 'api' in action && ['destroyVirtualMachine'].includes(action.api)) { @@ -785,7 +832,11 @@ export default { params.page = this.page params.pagesize = this.pageSize + this.searchParams = params + if (this.$showIcon()) { + params.showIcon = true + } api(this.apiName, params).then(json => { var responseName var objectName @@ -986,6 +1037,10 @@ export default { var extractedParamName = paramName.replace('ids', '').replace('id', '').toLowerCase() var params = { listall: true } const possibleName = 'list' + extractedParamName + 's' + var showIcon = false + if (this.$showIcon(extractedParamName)) { + showIcon = true + } var possibleApi if (this.currentAction.mapping && param.name in this.currentAction.mapping && this.currentAction.mapping[param.name].api) { possibleApi = this.currentAction.mapping[param.name].api @@ -1017,6 +1072,9 @@ export default { } else if (possibleApi === 'listHosts') { params.type = 'routing' } + if (showIcon) { + params.showicon = true + } api(possibleApi, params).then(json => { param.loading = false for (const obj in json) { diff --git a/ui/src/views/compute/AssignInstance.vue b/ui/src/views/compute/AssignInstance.vue index 085c53fae79..a4869742046 100644 --- a/ui/src/views/compute/AssignInstance.vue +++ b/ui/src/views/compute/AssignInstance.vue @@ -55,6 +55,8 @@ return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0 }" > + + {{ domain.path || domain.name || domain.description }} @@ -72,6 +74,8 @@ return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0 }" > + + {{ account.name }} @@ -91,6 +95,8 @@ return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0 }" > + + {{ project.name }} @@ -108,6 +114,8 @@ return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0 }" > + + {{ network.name ? network.name : '-' }} @@ -129,6 +137,7 @@