From 413d10dd8106af9b3c369c1a393fbcca5e7217ce Mon Sep 17 00:00:00 2001 From: Nicolas Vazquez Date: Wed, 8 Sep 2021 01:44:06 -0300 Subject: [PATCH] server: Extend the Annotations framework (#5103) * Extend addAnnotation and listAnnotations APIs * Allow users to add, list and remove comments * Add adminsonly UI and allow admins or owners to remove comments * New annotations tab * In progress: new comments section * Address review comments * Fix * Fix annotationfilter and comments section * Add keyword and delete action * Fix and rename annotations tab * Update annotation visibility API and update comments table accordingly * Allow users seeing all the comments for their owned resources * Extend comments for volumes and snapshots * Extend comments to multiple entities * Add uuid to ssh keypairs * SSH keypair UI refactor * Extend comments to the infrastructure entities * Add missing entities * Fix upgrade version for ssh keypairs * Fix typo on DB upgrade schema * Fix annotations table columns when there is no data * Extend the list view of items showing they if they have comments * Remove extra test * Add annotation permissions * Address review comments * Extend marvin tests for annotations * updating ui stuff * addition to toggle visibility * Fix pagination on comments section * Extend to kubernetes clusters * Fixes after last review * Change default value for adminsonly column * Remove the required field for the annotationfilter parameter * Small fixes on visibility and other fixes * Cleanup to reduce files changed * Rollback extra line * Address review comments * Fix cleanup error on smoke test * Fix sending incorrect parameter to checkPermissions method * Add check domain access for the calling account for domain networks * Fix only display annotations icon if there are comments the user can see * Simply change the Save button label to Submit * Change order of the Tools menu to provent users getting 404 error on clicking the text instead of expanding * Remove comments when removing entities * Address review comments on marvin tests * Allow users to list annotations for an entity ID * Allow users to see all comments for allowed entities * Fix search filters * Remove username from search filter * Add pagination to the annotations tab * Display username for user comments * Fix add permissions for domain and resource admins * Fix for domain admins * Trivial but important UI fix * Replace pagination for annotations tab * Add confirmation for delete comment * Lint warnings * Fix reduced list as domain admin * Fix display remove comment button for non admins * Improve display remove action button * Remove unused parameter on groupShow * Include a clock icon to the all comments filter except for root admin * Move cleanup SQL to the correct file after rebasing main Co-authored-by: davidjumani --- .../cluster/KubernetesClusterHelper.java | 25 + .../cloud/network/vpc/StaticRouteProfile.java | 5 + .../main/java/com/cloud/user/SSHKeyPair.java | 3 +- .../cloudstack/acl/ControlledEntity.java | 1 + .../annotation/AnnotationService.java | 50 +- .../apache/cloudstack/api/ApiConstants.java | 8 +- .../api/BaseResponseWithAnnotations.java | 35 ++ .../api/BaseResponseWithTagInformation.java | 2 +- .../admin/annotation/AddAnnotationCmd.java | 11 + .../admin/annotation/ListAnnotationsCmd.java | 21 + .../UpdateAnnotationVisibilityCmd.java | 74 +++ .../api/command/admin/host/UpdateHostCmd.java | 2 +- .../firewall/CreateEgressFirewallRuleCmd.java | 5 + .../user/firewall/CreateFirewallRuleCmd.java | 5 + .../firewall/CreatePortForwardingRuleCmd.java | 5 + .../user/nat/CreateIpForwardingRuleCmd.java | 5 + .../api/response/AnnotationResponse.java | 36 ++ .../api/response/ClusterResponse.java | 4 +- .../response/CreateSSHKeyPairResponse.java | 4 +- .../api/response/DiskOfferingResponse.java | 4 +- .../api/response/DomainResponse.java | 4 +- .../api/response/DomainRouterResponse.java | 4 +- .../cloudstack/api/response/HostResponse.java | 4 +- .../api/response/IPAddressResponse.java | 4 +- .../api/response/ImageStoreResponse.java | 4 +- .../api/response/InstanceGroupResponse.java | 4 +- .../api/response/NetworkOfferingResponse.java | 4 +- .../api/response/NetworkResponse.java | 4 +- .../cloudstack/api/response/PodResponse.java | 4 +- .../api/response/SSHKeyPairResponse.java | 19 +- .../api/response/ServiceOfferingResponse.java | 4 +- .../Site2SiteCustomerGatewayResponse.java | 4 +- .../api/response/StoragePoolResponse.java | 4 +- .../api/response/SystemVmResponse.java | 4 +- .../cloudstack/api/response/VpcResponse.java | 4 +- .../cloudstack/api/response/ZoneResponse.java | 4 +- .../spring-core-registry-core-context.xml | 3 + .../cloudstack/kubernetes/module.properties | 21 + ...fecycle-kubernetes-context-inheritable.xml | 32 ++ .../java/com/cloud/network/addr/PublicIp.java | 5 + .../network/rules/StaticNatRuleImpl.java | 5 + .../cloud/vm/VirtualMachineManagerImpl.java | 7 + .../orchestration/NetworkOrchestrator.java | 6 + .../main/java/com/cloud/event/EventVO.java | 5 + .../com/cloud/network/UserIpv6AddressVO.java | 5 + .../java/com/cloud/network/VpnUserVO.java | 5 + .../cloud/network/as/AutoScalePolicyVO.java | 5 + .../cloud/network/as/AutoScaleVmGroupVO.java | 5 + .../network/as/AutoScaleVmProfileVO.java | 5 + .../com/cloud/network/as/ConditionVO.java | 5 + .../com/cloud/network/dao/IPAddressVO.java | 5 + .../network/dao/MonitoringServiceVO.java | 5 + .../cloud/network/dao/RemoteAccessVpnVO.java | 5 + .../network/dao/Site2SiteVpnConnectionVO.java | 5 + .../network/dao/Site2SiteVpnGatewayVO.java | 5 + .../cloud/network/rules/FirewallRuleVO.java | 5 + .../com/cloud/network/vpc/StaticRouteVO.java | 5 + .../com/cloud/network/vpc/VpcGatewayVO.java | 5 + .../cloud/projects/ProjectInvitationVO.java | 5 + .../java/com/cloud/tags/ResourceTagVO.java | 5 + .../upgrade/dao/Upgrade41520to41600.java | 26 + .../main/java/com/cloud/user/AccountVO.java | 5 + .../java/com/cloud/user/SSHKeyPairVO.java | 16 + .../java/com/cloud/vm/ConsoleProxyVO.java | 5 + .../java/com/cloud/vm/DomainRouterVO.java | 4 + .../com/cloud/vm/SecondaryStorageVmVO.java | 5 + .../src/main/java/com/cloud/vm/UserVmVO.java | 5 + .../main/java/com/cloud/vm/VMInstanceVO.java | 5 + .../java/com/cloud/vm/dao/NicIpAliasVO.java | 5 + .../com/cloud/vm/dao/NicSecondaryIpVO.java | 5 + .../cloudstack/annotation/AnnotationVO.java | 20 +- .../annotation/dao/AnnotationDao.java | 9 +- .../annotation/dao/AnnotationDaoImpl.java | 120 +++- .../apache/cloudstack/backup/BackupVO.java | 5 + .../cloud/entity/api/db/VMEntityVO.java | 5 + .../db/schema-41520to41600-cleanup.sql | 1 + .../META-INF/db/schema-41520to41600.sql | 17 + .../datastore/PrimaryDataStoreHelper.java | 5 + .../storage/volume/VolumeServiceImpl.java | 5 + .../cluster/KubernetesClusterHelperImpl.java | 48 ++ .../cluster/KubernetesClusterManagerImpl.java | 6 + .../KubernetesClusterDestroyWorker.java | 5 + .../response/KubernetesClusterResponse.java | 4 +- .../kubernetes-service/module.properties | 2 +- .../spring-kubernetes-service-context.xml | 4 + .../metrics/MetricsServiceImpl.java | 6 + .../java/com/cloud/api/ApiResponseHelper.java | 31 +- .../api/query/dao/DataCenterJoinDaoImpl.java | 7 + .../query/dao/DiskOfferingJoinDaoImpl.java | 11 + .../api/query/dao/DomainJoinDaoImpl.java | 14 + .../query/dao/DomainRouterJoinDaoImpl.java | 8 + .../cloud/api/query/dao/HostJoinDaoImpl.java | 10 + .../api/query/dao/ImageStoreJoinDaoImpl.java | 14 + .../query/dao/InstanceGroupJoinDaoImpl.java | 13 + .../query/dao/ServiceOfferingJoinDaoImpl.java | 11 + .../api/query/dao/StoragePoolJoinDaoImpl.java | 14 + .../api/query/dao/TemplateJoinDaoImpl.java | 29 +- .../api/query/dao/UserVmJoinDaoImpl.java | 13 + .../api/query/dao/VolumeJoinDaoImpl.java | 12 + .../cloud/api/query/vo/AsyncJobJoinVO.java | 5 + .../com/cloud/api/query/vo/EventJoinVO.java | 5 + .../api/query/vo/ProjectInvitationJoinVO.java | 5 + .../cloud/api/query/vo/ResourceTagJoinVO.java | 5 + .../cloud/api/query/vo/UserAccountJoinVO.java | 5 + .../ConfigurationManagerImpl.java | 12 + .../cloud/network/IpAddressManagerImpl.java | 6 + .../network/vpc/PrivateGatewayProfile.java | 5 + .../com/cloud/network/vpc/VpcManagerImpl.java | 7 + .../network/vpn/Site2SiteVpnManagerImpl.java | 5 + .../cloud/resource/ResourceManagerImpl.java | 9 + .../cloud/server/ManagementServerImpl.java | 5 + .../com/cloud/storage/StorageManagerImpl.java | 5 + .../storage/snapshot/SnapshotManagerImpl.java | 6 + .../template/HypervisorTemplateAdapter.java | 9 + .../com/cloud/user/DomainManagerImpl.java | 5 + .../java/com/cloud/vm/UserVmManagerImpl.java | 6 + .../vm/snapshot/VMSnapshotManagerImpl.java | 5 + .../annotation/AnnotationManagerImpl.java | 524 ++++++++++++++++-- .../spring-server-core-managers-context.xml | 4 +- .../com/cloud/user/DomainManagerImplTest.java | 3 + .../CreateNetworkOfferingTest.java | 4 + .../test/resources/createNetworkOffering.xml | 1 + ...ost_annotations.py => test_annotations.py} | 149 +++-- tools/apidoc/gen_toc.py | 1 + ui/public/locales/en.json | 14 +- ui/src/components/view/ActionButton.vue | 10 +- ui/src/components/view/AnnotationsTab.vue | 312 +++++++++++ ui/src/components/view/InfoCard.vue | 145 +---- ui/src/components/view/ListView.vue | 109 +++- ui/src/components/view/SearchView.vue | 52 +- ui/src/config/router.js | 4 + ui/src/config/section/compute.js | 27 +- ui/src/config/section/domain.js | 3 + ui/src/config/section/image.js | 8 + ui/src/config/section/infra/clusters.js | 4 + ui/src/config/section/infra/hosts.js | 3 + ui/src/config/section/infra/pods.js | 4 + .../config/section/infra/primaryStorages.js | 4 + ui/src/config/section/infra/routers.js | 4 + .../config/section/infra/secondaryStorages.js | 4 + ui/src/config/section/infra/systemVms.js | 11 + ui/src/config/section/infra/zones.js | 4 + ui/src/config/section/network.js | 19 + ui/src/config/section/offering.js | 33 ++ ui/src/config/section/storage.js | 30 + ui/src/config/section/tools.js | 43 +- ui/src/views/AutogenView.vue | 34 +- ui/src/views/compute/InstanceTab.vue | 19 +- ui/src/views/compute/KubernetesServiceTab.vue | 26 +- ui/src/views/network/VpcTab.vue | 28 +- 150 files changed, 2492 insertions(+), 336 deletions(-) create mode 100644 api/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelper.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/BaseResponseWithAnnotations.java create mode 100644 api/src/main/java/org/apache/cloudstack/api/command/admin/annotation/UpdateAnnotationVisibilityCmd.java create mode 100644 core/src/main/resources/META-INF/cloudstack/kubernetes/module.properties create mode 100644 core/src/main/resources/META-INF/cloudstack/kubernetes/spring-core-lifecycle-kubernetes-context-inheritable.xml create mode 100644 plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelperImpl.java rename test/integration/smoke/{test_host_annotations.py => test_annotations.py} (53%) create mode 100644 ui/src/components/view/AnnotationsTab.vue diff --git a/api/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelper.java b/api/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelper.java new file mode 100644 index 00000000000..e445e50f82c --- /dev/null +++ b/api/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelper.java @@ -0,0 +1,25 @@ +// 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.kubernetes.cluster; + +import com.cloud.utils.component.Adapter; +import org.apache.cloudstack.acl.ControlledEntity; + +public interface KubernetesClusterHelper extends Adapter { + + ControlledEntity findByUuid(String uuid); +} diff --git a/api/src/main/java/com/cloud/network/vpc/StaticRouteProfile.java b/api/src/main/java/com/cloud/network/vpc/StaticRouteProfile.java index 737541f0a40..cb4849f1f7b 100644 --- a/api/src/main/java/com/cloud/network/vpc/StaticRouteProfile.java +++ b/api/src/main/java/com/cloud/network/vpc/StaticRouteProfile.java @@ -106,4 +106,9 @@ public class StaticRouteProfile implements StaticRoute { public Class getEntityType() { return StaticRoute.class; } + + @Override + public String getName() { + return null; + } } diff --git a/api/src/main/java/com/cloud/user/SSHKeyPair.java b/api/src/main/java/com/cloud/user/SSHKeyPair.java index aa20c17eb07..4cecc708af7 100644 --- a/api/src/main/java/com/cloud/user/SSHKeyPair.java +++ b/api/src/main/java/com/cloud/user/SSHKeyPair.java @@ -17,9 +17,10 @@ package com.cloud.user; import org.apache.cloudstack.acl.ControlledEntity; +import org.apache.cloudstack.api.Identity; import org.apache.cloudstack.api.InternalIdentity; -public interface SSHKeyPair extends ControlledEntity, InternalIdentity { +public interface SSHKeyPair extends ControlledEntity, InternalIdentity, Identity { /** * @return The given name of the key pair. diff --git a/api/src/main/java/org/apache/cloudstack/acl/ControlledEntity.java b/api/src/main/java/org/apache/cloudstack/acl/ControlledEntity.java index b8a244f3bdd..7bd8037938e 100644 --- a/api/src/main/java/org/apache/cloudstack/acl/ControlledEntity.java +++ b/api/src/main/java/org/apache/cloudstack/acl/ControlledEntity.java @@ -30,4 +30,5 @@ public interface ControlledEntity extends OwnedBy, PartOf { } Class getEntityType(); + String getName(); } diff --git a/api/src/main/java/org/apache/cloudstack/annotation/AnnotationService.java b/api/src/main/java/org/apache/cloudstack/annotation/AnnotationService.java index 769a753b587..0aca007a44f 100644 --- a/api/src/main/java/org/apache/cloudstack/annotation/AnnotationService.java +++ b/api/src/main/java/org/apache/cloudstack/annotation/AnnotationService.java @@ -16,27 +16,46 @@ // under the License. package org.apache.cloudstack.annotation; +import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.command.admin.annotation.AddAnnotationCmd; import org.apache.cloudstack.api.command.admin.annotation.ListAnnotationsCmd; import org.apache.cloudstack.api.command.admin.annotation.RemoveAnnotationCmd; +import org.apache.cloudstack.api.command.admin.annotation.UpdateAnnotationVisibilityCmd; import org.apache.cloudstack.api.response.AnnotationResponse; import org.apache.cloudstack.api.response.ListResponse; +import java.util.ArrayList; +import java.util.List; + public interface AnnotationService { ListResponse searchForAnnotations(ListAnnotationsCmd cmd); AnnotationResponse addAnnotation(AddAnnotationCmd addAnnotationCmd); - AnnotationResponse addAnnotation(String text, EntityType type, String uuid); + AnnotationResponse addAnnotation(String text, EntityType type, String uuid, boolean adminsOnly); AnnotationResponse removeAnnotation(RemoveAnnotationCmd removeAnnotationCmd); - enum EntityType { - HOST("host"), DOMAIN("domain"), VM("vm_instance"); - private String tableName; + AnnotationResponse updateAnnotationVisibility(UpdateAnnotationVisibilityCmd cmd); - EntityType(String tableName) { - this.tableName = tableName; + enum EntityType { + VM(true), VOLUME(true), SNAPSHOT(true), + VM_SNAPSHOT(true), INSTANCE_GROUP(true), SSH_KEYPAIR(true), + NETWORK(true), VPC(true), PUBLIC_IP_ADDRESS(true), VPN_CUSTOMER_GATEWAY(true), + TEMPLATE(true), ISO(true), KUBERNETES_CLUSTER(true), + SERVICE_OFFERING(false), DISK_OFFERING(false), NETWORK_OFFERING(false), + ZONE(false), POD(false), CLUSTER(false), HOST(false), DOMAIN(false), + PRIMARY_STORAGE(false), SECONDARY_STORAGE(false), VR(false), SYSTEM_VM(false); + + private final boolean usersAllowed; + + public boolean isUserAllowed() { + return this.usersAllowed; } + + EntityType(boolean usersAllowed) { + this.usersAllowed = usersAllowed; + } + static public boolean contains(String representation) { try { /* EntityType tiep = */ valueOf(representation); @@ -45,5 +64,24 @@ public interface AnnotationService { return false; } } + + static public List getNotAllowedTypesForNonAdmins(RoleType roleType) { + List list = new ArrayList<>(); + list.add(EntityType.NETWORK_OFFERING); + list.add(EntityType.ZONE); + list.add(EntityType.POD); + list.add(EntityType.CLUSTER); + list.add(EntityType.HOST); + list.add(EntityType.PRIMARY_STORAGE); + list.add(EntityType.SECONDARY_STORAGE); + list.add(EntityType.VR); + list.add(EntityType.SYSTEM_VM); + if (roleType != RoleType.DomainAdmin) { + list.add(EntityType.DOMAIN); + list.add(EntityType.SERVICE_OFFERING); + list.add(EntityType.DISK_OFFERING); + } + return list; + } } } 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 48386a45bf3..f930800b9f8 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -742,6 +742,7 @@ public class ApiConstants { public static final String IAM_GROUPS = "groups"; public static final String ENTITY_TYPE = "entitytype"; public static final String ENTITY_ID = "entityid"; + public static final String ENTITY_NAME = "entityname"; public static final String EXTERNAL_ID = "externalid"; public static final String ACCESS_TYPE = "accesstype"; @@ -793,7 +794,7 @@ public class ApiConstants { + " \"{}\", not including the double quotes. In this is the exact string\n" + " representing the java supported algorithm, i.e. MD5 or SHA-256. Note that java does not\n" + " contain an algorithm called SHA256 or one called sha-256, only SHA-256."; - public static final String HAS_ANNOTATION = "hasannotation"; + public static final String HAS_ANNOTATIONS = "hasannotations"; public static final String LAST_ANNOTATED = "lastannotated"; public static final String LDAP_DOMAIN = "ldapdomain"; @@ -844,7 +845,10 @@ public class ApiConstants { public static final String SOURCETEMPLATEID = "sourcetemplateid"; public static final String DYNAMIC_SCALING_ENABLED = "dynamicscalingenabled"; - public static final String POOL_TYPE ="pooltype"; + public static final String POOL_TYPE = "pooltype"; + + public static final String ADMINS_ONLY = "adminsonly"; + public static final String ANNOTATION_FILTER = "annotationfilter"; public enum BootType { UEFI, BIOS; diff --git a/api/src/main/java/org/apache/cloudstack/api/BaseResponseWithAnnotations.java b/api/src/main/java/org/apache/cloudstack/api/BaseResponseWithAnnotations.java new file mode 100644 index 00000000000..f7c0c21395f --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/BaseResponseWithAnnotations.java @@ -0,0 +1,35 @@ +// 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; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +public abstract class BaseResponseWithAnnotations extends BaseResponse { + + @SerializedName(ApiConstants.HAS_ANNOTATIONS) + @Param(description = "true if the entity/resource has annotations") + private Boolean hasAnnotation; + + public Boolean hasAnnotation() { + return hasAnnotation; + } + + public void setHasAnnotation(Boolean hasAnnotation) { + this.hasAnnotation = hasAnnotation; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/BaseResponseWithTagInformation.java b/api/src/main/java/org/apache/cloudstack/api/BaseResponseWithTagInformation.java index 3d694a3141d..710b9f0b9ec 100755 --- a/api/src/main/java/org/apache/cloudstack/api/BaseResponseWithTagInformation.java +++ b/api/src/main/java/org/apache/cloudstack/api/BaseResponseWithTagInformation.java @@ -23,7 +23,7 @@ import org.apache.cloudstack.api.response.ResourceTagResponse; import com.cloud.serializer.Param; import com.google.gson.annotations.SerializedName; -public abstract class BaseResponseWithTagInformation extends BaseResponse { +public abstract class BaseResponseWithTagInformation extends BaseResponseWithAnnotations { @SerializedName(ApiConstants.TAGS) @Param(description = "the list of resource tags associated", responseObject = ResourceTagResponse.class) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/annotation/AddAnnotationCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/annotation/AddAnnotationCmd.java index 07a73ce095f..eff25d929e5 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/annotation/AddAnnotationCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/annotation/AddAnnotationCmd.java @@ -31,6 +31,7 @@ import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.response.AnnotationResponse; import org.apache.cloudstack.context.CallContext; +import org.apache.commons.lang3.BooleanUtils; @APICommand(name = AddAnnotationCmd.APINAME, description = "add an annotation.", responseObject = AnnotationResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.11", authorized = {RoleType.Admin}) @@ -40,11 +41,17 @@ public class AddAnnotationCmd extends BaseCmd { @Parameter(name = ApiConstants.ANNOTATION, type = CommandType.STRING, description = "the annotation text") private String annotation; + @Parameter(name = ApiConstants.ENTITY_TYPE, type = CommandType.STRING, description = "the entity type (only HOST is allowed atm)") private String entityType; + @Parameter(name = ApiConstants.ENTITY_ID, type = CommandType.STRING, description = "the id of the entity to annotate") private String entityUuid; + @Parameter(name = ApiConstants.ADMINS_ONLY, type = CommandType.BOOLEAN, since = "4.16.0", + description = "the annotation is visible for admins only") + private Boolean adminsOnly; + public String getAnnotation() { return annotation; } @@ -63,6 +70,10 @@ public class AddAnnotationCmd extends BaseCmd { return entityUuid; } + public boolean isAdminsOnly() { + return BooleanUtils.toBoolean(adminsOnly); + } + @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/annotation/ListAnnotationsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/annotation/ListAnnotationsCmd.java index 4657eb9e16a..92e5414c33e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/annotation/ListAnnotationsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/annotation/ListAnnotationsCmd.java @@ -41,11 +41,24 @@ public class ListAnnotationsCmd extends BaseListCmd { @Parameter(name = ApiConstants.ID, type = CommandType.STRING, description = "the id of the annotation") private String uuid; + @Parameter(name = ApiConstants.ENTITY_TYPE, type = CommandType.STRING, description = "the entity type") private String entityType; + @Parameter(name = ApiConstants.ENTITY_ID, type = CommandType.STRING, description = "the id of the entity for which to show annotations") private String entityUuid; + @Parameter(name = ApiConstants.USER_ID, type = CommandType.STRING, since = "4.16.0", + description = "optional: the id of the user of the annotation", required = false) + private String userUuid; + + @Parameter(name = ApiConstants.ANNOTATION_FILTER, + type = CommandType.STRING, since = "4.16.0", + description = "possible values are \"self\" and \"all\". " + + "* self : annotations that have been created by the calling user. " + + "* all : all the annotations the calling user can access") + private String annotationFilter; + public String getUuid() { return uuid; } @@ -58,6 +71,14 @@ public class ListAnnotationsCmd extends BaseListCmd { return entityUuid; } + public String getUserUuid() { + return userUuid; + } + + public String getAnnotationFilter() { + return annotationFilter; + } + @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/annotation/UpdateAnnotationVisibilityCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/annotation/UpdateAnnotationVisibilityCmd.java new file mode 100644 index 00000000000..d152e10e5d1 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/annotation/UpdateAnnotationVisibilityCmd.java @@ -0,0 +1,74 @@ +// 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.annotation; + +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +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.ServerApiException; +import org.apache.cloudstack.api.response.AnnotationResponse; +import org.apache.cloudstack.context.CallContext; + +@APICommand(name = UpdateAnnotationVisibilityCmd.APINAME, description = "update an annotation visibility.", + responseObject = AnnotationResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + since = "4.16", authorized = {RoleType.Admin}) +public class UpdateAnnotationVisibilityCmd extends BaseCmd { + + public static final String APINAME = "updateAnnotationVisibility"; + + @Parameter(name = ApiConstants.ID, type = CommandType.STRING, required = true, + description = "the id of the annotation") + private String uuid; + + @Parameter(name = ApiConstants.ADMINS_ONLY, type = CommandType.BOOLEAN, required = true, + description = "the annotation is visible for admins only") + private Boolean adminsOnly; + + public String getUuid() { + return uuid; + } + + public Boolean getAdminsOnly() { + return adminsOnly; + } + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, + ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + AnnotationResponse annotationResponse = annotationService.updateAnnotationVisibility(this); + annotationResponse.setResponseName(getCommandName()); + this.setResponseObject(annotationResponse); + } + + @Override + public String getCommandName() { + return String.format("%s%s", APINAME.toLowerCase(), BaseCmd.RESPONSE_SUFFIX); + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getAccountId(); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/host/UpdateHostCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/host/UpdateHostCmd.java index 16fc608930d..40f19388934 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/host/UpdateHostCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/host/UpdateHostCmd.java @@ -124,7 +124,7 @@ public class UpdateHostCmd extends BaseCmd { try { result = _resourceService.updateHost(this); if(getAnnotation() != null) { - annotationService.addAnnotation(getAnnotation(), AnnotationService.EntityType.HOST, result.getUuid()); + annotationService.addAnnotation(getAnnotation(), AnnotationService.EntityType.HOST, result.getUuid(), true); } HostResponse hostResponse = _responseGenerator.createHostResponse(result); hostResponse.setResponseName(getCommandName()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreateEgressFirewallRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreateEgressFirewallRuleCmd.java index be5a613c445..3bb140011c4 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreateEgressFirewallRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreateEgressFirewallRuleCmd.java @@ -384,4 +384,9 @@ public class CreateEgressFirewallRuleCmd extends BaseAsyncCreateCmd implements F return FirewallRule.class; } + @Override + public String getName() { + return null; + } + } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreateFirewallRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreateFirewallRuleCmd.java index ea5657cf965..ca66ee762b1 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreateFirewallRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreateFirewallRuleCmd.java @@ -358,4 +358,9 @@ public class CreateFirewallRuleCmd extends BaseAsyncCreateCmd implements Firewal return FirewallRule.class; } + @Override + public String getName() { + return null; + } + } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreatePortForwardingRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreatePortForwardingRuleCmd.java index f3c9e590851..6a4e802e93a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreatePortForwardingRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreatePortForwardingRuleCmd.java @@ -447,4 +447,9 @@ public class CreatePortForwardingRuleCmd extends BaseAsyncCreateCmd implements P return FirewallRule.class; } + @Override + public String getName() { + return null; + } + } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/nat/CreateIpForwardingRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/nat/CreateIpForwardingRuleCmd.java index 1e65a413fd1..de7f68de004 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/nat/CreateIpForwardingRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/nat/CreateIpForwardingRuleCmd.java @@ -333,4 +333,9 @@ public class CreateIpForwardingRuleCmd extends BaseAsyncCreateCmd implements Sta return FirewallRule.class; } + @Override + public String getName() { + return null; + } + } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/AnnotationResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/AnnotationResponse.java index c16971ae7f3..f3aed3e7be5 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/AnnotationResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/AnnotationResponse.java @@ -43,6 +43,10 @@ public class AnnotationResponse extends BaseResponse { @Param(description = "the (uu)id of the entitiy to which this annotation pertains") private String entityUuid; + @SerializedName(ApiConstants.ENTITY_NAME) + @Param(description = "the name of the entitiy to which this annotation pertains") + private String entityName; + @SerializedName(ApiConstants.ANNOTATION) @Param(description = "the contents of the annotation") private String annotation; @@ -51,6 +55,14 @@ public class AnnotationResponse extends BaseResponse { @Param(description = "The (uu)id of the user that entered the annotation") private String userUuid; + @SerializedName(ApiConstants.USERNAME) + @Param(description = "The username of the user that entered the annotation") + private String username; + + @SerializedName(ApiConstants.ADMINS_ONLY) + @Param(description = "True if the annotation is available for admins only") + private Boolean adminsOnly; + @SerializedName(ApiConstants.CREATED) @Param(description = "the creation timestamp for this annotation") private Date created; @@ -118,4 +130,28 @@ public class AnnotationResponse extends BaseResponse { public void setRemoved(Date removed) { this.removed = removed; } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public Boolean getAdminsOnly() { + return adminsOnly; + } + + public void setAdminsOnly(Boolean adminsOnly) { + this.adminsOnly = adminsOnly; + } + + public String getEntityName() { + return entityName; + } + + public void setEntityName(String entityName) { + this.entityName = entityName; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ClusterResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ClusterResponse.java index c27ee3ded6b..72dab3da3b1 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ClusterResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ClusterResponse.java @@ -22,7 +22,7 @@ import java.util.List; import java.util.Map; import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.BaseResponseWithAnnotations; import org.apache.cloudstack.api.EntityReference; import com.cloud.org.Cluster; @@ -30,7 +30,7 @@ import com.cloud.serializer.Param; import com.google.gson.annotations.SerializedName; @EntityReference(value = Cluster.class) -public class ClusterResponse extends BaseResponse { +public class ClusterResponse extends BaseResponseWithAnnotations { @SerializedName(ApiConstants.ID) @Param(description = "the cluster ID") private String id; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/CreateSSHKeyPairResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/CreateSSHKeyPairResponse.java index 410719fdc4b..b43eb453a01 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/CreateSSHKeyPairResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/CreateSSHKeyPairResponse.java @@ -28,8 +28,8 @@ public class CreateSSHKeyPairResponse extends SSHKeyPairResponse { public CreateSSHKeyPairResponse() { } - public CreateSSHKeyPairResponse(String name, String fingerprint, String privateKey) { - super(name, fingerprint); + public CreateSSHKeyPairResponse(String uuid, String name, String fingerprint, String privateKey) { + super(uuid, name, fingerprint); this.privateKey = privateKey; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/DiskOfferingResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/DiskOfferingResponse.java index 5398dd32bd6..004a7027262 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/DiskOfferingResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/DiskOfferingResponse.java @@ -19,7 +19,7 @@ package org.apache.cloudstack.api.response; import java.util.Date; import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.BaseResponseWithAnnotations; import org.apache.cloudstack.api.EntityReference; import com.cloud.offering.DiskOffering; @@ -27,7 +27,7 @@ import com.cloud.serializer.Param; import com.google.gson.annotations.SerializedName; @EntityReference(value = DiskOffering.class) -public class DiskOfferingResponse extends BaseResponse { +public class DiskOfferingResponse extends BaseResponseWithAnnotations { @SerializedName(ApiConstants.ID) @Param(description = "unique ID of the disk offering") private String id; 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 818822bf527..556fe04fd6f 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 @@ -19,7 +19,7 @@ package org.apache.cloudstack.api.response; import com.google.gson.annotations.SerializedName; import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.BaseResponseWithAnnotations; import org.apache.cloudstack.api.EntityReference; import com.cloud.domain.Domain; @@ -28,7 +28,7 @@ import com.cloud.serializer.Param; import java.util.Date; @EntityReference(value = Domain.class) -public class DomainResponse extends BaseResponse implements ResourceLimitAndCountResponse { +public class DomainResponse extends BaseResponseWithAnnotations implements ResourceLimitAndCountResponse { @SerializedName(ApiConstants.ID) @Param(description = "the ID of the domain") private String id; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/DomainRouterResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/DomainRouterResponse.java index 563d6a9f961..66f40307449 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/DomainRouterResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/DomainRouterResponse.java @@ -22,7 +22,7 @@ import java.util.List; import java.util.Set; import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.BaseResponseWithAnnotations; import org.apache.cloudstack.api.EntityReference; import com.cloud.serializer.Param; @@ -32,7 +32,7 @@ import com.google.gson.annotations.SerializedName; @EntityReference(value = VirtualMachine.class) @SuppressWarnings("unused") -public class DomainRouterResponse extends BaseResponse implements ControlledViewEntityResponse { +public class DomainRouterResponse extends BaseResponseWithAnnotations implements ControlledViewEntityResponse { @SerializedName(ApiConstants.ID) @Param(description = "the id of the router") private String id; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/HostResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/HostResponse.java index 526dffa707a..fcf0870bcdd 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/HostResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/HostResponse.java @@ -22,7 +22,7 @@ import java.util.List; import java.util.Map; import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.BaseResponseWithAnnotations; import org.apache.cloudstack.api.EntityReference; import org.apache.cloudstack.ha.HAConfig; import org.apache.cloudstack.outofbandmanagement.OutOfBandManagement; @@ -34,7 +34,7 @@ import com.cloud.serializer.Param; import com.google.gson.annotations.SerializedName; @EntityReference(value = Host.class) -public class HostResponse extends BaseResponse { +public class HostResponse extends BaseResponseWithAnnotations { @SerializedName(ApiConstants.ID) @Param(description = "the ID of the host") private String id; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/IPAddressResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/IPAddressResponse.java index f66a61a111a..13497d89308 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/IPAddressResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/IPAddressResponse.java @@ -21,7 +21,7 @@ import java.util.List; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.BaseResponseWithAnnotations; import org.apache.cloudstack.api.EntityReference; import com.cloud.network.IpAddress; @@ -30,7 +30,7 @@ import com.google.gson.annotations.SerializedName; @EntityReference(value = IpAddress.class) @SuppressWarnings("unused") -public class IPAddressResponse extends BaseResponse implements ControlledEntityResponse { +public class IPAddressResponse extends BaseResponseWithAnnotations implements ControlledEntityResponse { @SerializedName(ApiConstants.ID) @Param(description = "public IP address id") private String id; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ImageStoreResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ImageStoreResponse.java index 190181e67a9..2997c50eac9 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ImageStoreResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ImageStoreResponse.java @@ -17,7 +17,7 @@ package org.apache.cloudstack.api.response; import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.BaseResponseWithAnnotations; import org.apache.cloudstack.api.EntityReference; import com.cloud.serializer.Param; @@ -26,7 +26,7 @@ import com.cloud.storage.ScopeType; import com.google.gson.annotations.SerializedName; @EntityReference(value = ImageStore.class) -public class ImageStoreResponse extends BaseResponse { +public class ImageStoreResponse extends BaseResponseWithAnnotations { @SerializedName("id") @Param(description = "the ID of the image store") private String id; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/InstanceGroupResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/InstanceGroupResponse.java index 39f4b2f8538..e1241cc19bc 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/InstanceGroupResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/InstanceGroupResponse.java @@ -21,7 +21,7 @@ import java.util.Date; import com.google.gson.annotations.SerializedName; import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.BaseResponseWithAnnotations; import org.apache.cloudstack.api.EntityReference; import com.cloud.serializer.Param; @@ -29,7 +29,7 @@ import com.cloud.vm.InstanceGroup; @SuppressWarnings("unused") @EntityReference(value = InstanceGroup.class) -public class InstanceGroupResponse extends BaseResponse implements ControlledViewEntityResponse { +public class InstanceGroupResponse extends BaseResponseWithAnnotations implements ControlledViewEntityResponse { @SerializedName(ApiConstants.ID) @Param(description = "the ID of the instance group") diff --git a/api/src/main/java/org/apache/cloudstack/api/response/NetworkOfferingResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/NetworkOfferingResponse.java index e9bda5d37e7..a3e979ec952 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/NetworkOfferingResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/NetworkOfferingResponse.java @@ -21,7 +21,7 @@ import java.util.List; import java.util.Map; import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.BaseResponseWithAnnotations; import org.apache.cloudstack.api.EntityReference; import com.cloud.offering.NetworkOffering; @@ -30,7 +30,7 @@ import com.google.gson.annotations.SerializedName; @EntityReference(value = NetworkOffering.class) @SuppressWarnings("unused") -public class NetworkOfferingResponse extends BaseResponse { +public class NetworkOfferingResponse extends BaseResponseWithAnnotations { @SerializedName("id") @Param(description = "the id of the network offering") private String id; 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 445321bddf4..80b1999eaca 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 @@ -23,7 +23,7 @@ import java.util.Set; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.BaseResponseWithAnnotations; import org.apache.cloudstack.api.EntityReference; import com.cloud.network.Network; @@ -33,7 +33,7 @@ import com.google.gson.annotations.SerializedName; @SuppressWarnings("unused") @EntityReference(value = {Network.class, ProjectAccount.class}) -public class NetworkResponse extends BaseResponse implements ControlledEntityResponse { +public class NetworkResponse extends BaseResponseWithAnnotations implements ControlledEntityResponse { @SerializedName(ApiConstants.ID) @Param(description = "the id of the network") diff --git a/api/src/main/java/org/apache/cloudstack/api/response/PodResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/PodResponse.java index 27ebf71a994..038e3a2f746 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/PodResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/PodResponse.java @@ -21,14 +21,14 @@ import java.util.List; import com.google.gson.annotations.SerializedName; import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.BaseResponseWithAnnotations; import org.apache.cloudstack.api.EntityReference; import com.cloud.dc.Pod; import com.cloud.serializer.Param; @EntityReference(value = Pod.class) -public class PodResponse extends BaseResponse { +public class PodResponse extends BaseResponseWithAnnotations { @SerializedName("id") @Param(description = "the ID of the Pod") private String id; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/SSHKeyPairResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/SSHKeyPairResponse.java index f494ad2ef4b..5a4d69b76cc 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/SSHKeyPairResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/SSHKeyPairResponse.java @@ -19,11 +19,15 @@ package org.apache.cloudstack.api.response; import com.google.gson.annotations.SerializedName; import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.BaseResponse; import com.cloud.serializer.Param; +import org.apache.cloudstack.api.BaseResponseWithAnnotations; -public class SSHKeyPairResponse extends BaseResponse { +public class SSHKeyPairResponse extends BaseResponseWithAnnotations { + + @SerializedName(ApiConstants.ID) + @Param(description = "ID of the ssh keypair") + private String id; @SerializedName(ApiConstants.NAME) @Param(description = "Name of the keypair") @@ -45,7 +49,8 @@ public class SSHKeyPairResponse extends BaseResponse { public SSHKeyPairResponse() { } - public SSHKeyPairResponse(String name, String fingerprint) { + public SSHKeyPairResponse(String uuid, String name, String fingerprint) { + this.id = uuid; this.name = name; this.fingerprint = fingerprint; } @@ -89,4 +94,12 @@ public class SSHKeyPairResponse extends BaseResponse { public void setDomainName(String domain) { this.domain = domain; } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ServiceOfferingResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ServiceOfferingResponse.java index 42f89b1b8a9..ea9d8eef7a0 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ServiceOfferingResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ServiceOfferingResponse.java @@ -20,7 +20,7 @@ import java.util.Date; import java.util.Map; import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.BaseResponseWithAnnotations; import org.apache.cloudstack.api.EntityReference; import com.cloud.offering.ServiceOffering; @@ -28,7 +28,7 @@ import com.cloud.serializer.Param; import com.google.gson.annotations.SerializedName; @EntityReference(value = ServiceOffering.class) -public class ServiceOfferingResponse extends BaseResponse { +public class ServiceOfferingResponse extends BaseResponseWithAnnotations { @SerializedName("id") @Param(description = "the id of the service offering") private String id; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/Site2SiteCustomerGatewayResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/Site2SiteCustomerGatewayResponse.java index 88e0e1600e5..babc9bf4432 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/Site2SiteCustomerGatewayResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/Site2SiteCustomerGatewayResponse.java @@ -21,7 +21,7 @@ import java.util.Date; import com.google.gson.annotations.SerializedName; import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.BaseResponseWithAnnotations; import org.apache.cloudstack.api.EntityReference; import com.cloud.network.Site2SiteCustomerGateway; @@ -29,7 +29,7 @@ import com.cloud.serializer.Param; @EntityReference(value = Site2SiteCustomerGateway.class) @SuppressWarnings("unused") -public class Site2SiteCustomerGatewayResponse extends BaseResponse implements ControlledEntityResponse { +public class Site2SiteCustomerGatewayResponse extends BaseResponseWithAnnotations implements ControlledEntityResponse { @SerializedName(ApiConstants.ID) @Param(description = "the vpn gateway ID") private String id; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/StoragePoolResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/StoragePoolResponse.java index a03c2d8d751..89256c26473 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/StoragePoolResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/StoragePoolResponse.java @@ -21,14 +21,14 @@ import com.cloud.storage.StoragePool; import com.cloud.storage.StoragePoolStatus; import com.google.gson.annotations.SerializedName; import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.BaseResponseWithAnnotations; import org.apache.cloudstack.api.EntityReference; import java.util.Date; import java.util.Map; @EntityReference(value = StoragePool.class) -public class StoragePoolResponse extends BaseResponse { +public class StoragePoolResponse extends BaseResponseWithAnnotations { @SerializedName("id") @Param(description = "the ID of the storage pool") private String id; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/SystemVmResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/SystemVmResponse.java index bfc9f9aad86..ce2b406d15f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/SystemVmResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/SystemVmResponse.java @@ -20,7 +20,7 @@ import java.util.Date; import java.util.List; import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.BaseResponseWithAnnotations; import org.apache.cloudstack.api.EntityReference; import com.cloud.host.Status; @@ -29,7 +29,7 @@ import com.cloud.vm.VirtualMachine; import com.google.gson.annotations.SerializedName; @EntityReference(value = VirtualMachine.class) -public class SystemVmResponse extends BaseResponse { +public class SystemVmResponse extends BaseResponseWithAnnotations { @SerializedName("id") @Param(description = "the ID of the system VM") private String id; 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 9c9f059384e..f91945f3bcf 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 @@ -21,7 +21,7 @@ import java.util.List; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.BaseResponseWithAnnotations; import org.apache.cloudstack.api.EntityReference; import com.cloud.network.vpc.Vpc; @@ -30,7 +30,7 @@ import com.google.gson.annotations.SerializedName; @EntityReference(value = Vpc.class) @SuppressWarnings("unused") -public class VpcResponse extends BaseResponse implements ControlledEntityResponse { +public class VpcResponse extends BaseResponseWithAnnotations implements ControlledEntityResponse { @SerializedName("id") @Param(description = "the id of the VPC") private String id; 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 e95333fbd65..47f3b0d050c 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 @@ -23,7 +23,7 @@ import java.util.Map; import java.util.Set; import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.BaseResponseWithAnnotations; import org.apache.cloudstack.api.EntityReference; import com.cloud.dc.DataCenter; @@ -32,7 +32,7 @@ import com.google.gson.annotations.SerializedName; @SuppressWarnings("unused") @EntityReference(value = DataCenter.class) -public class ZoneResponse extends BaseResponse { +public class ZoneResponse extends BaseResponseWithAnnotations { @SerializedName(ApiConstants.ID) @Param(description = "Zone id") private String id; diff --git a/core/src/main/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml b/core/src/main/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml index affd441fb6c..cb5e9652165 100644 --- a/core/src/main/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml +++ b/core/src/main/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml @@ -332,4 +332,7 @@ class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry"> + + diff --git a/core/src/main/resources/META-INF/cloudstack/kubernetes/module.properties b/core/src/main/resources/META-INF/cloudstack/kubernetes/module.properties new file mode 100644 index 00000000000..ea954a9573a --- /dev/null +++ b/core/src/main/resources/META-INF/cloudstack/kubernetes/module.properties @@ -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. +# + +name=kubernetes +parent=compute \ No newline at end of file diff --git a/core/src/main/resources/META-INF/cloudstack/kubernetes/spring-core-lifecycle-kubernetes-context-inheritable.xml b/core/src/main/resources/META-INF/cloudstack/kubernetes/spring-core-lifecycle-kubernetes-context-inheritable.xml new file mode 100644 index 00000000000..df1a4b5c229 --- /dev/null +++ b/core/src/main/resources/META-INF/cloudstack/kubernetes/spring-core-lifecycle-kubernetes-context-inheritable.xml @@ -0,0 +1,32 @@ + + + + + + + + + diff --git a/engine/components-api/src/main/java/com/cloud/network/addr/PublicIp.java b/engine/components-api/src/main/java/com/cloud/network/addr/PublicIp.java index 8e9d7995ad8..d1153a291f8 100644 --- a/engine/components-api/src/main/java/com/cloud/network/addr/PublicIp.java +++ b/engine/components-api/src/main/java/com/cloud/network/addr/PublicIp.java @@ -255,6 +255,11 @@ public class PublicIp implements PublicIpAddress { return IpAddress.class; } + @Override + public String getName() { + return _addr.getName(); + } + @Override public State getRuleState() { return _addr.getRuleState(); diff --git a/engine/components-api/src/main/java/com/cloud/network/rules/StaticNatRuleImpl.java b/engine/components-api/src/main/java/com/cloud/network/rules/StaticNatRuleImpl.java index 96950a12c84..4d8270ca078 100644 --- a/engine/components-api/src/main/java/com/cloud/network/rules/StaticNatRuleImpl.java +++ b/engine/components-api/src/main/java/com/cloud/network/rules/StaticNatRuleImpl.java @@ -158,4 +158,9 @@ public class StaticNatRuleImpl implements StaticNatRule { public Class getEntityType() { return FirewallRule.class; } + + @Override + public String getName() { + return null; + } } diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java index 4c498d114f9..69c5cc6e278 100755 --- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java @@ -45,6 +45,8 @@ import javax.naming.ConfigurationException; import com.cloud.api.ApiDBUtils; import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; +import org.apache.cloudstack.annotation.AnnotationService; +import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.command.admin.vm.MigrateVMCmd; import org.apache.cloudstack.api.command.admin.volume.MigrateVolumeCmdByAdmin; @@ -368,6 +370,8 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac private NetworkOfferingDao networkOfferingDao; @Inject private DomainRouterJoinDao domainRouterJoinDao; + @Inject + private AnnotationDao annotationDao; VmWorkJobHandlerProxy _jobHandlerProxy = new VmWorkJobHandlerProxy(this); @@ -627,6 +631,9 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac // Remove deploy as-is (if any) userVmDeployAsIsDetailsDao.removeDetails(vm.getId()); + // Remove comments (if any) + annotationDao.removeByEntityType(AnnotationService.EntityType.VM.name(), vm.getUuid()); + // send hypervisor-dependent commands before removing final List finalizeExpungeCommands = hvGuru.finalizeExpunge(vm); if (finalizeExpungeCommands != null && finalizeExpungeCommands.size() > 0) { diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java index 3c5dd41c725..8fdf30bd56f 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java @@ -48,6 +48,8 @@ import com.cloud.dc.ClusterVO; import com.cloud.dc.dao.ClusterDao; import com.cloud.deployasis.dao.TemplateDeployAsIsDetailsDao; import org.apache.cloudstack.acl.ControlledEntity.ACLType; +import org.apache.cloudstack.annotation.AnnotationService; +import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.api.ApiConstants; import com.cloud.agent.api.to.deployasis.OVFNetworkTO; import org.apache.cloudstack.context.CallContext; @@ -317,6 +319,8 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra TemplateDeployAsIsDetailsDao templateDeployAsIsDetailsDao; @Inject ResourceManager resourceManager; + @Inject + private AnnotationDao annotationDao; List networkGurus; @@ -3673,6 +3677,8 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra throw new CloudRuntimeException("We should never get to here because we used true when applyIpAssociations", e); } + annotationDao.removeByEntityType(AnnotationService.EntityType.NETWORK.name(), network.getUuid()); + return success; } diff --git a/engine/schema/src/main/java/com/cloud/event/EventVO.java b/engine/schema/src/main/java/com/cloud/event/EventVO.java index 9be37ddb966..e5cf2a27397 100644 --- a/engine/schema/src/main/java/com/cloud/event/EventVO.java +++ b/engine/schema/src/main/java/com/cloud/event/EventVO.java @@ -224,4 +224,9 @@ public class EventVO implements Event { public Class getEntityType() { return Event.class; } + + @Override + public String getName() { + return null; + } } diff --git a/engine/schema/src/main/java/com/cloud/network/UserIpv6AddressVO.java b/engine/schema/src/main/java/com/cloud/network/UserIpv6AddressVO.java index e97961a2dae..65688179602 100644 --- a/engine/schema/src/main/java/com/cloud/network/UserIpv6AddressVO.java +++ b/engine/schema/src/main/java/com/cloud/network/UserIpv6AddressVO.java @@ -189,4 +189,9 @@ public class UserIpv6AddressVO implements UserIpv6Address { public Class getEntityType() { return UserIpv6Address.class; } + + @Override + public String getName() { + return null; + } } diff --git a/engine/schema/src/main/java/com/cloud/network/VpnUserVO.java b/engine/schema/src/main/java/com/cloud/network/VpnUserVO.java index 9e403e4ec99..aadbd3f375e 100644 --- a/engine/schema/src/main/java/com/cloud/network/VpnUserVO.java +++ b/engine/schema/src/main/java/com/cloud/network/VpnUserVO.java @@ -130,4 +130,9 @@ public class VpnUserVO implements VpnUser { public Class getEntityType() { return VpnUser.class; } + + @Override + public String getName() { + return null; + } } diff --git a/engine/schema/src/main/java/com/cloud/network/as/AutoScalePolicyVO.java b/engine/schema/src/main/java/com/cloud/network/as/AutoScalePolicyVO.java index 1842533000f..6379f65c30d 100644 --- a/engine/schema/src/main/java/com/cloud/network/as/AutoScalePolicyVO.java +++ b/engine/schema/src/main/java/com/cloud/network/as/AutoScalePolicyVO.java @@ -156,4 +156,9 @@ public class AutoScalePolicyVO implements AutoScalePolicy, InternalIdentity { return AutoScalePolicy.class; } + @Override + public String getName() { + return null; + } + } diff --git a/engine/schema/src/main/java/com/cloud/network/as/AutoScaleVmGroupVO.java b/engine/schema/src/main/java/com/cloud/network/as/AutoScaleVmGroupVO.java index d32e7f873a0..ea8eaf68806 100644 --- a/engine/schema/src/main/java/com/cloud/network/as/AutoScaleVmGroupVO.java +++ b/engine/schema/src/main/java/com/cloud/network/as/AutoScaleVmGroupVO.java @@ -229,4 +229,9 @@ public class AutoScaleVmGroupVO implements AutoScaleVmGroup, InternalIdentity { public Class getEntityType() { return AutoScaleVmGroup.class; } + + @Override + public String getName() { + return null; + } } diff --git a/engine/schema/src/main/java/com/cloud/network/as/AutoScaleVmProfileVO.java b/engine/schema/src/main/java/com/cloud/network/as/AutoScaleVmProfileVO.java index 69e4c8190f4..eb7e34a8789 100644 --- a/engine/schema/src/main/java/com/cloud/network/as/AutoScaleVmProfileVO.java +++ b/engine/schema/src/main/java/com/cloud/network/as/AutoScaleVmProfileVO.java @@ -238,4 +238,9 @@ public class AutoScaleVmProfileVO implements AutoScaleVmProfile, Identity, Inter return AutoScaleVmProfile.class; } + @Override + public String getName() { + return null; + } + } diff --git a/engine/schema/src/main/java/com/cloud/network/as/ConditionVO.java b/engine/schema/src/main/java/com/cloud/network/as/ConditionVO.java index 5035f731b57..5a512abda41 100644 --- a/engine/schema/src/main/java/com/cloud/network/as/ConditionVO.java +++ b/engine/schema/src/main/java/com/cloud/network/as/ConditionVO.java @@ -133,4 +133,9 @@ public class ConditionVO implements Condition, Identity, InternalIdentity { return Condition.class; } + @Override + public String getName() { + return null; + } + } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/IPAddressVO.java b/engine/schema/src/main/java/com/cloud/network/dao/IPAddressVO.java index 686f4975edb..7c4d56bd1ee 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/IPAddressVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/IPAddressVO.java @@ -366,6 +366,11 @@ public class IPAddressVO implements IpAddress { return IpAddress.class; } + @Override + public String getName() { + return address.addr(); + } + @Override public Date getRemoved() { return removed; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/MonitoringServiceVO.java b/engine/schema/src/main/java/com/cloud/network/dao/MonitoringServiceVO.java index 01fba00d0ba..97e8a041c6c 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/MonitoringServiceVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/MonitoringServiceVO.java @@ -121,4 +121,9 @@ public class MonitoringServiceVO implements MonitoringService { public Class getEntityType() { return MonitoringService.class; } + + @Override + public String getName() { + return null; + } } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnVO.java b/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnVO.java index 366a6cc52d4..95e3693a99c 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnVO.java @@ -170,4 +170,9 @@ public class RemoteAccessVpnVO implements RemoteAccessVpn { public Class getEntityType() { return RemoteAccessVpn.class; } + + @Override + public String getName() { + return null; + } } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnConnectionVO.java b/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnConnectionVO.java index 04a9d1ca1d4..b032966dd5a 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnConnectionVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnConnectionVO.java @@ -177,4 +177,9 @@ public class Site2SiteVpnConnectionVO implements Site2SiteVpnConnection, Interna public Class getEntityType() { return Site2SiteVpnConnection.class; } + + @Override + public String getName() { + return null; + } } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayVO.java b/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayVO.java index 184162ad92b..703c78c7b86 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayVO.java @@ -134,4 +134,9 @@ public class Site2SiteVpnGatewayVO implements Site2SiteVpnGateway { public Class getEntityType() { return Site2SiteVpnGateway.class; } + + @Override + public String getName() { + return null; + } } diff --git a/engine/schema/src/main/java/com/cloud/network/rules/FirewallRuleVO.java b/engine/schema/src/main/java/com/cloud/network/rules/FirewallRuleVO.java index 282fa7403e7..07b25e7a28c 100644 --- a/engine/schema/src/main/java/com/cloud/network/rules/FirewallRuleVO.java +++ b/engine/schema/src/main/java/com/cloud/network/rules/FirewallRuleVO.java @@ -308,4 +308,9 @@ public class FirewallRuleVO implements FirewallRule { public Class getEntityType() { return FirewallRule.class; } + + @Override + public String getName() { + return null; + } } diff --git a/engine/schema/src/main/java/com/cloud/network/vpc/StaticRouteVO.java b/engine/schema/src/main/java/com/cloud/network/vpc/StaticRouteVO.java index 2c2d53edb29..586f9a4770d 100644 --- a/engine/schema/src/main/java/com/cloud/network/vpc/StaticRouteVO.java +++ b/engine/schema/src/main/java/com/cloud/network/vpc/StaticRouteVO.java @@ -140,4 +140,9 @@ public class StaticRouteVO implements StaticRoute { public Class getEntityType() { return StaticRoute.class; } + + @Override + public String getName() { + return null; + } } \ No newline at end of file diff --git a/engine/schema/src/main/java/com/cloud/network/vpc/VpcGatewayVO.java b/engine/schema/src/main/java/com/cloud/network/vpc/VpcGatewayVO.java index 9919ba3bf7f..72f6a89e70f 100644 --- a/engine/schema/src/main/java/com/cloud/network/vpc/VpcGatewayVO.java +++ b/engine/schema/src/main/java/com/cloud/network/vpc/VpcGatewayVO.java @@ -221,6 +221,11 @@ public class VpcGatewayVO implements VpcGateway { return VpcGateway.class; } + @Override + public String getName() { + return null; + } + public void setVpcId(Long vpcId) { this.vpcId = vpcId; } diff --git a/engine/schema/src/main/java/com/cloud/projects/ProjectInvitationVO.java b/engine/schema/src/main/java/com/cloud/projects/ProjectInvitationVO.java index 4f794a83162..36e772edd3a 100644 --- a/engine/schema/src/main/java/com/cloud/projects/ProjectInvitationVO.java +++ b/engine/schema/src/main/java/com/cloud/projects/ProjectInvitationVO.java @@ -187,4 +187,9 @@ public class ProjectInvitationVO implements ProjectInvitation { public Class getEntityType() { return ProjectInvitation.class; } + + @Override + public String getName() { + return null; + } } diff --git a/engine/schema/src/main/java/com/cloud/tags/ResourceTagVO.java b/engine/schema/src/main/java/com/cloud/tags/ResourceTagVO.java index cc29d8ea15a..1db9a618bbf 100644 --- a/engine/schema/src/main/java/com/cloud/tags/ResourceTagVO.java +++ b/engine/schema/src/main/java/com/cloud/tags/ResourceTagVO.java @@ -171,4 +171,9 @@ public class ResourceTagVO implements ResourceTag { public Class getEntityType() { return ResourceTag.class; } + + @Override + public String getName() { + return null; + } } diff --git a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41520to41600.java b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41520to41600.java index eea3a58ef44..5c5523c7821 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41520to41600.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41520to41600.java @@ -64,6 +64,32 @@ public class Upgrade41520to41600 implements DbUpgrade, DbUpgradeSystemVmTemplate @Override public void performDataMigration(Connection conn) { + generateUuidForExistingSshKeyPairs(conn); + } + + private void generateUuidForExistingSshKeyPairs(Connection conn) { + LOG.debug("Generating uuid for existing ssh key-pairs"); + try { + PreparedStatement pstmt = conn.prepareStatement("SELECT id FROM `cloud`.`ssh_keypairs` WHERE uuid is null"); + ResultSet rs = pstmt.executeQuery(); + if (rs.next()) { + long sshKeyId = rs.getLong(1); + pstmt = conn.prepareStatement("UPDATE `cloud`.`ssh_keypairs` SET `uuid` = UUID() WHERE id = ?"); + pstmt.setLong(1, sshKeyId); + pstmt.executeUpdate(); + } + if (!rs.isClosed()) { + rs.close(); + } + if (!pstmt.isClosed()) { + pstmt.close(); + } + LOG.debug("Successfully generated uuid for existing ssh key-pairs"); + } catch (SQLException e) { + String errMsg = "Exception while generating uuid for existing ssh key-pairs: " + e.getMessage(); + LOG.error(errMsg, e); + throw new CloudRuntimeException(errMsg, e); + } } @Override diff --git a/engine/schema/src/main/java/com/cloud/user/AccountVO.java b/engine/schema/src/main/java/com/cloud/user/AccountVO.java index 36b2508c109..2a285c28c85 100644 --- a/engine/schema/src/main/java/com/cloud/user/AccountVO.java +++ b/engine/schema/src/main/java/com/cloud/user/AccountVO.java @@ -218,4 +218,9 @@ public class AccountVO implements Account { public Class getEntityType() { return Account.class; } + + @Override + public String getName() { + return accountName; + } } diff --git a/engine/schema/src/main/java/com/cloud/user/SSHKeyPairVO.java b/engine/schema/src/main/java/com/cloud/user/SSHKeyPairVO.java index fd7173ee7a8..00feda5fe8a 100644 --- a/engine/schema/src/main/java/com/cloud/user/SSHKeyPairVO.java +++ b/engine/schema/src/main/java/com/cloud/user/SSHKeyPairVO.java @@ -23,16 +23,24 @@ import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; import javax.persistence.Transient; +import java.util.UUID; @Entity @Table(name = "ssh_keypairs") public class SSHKeyPairVO implements SSHKeyPair { + public SSHKeyPairVO() { + uuid = UUID.randomUUID().toString(); + } + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private Long id = null; + @Column(name = "uuid") + private String uuid; + @Column(name = "account_id") private long accountId; @@ -114,6 +122,14 @@ public class SSHKeyPairVO implements SSHKeyPair { this.privateKey = privateKey; } + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + @Override public Class getEntityType() { return SSHKeyPair.class; diff --git a/engine/schema/src/main/java/com/cloud/vm/ConsoleProxyVO.java b/engine/schema/src/main/java/com/cloud/vm/ConsoleProxyVO.java index 729499284a9..8f47ce0583d 100644 --- a/engine/schema/src/main/java/com/cloud/vm/ConsoleProxyVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/ConsoleProxyVO.java @@ -153,4 +153,9 @@ public class ConsoleProxyVO extends VMInstanceVO implements ConsoleProxy { public String toString() { return String.format("Console %s", super.toString()); } + + @Override + public String getName() { + return instanceName; + } } diff --git a/engine/schema/src/main/java/com/cloud/vm/DomainRouterVO.java b/engine/schema/src/main/java/com/cloud/vm/DomainRouterVO.java index 2a7aa49b6ed..8bd973af531 100644 --- a/engine/schema/src/main/java/com/cloud/vm/DomainRouterVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/DomainRouterVO.java @@ -207,4 +207,8 @@ public class DomainRouterVO extends VMInstanceVO implements VirtualRouter { } + @Override + public String getName() { + return instanceName; + } } diff --git a/engine/schema/src/main/java/com/cloud/vm/SecondaryStorageVmVO.java b/engine/schema/src/main/java/com/cloud/vm/SecondaryStorageVmVO.java index 9ffc918f857..37a312ff78f 100644 --- a/engine/schema/src/main/java/com/cloud/vm/SecondaryStorageVmVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/SecondaryStorageVmVO.java @@ -133,4 +133,9 @@ public class SecondaryStorageVmVO extends VMInstanceVO implements SecondaryStora public void setRole(Role role) { this.role = role; } + + @Override + public String getName() { + return instanceName; + } } diff --git a/engine/schema/src/main/java/com/cloud/vm/UserVmVO.java b/engine/schema/src/main/java/com/cloud/vm/UserVmVO.java index f02380a0471..311a6c5b374 100644 --- a/engine/schema/src/main/java/com/cloud/vm/UserVmVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/UserVmVO.java @@ -125,4 +125,9 @@ public class UserVmVO extends VMInstanceVO implements UserVm { public boolean isUpdateParameters() { return updateParameters; } + + @Override + public String getName() { + return instanceName; + } } diff --git a/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java b/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java index afc7134990c..886933a5b46 100644 --- a/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java @@ -553,6 +553,11 @@ public class VMInstanceVO implements VirtualMachine, FiniteStateObject getEntityType() { return NicIpAlias.class; } + + @Override + public String getName() { + return null; + } } diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/NicSecondaryIpVO.java b/engine/schema/src/main/java/com/cloud/vm/dao/NicSecondaryIpVO.java index d60ac9298fc..093434052bc 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/NicSecondaryIpVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/NicSecondaryIpVO.java @@ -144,4 +144,9 @@ public class NicSecondaryIpVO implements NicSecondaryIp { public Class getEntityType() { return NicSecondaryIp.class; } + + @Override + public String getName() { + return null; + } } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/annotation/AnnotationVO.java b/engine/schema/src/main/java/org/apache/cloudstack/annotation/AnnotationVO.java index 982dd6db15b..0d34bc0156d 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/annotation/AnnotationVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/annotation/AnnotationVO.java @@ -53,6 +53,9 @@ public class AnnotationVO implements Annotation { @Column(name = "user_uuid") private String userUuid; + @Column(name = "admins_only") + private boolean adminsOnly; + @Column(name = GenericDao.CREATED_COLUMN) private Date created; @@ -64,19 +67,14 @@ public class AnnotationVO implements Annotation { this.uuid = UUID.randomUUID().toString(); } - public AnnotationVO(String text, AnnotationService.EntityType type, String uuid) { + public AnnotationVO(String text, AnnotationService.EntityType type, String uuid, boolean adminsOnly) { this(); setAnnotation(text); setEntityType(type); setEntityUuid(uuid); + setAdminsOnly(adminsOnly); } - public AnnotationVO(String text, String type, String uuid) { - this(); - setAnnotation(text); - setEntityType(type); - setEntityUuid(uuid); - } // access @Override @@ -151,4 +149,12 @@ public class AnnotationVO implements Annotation { public void setRemoved(Date removed) { this.removed = removed; } + + public boolean isAdminsOnly() { + return adminsOnly; + } + + public void setAdminsOnly(boolean adminsOnly) { + this.adminsOnly = adminsOnly; + } } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/annotation/dao/AnnotationDao.java b/engine/schema/src/main/java/org/apache/cloudstack/annotation/dao/AnnotationDao.java index 6bf8484560c..7370791716d 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/annotation/dao/AnnotationDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/annotation/dao/AnnotationDao.java @@ -16,6 +16,7 @@ // under the License. package org.apache.cloudstack.annotation.dao; +import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.annotation.AnnotationVO; import com.cloud.utils.db.GenericDao; @@ -25,6 +26,10 @@ import java.util.List; * @since 4.11 */ public interface AnnotationDao extends GenericDao { - public List findByEntityType(String entityType); - public List findByEntity(String entityType, String entityUuid); + List listByEntityType(String entityType, String userUuid, boolean isCallerAdmin, String annotationFilter, String callingUserUuid, String keyword); + List listByEntity(String entityType, String entityUuid, String userUuid, boolean isCallerAdmin, String annotationFilter, String callingUserUuid, String keyword); + List listAllAnnotations(String userUuid, RoleType roleType, String annotationFilter, String keyword); + boolean hasAnnotations(String entityUuid, String entityType, boolean isCallerAdmin); + boolean removeByEntityType(String entityType, String entityUuid); + AnnotationVO findOneByEntityId(String entityUuid); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/annotation/dao/AnnotationDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/annotation/dao/AnnotationDaoImpl.java index e2fcc905e0b..0bb47a5660c 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/annotation/dao/AnnotationDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/annotation/dao/AnnotationDaoImpl.java @@ -16,12 +16,19 @@ // under the License. package org.apache.cloudstack.annotation.dao; +import com.cloud.utils.db.Filter; import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.annotation.AnnotationService.EntityType; import org.apache.cloudstack.annotation.AnnotationVO; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang.StringUtils; import org.springframework.stereotype.Component; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; /** @@ -29,31 +36,112 @@ import java.util.List; */ @Component public class AnnotationDaoImpl extends GenericDaoBase implements AnnotationDao { - private final SearchBuilder AnnotationSearchByType; - private final SearchBuilder AnnotationSearchByTypeAndUuid; + private final SearchBuilder AnnotationSearchBuilder; public AnnotationDaoImpl() { super(); - AnnotationSearchByType = createSearchBuilder(); - AnnotationSearchByType.and("entityType", AnnotationSearchByType.entity().getEntityType(), SearchCriteria.Op.EQ); - AnnotationSearchByType.done(); - AnnotationSearchByTypeAndUuid = createSearchBuilder(); - AnnotationSearchByTypeAndUuid.and("entityType", AnnotationSearchByTypeAndUuid.entity().getEntityType(), SearchCriteria.Op.EQ); - AnnotationSearchByTypeAndUuid.and("entityUuid", AnnotationSearchByTypeAndUuid.entity().getEntityUuid(), SearchCriteria.Op.EQ); - AnnotationSearchByTypeAndUuid.done(); - + AnnotationSearchBuilder = createSearchBuilder(); + AnnotationSearchBuilder.and("entityType", AnnotationSearchBuilder.entity().getEntityType(), SearchCriteria.Op.EQ); + AnnotationSearchBuilder.and("entityUuid", AnnotationSearchBuilder.entity().getEntityUuid(), SearchCriteria.Op.EQ); + AnnotationSearchBuilder.and("userUuid", AnnotationSearchBuilder.entity().getUserUuid(), SearchCriteria.Op.EQ); + AnnotationSearchBuilder.and("adminsOnly", AnnotationSearchBuilder.entity().getUserUuid(), SearchCriteria.Op.EQ); + AnnotationSearchBuilder.and("annotation", AnnotationSearchBuilder.entity().getAnnotation(), SearchCriteria.Op.LIKE); + AnnotationSearchBuilder.and("entityTypeNotIn", AnnotationSearchBuilder.entity().getEntityType(), SearchCriteria.Op.NOTIN); + AnnotationSearchBuilder.done(); } - @Override public List findByEntityType(String entityType) { - SearchCriteria sc = createSearchCriteria(); + private List listAnnotationsOrderedByCreatedDate(SearchCriteria sc) { + Filter filter = new Filter(AnnotationVO.class, "created", false, null, null); + return listBy(sc, filter); + } + + @Override + public List listByEntityType(String entityType, String userUuid, boolean isCallerAdmin, + String annotationFilter, String callingUserUuid, String keyword) { + SearchCriteria sc = AnnotationSearchBuilder.create(); sc.addAnd("entityType", SearchCriteria.Op.EQ, entityType); - return listBy(sc); + if (StringUtils.isNotBlank(userUuid)) { + sc.addAnd("userUuid", SearchCriteria.Op.EQ, userUuid); + } + if (!isCallerAdmin) { + List adminOnlyTypes = Arrays.asList(EntityType.SERVICE_OFFERING, EntityType.DISK_OFFERING, + EntityType.NETWORK_OFFERING, EntityType.ZONE, EntityType.POD, EntityType.CLUSTER, EntityType.HOST, + EntityType.DOMAIN, EntityType.PRIMARY_STORAGE, EntityType.SECONDARY_STORAGE, + EntityType.VR, EntityType.SYSTEM_VM); + if (StringUtils.isBlank(entityType)) { + sc.setParameters("entityTypeNotIn", EntityType.SERVICE_OFFERING, EntityType.DISK_OFFERING, + EntityType.NETWORK_OFFERING, EntityType.ZONE, EntityType.POD, EntityType.CLUSTER, EntityType.HOST, + EntityType.DOMAIN, EntityType.PRIMARY_STORAGE, EntityType.SECONDARY_STORAGE, + EntityType.VR, EntityType.SYSTEM_VM); + } else if (adminOnlyTypes.contains(EntityType.valueOf(entityType))) { + return new ArrayList<>(); + } + sc.addAnd("adminsOnly", SearchCriteria.Op.EQ, false); + } + if (StringUtils.isNotBlank(keyword)) { + sc.setParameters("annotation", "%" + keyword + "%"); + } + return listAnnotationsOrderedByCreatedDate(sc); } - @Override public List findByEntity(String entityType, String entityUuid) { - SearchCriteria sc = createSearchCriteria(); + @Override + public List listByEntity(String entityType, String entityUuid, String userUuid, boolean isCallerAdmin, + String annotationFilter, String callingUserUuid, String keyword) { + SearchCriteria sc = AnnotationSearchBuilder.create(); sc.addAnd("entityType", SearchCriteria.Op.EQ, entityType); sc.addAnd("entityUuid", SearchCriteria.Op.EQ, entityUuid); - return listBy(sc, null); + if (StringUtils.isNotBlank(userUuid)) { + sc.addAnd("userUuid", SearchCriteria.Op.EQ, userUuid); + } + if (StringUtils.isNotBlank(callingUserUuid) && StringUtils.isNotBlank(annotationFilter) && + annotationFilter.equalsIgnoreCase("self")) { + sc.addAnd("userUuid", SearchCriteria.Op.EQ, callingUserUuid); + } + if (!isCallerAdmin) { + sc.addAnd("adminsOnly", SearchCriteria.Op.EQ, false); + } + if (StringUtils.isNotBlank(keyword)) { + sc.setParameters("annotation", "%" + keyword + "%"); + } + return listAnnotationsOrderedByCreatedDate(sc); + } + + @Override + public List listAllAnnotations(String userUuid, RoleType roleType, String annotationFilter, String keyword) { + SearchCriteria sc = AnnotationSearchBuilder.create(); + if (StringUtils.isNotBlank(keyword)) { + sc.setParameters("annotation", "%" + keyword + "%"); + } + if (StringUtils.isNotBlank(userUuid)) { + sc.addAnd("userUuid", SearchCriteria.Op.EQ, userUuid); + } + if (roleType != RoleType.Admin) { + sc.addAnd("adminsOnly", SearchCriteria.Op.EQ, false); + List notAllowedTypes = EntityType.getNotAllowedTypesForNonAdmins(roleType); + sc.setParameters("entityTypeNotIn", notAllowedTypes.toArray()); + } + return listAnnotationsOrderedByCreatedDate(sc); + } + + @Override + public boolean hasAnnotations(String entityUuid, String entityType, boolean isCallerAdmin) { + List annotations = listByEntity(entityType, entityUuid, null, + isCallerAdmin, "all", null, null); + return CollectionUtils.isNotEmpty(annotations); + } + + @Override + public boolean removeByEntityType(String entityType, String entityUuid) { + SearchCriteria sc = AnnotationSearchBuilder.create(); + sc.addAnd("entityType", SearchCriteria.Op.EQ, entityType); + sc.addAnd("entityUuid", SearchCriteria.Op.EQ, entityUuid); + return remove(sc) > 0; + } + + @Override + public AnnotationVO findOneByEntityId(String entityUuid) { + SearchCriteria sc = AnnotationSearchBuilder.create(); + sc.addAnd("entityUuid", SearchCriteria.Op.EQ, entityUuid); + return findOneBy(sc); } } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java index e56f55cbf59..dc47fcb6bb3 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java @@ -187,4 +187,9 @@ public class BackupVO implements Backup { public Class getEntityType() { return Backup.class; } + + @Override + public String getName() { + return null; + } } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMEntityVO.java b/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMEntityVO.java index 67af516c33b..286f05deb5a 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMEntityVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMEntityVO.java @@ -559,6 +559,11 @@ public class VMEntityVO implements VirtualMachine, FiniteStateObject[] getConfigKeys() { + return new ConfigKey[]{}; + } +} diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java index 7e52d98bcc4..01ac63ff203 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java @@ -39,6 +39,8 @@ import javax.naming.ConfigurationException; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.SecurityChecker; +import org.apache.cloudstack.annotation.AnnotationService; +import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiConstants.VMDetails; import org.apache.cloudstack.api.ResponseObject.ResponseView; @@ -233,6 +235,8 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne protected ResourceManager resourceManager; @Inject protected FirewallRulesDao firewallRulesDao; + @Inject + private AnnotationDao annotationDao; private void logMessage(final Level logLevel, final String message, final Exception e) { if (logLevel == Level.WARN) { @@ -648,6 +652,8 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne } } } + response.setHasAnnotation(annotationDao.hasAnnotations(kubernetesCluster.getUuid(), + AnnotationService.EntityType.KUBERNETES_CLUSTER.name(), accountService.isRootAdmin(caller.getId()))); response.setVirtualMachines(vmResponses); return response; } diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterDestroyWorker.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterDestroyWorker.java index 01ed9aaf541..47acf31f10f 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterDestroyWorker.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterDestroyWorker.java @@ -22,6 +22,8 @@ import java.util.List; import javax.inject.Inject; +import org.apache.cloudstack.annotation.AnnotationService; +import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.context.CallContext; import org.apache.commons.collections.CollectionUtils; import org.apache.log4j.Level; @@ -55,6 +57,8 @@ public class KubernetesClusterDestroyWorker extends KubernetesClusterResourceMod @Inject protected AccountManager accountManager; + @Inject + private AnnotationDao annotationDao; private List clusterVMs; @@ -262,6 +266,7 @@ public class KubernetesClusterDestroyWorker extends KubernetesClusterResourceMod throw new CloudRuntimeException(msg); } stateTransitTo(kubernetesCluster.getId(), KubernetesCluster.Event.OperationSucceeded); + annotationDao.removeByEntityType(AnnotationService.EntityType.KUBERNETES_CLUSTER.name(), kubernetesCluster.getUuid()); boolean deleted = kubernetesClusterDao.remove(kubernetesCluster.getId()); if (!deleted) { logMessage(Level.WARN, String.format("Failed to delete Kubernetes cluster : %s", kubernetesCluster.getName()), null); diff --git a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/response/KubernetesClusterResponse.java b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/response/KubernetesClusterResponse.java index cbfa6accff5..8324771c03a 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/response/KubernetesClusterResponse.java +++ b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/response/KubernetesClusterResponse.java @@ -19,7 +19,7 @@ package org.apache.cloudstack.api.response; import java.util.List; import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.BaseResponseWithAnnotations; import org.apache.cloudstack.api.EntityReference; import com.cloud.kubernetes.cluster.KubernetesCluster; @@ -28,7 +28,7 @@ import com.google.gson.annotations.SerializedName; @SuppressWarnings("unused") @EntityReference(value = {KubernetesCluster.class}) -public class KubernetesClusterResponse extends BaseResponse implements ControlledEntityResponse { +public class KubernetesClusterResponse extends BaseResponseWithAnnotations implements ControlledEntityResponse { @SerializedName(ApiConstants.ID) @Param(description = "the id of the Kubernetes cluster") private String id; diff --git a/plugins/integrations/kubernetes-service/src/main/resources/META-INF/cloudstack/kubernetes-service/module.properties b/plugins/integrations/kubernetes-service/src/main/resources/META-INF/cloudstack/kubernetes-service/module.properties index e6f02da6586..b149a41f00e 100644 --- a/plugins/integrations/kubernetes-service/src/main/resources/META-INF/cloudstack/kubernetes-service/module.properties +++ b/plugins/integrations/kubernetes-service/src/main/resources/META-INF/cloudstack/kubernetes-service/module.properties @@ -15,4 +15,4 @@ # specific language governing permissions and limitations # under the License. name=kubernetes-service -parent=compute +parent=kubernetes diff --git a/plugins/integrations/kubernetes-service/src/main/resources/META-INF/cloudstack/kubernetes-service/spring-kubernetes-service-context.xml b/plugins/integrations/kubernetes-service/src/main/resources/META-INF/cloudstack/kubernetes-service/spring-kubernetes-service-context.xml index 12f2a46a8ac..cf9faeeead5 100644 --- a/plugins/integrations/kubernetes-service/src/main/resources/META-INF/cloudstack/kubernetes-service/spring-kubernetes-service-context.xml +++ b/plugins/integrations/kubernetes-service/src/main/resources/META-INF/cloudstack/kubernetes-service/spring-kubernetes-service-context.xml @@ -34,4 +34,8 @@ + + + + diff --git a/plugins/metrics/src/main/java/org/apache/cloudstack/metrics/MetricsServiceImpl.java b/plugins/metrics/src/main/java/org/apache/cloudstack/metrics/MetricsServiceImpl.java index 1ce58c502dc..cb16501ed8d 100644 --- a/plugins/metrics/src/main/java/org/apache/cloudstack/metrics/MetricsServiceImpl.java +++ b/plugins/metrics/src/main/java/org/apache/cloudstack/metrics/MetricsServiceImpl.java @@ -171,6 +171,7 @@ public class MetricsServiceImpl extends ComponentLifecycleBase implements Metric throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to generate volume metrics response"); } + metricsResponse.setHasAnnotation(volumeResponse.hasAnnotation()); metricsResponse.setDiskSizeGB(volumeResponse.getSize()); metricsResponse.setDiskIopsTotal(volumeResponse.getDiskIORead(), volumeResponse.getDiskIOWrite()); Account account = CallContext.current().getCallingAccount(); @@ -194,6 +195,7 @@ public class MetricsServiceImpl extends ComponentLifecycleBase implements Metric throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to generate vm metrics response"); } + metricsResponse.setHasAnnotation(vmResponse.hasAnnotation()); metricsResponse.setIpAddress(vmResponse.getNics()); metricsResponse.setCpuTotal(vmResponse.getCpuNumber(), vmResponse.getCpuSpeed()); metricsResponse.setMemTotal(vmResponse.getMemory()); @@ -228,6 +230,7 @@ public class MetricsServiceImpl extends ComponentLifecycleBase implements Metric final Double storageThreshold = AlertManager.StorageCapacityThreshold.valueIn(poolClusterId); final Double storageDisableThreshold = CapacityManager.StorageCapacityDisableThreshold.valueIn(poolClusterId); + metricsResponse.setHasAnnotation(poolResponse.hasAnnotation()); metricsResponse.setDiskSizeUsedGB(poolResponse.getDiskSizeUsed()); metricsResponse.setDiskSizeTotalGB(poolResponse.getDiskSizeTotal(), poolResponse.getOverProvisionFactor()); metricsResponse.setDiskSizeAllocatedGB(poolResponse.getDiskSizeAllocated()); @@ -301,6 +304,7 @@ public class MetricsServiceImpl extends ComponentLifecycleBase implements Metric metricsResponse.setMemoryAllocatedThreshold(hostResponse.getMemoryAllocated(), hostResponse.getMemoryTotal(), memoryThreshold); metricsResponse.setMemoryAllocatedDisableThreshold(hostResponse.getMemoryAllocated(), hostResponse.getMemoryTotal(), memoryDisableThreshold); metricsResponses.add(metricsResponse); + metricsResponse.setHasAnnotation(hostResponse.hasAnnotation()); } return metricsResponses; } @@ -380,6 +384,7 @@ public class MetricsServiceImpl extends ComponentLifecycleBase implements Metric metricsResponse.setMemoryAllocatedThreshold(metrics.getMemoryAllocated(), metrics.getTotalMemory(), memoryThreshold); metricsResponse.setMemoryAllocatedDisableThreshold(metrics.getMemoryAllocated(), metrics.getTotalMemory(), memoryDisableThreshold); + metricsResponse.setHasAnnotation(clusterResponse.hasAnnotation()); metricsResponses.add(metricsResponse); } return metricsResponses; @@ -432,6 +437,7 @@ public class MetricsServiceImpl extends ComponentLifecycleBase implements Metric } } + metricsResponse.setHasAnnotation(zoneResponse.hasAnnotation()); metricsResponse.setState(zoneResponse.getAllocationState()); metricsResponse.setResource(metrics.getUpResources(), metrics.getTotalResources()); // CPU diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index 5dc6412ba6f..09057e64277 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -37,6 +37,8 @@ import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.affinity.AffinityGroup; import org.apache.cloudstack.affinity.AffinityGroupResponse; +import org.apache.cloudstack.annotation.AnnotationService; +import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.api.ApiConstants.DomainDetails; import org.apache.cloudstack.api.ApiConstants.HostDetails; import org.apache.cloudstack.api.ApiConstants.VMDetails; @@ -405,6 +407,8 @@ public class ApiResponseHelper implements ResponseGenerator { @Inject private GuestOSDao _guestOsDao; @Inject + private AnnotationDao annotationDao; + @Inject private UserStatisticsDao userStatsDao; @Override @@ -592,6 +596,8 @@ public class ApiResponseHelper implements ResponseGenerator { CollectionUtils.addIgnoreNull(tagResponses, tagResponse); } snapshotResponse.setTags(new HashSet<>(tagResponses)); + snapshotResponse.setHasAnnotation(annotationDao.hasAnnotations(snapshot.getUuid(), AnnotationService.EntityType.SNAPSHOT.name(), + _accountMgr.isRootAdmin(CallContext.current().getCallingAccount().getId()))); snapshotResponse.setObjectName("snapshot"); return snapshotResponse; @@ -674,6 +680,8 @@ public class ApiResponseHelper implements ResponseGenerator { CollectionUtils.addIgnoreNull(tagResponses, tagResponse); } vmSnapshotResponse.setTags(new HashSet<>(tagResponses)); + vmSnapshotResponse.setHasAnnotation(annotationDao.hasAnnotations(vmSnapshot.getUuid(), AnnotationService.EntityType.VM_SNAPSHOT.name(), + _accountMgr.isRootAdmin(CallContext.current().getCallingAccount().getId()))); vmSnapshotResponse.setCurrent(vmSnapshot.getCurrent()); vmSnapshotResponse.setType(vmSnapshot.getType().toString()); @@ -958,6 +966,8 @@ public class ApiResponseHelper implements ResponseGenerator { CollectionUtils.addIgnoreNull(tagResponses, tagResponse); } ipResponse.setTags(tagResponses); + ipResponse.setHasAnnotation(annotationDao.hasAnnotations(ipAddr.getUuid(), AnnotationService.EntityType.PUBLIC_IP_ADDRESS.name(), + _accountMgr.isRootAdmin(CallContext.current().getCallingAccount().getId()))); ipResponse.setObjectName("ipaddress"); return ipResponse; @@ -1134,6 +1144,8 @@ public class ApiResponseHelper implements ResponseGenerator { capacityResponses.addAll(getStatsCapacityresponse(null, null, pod.getId(), pod.getDataCenterId())); podResponse.setCapacitites(new ArrayList(capacityResponses)); } + podResponse.setHasAnnotation(annotationDao.hasAnnotations(pod.getUuid(), AnnotationService.EntityType.POD.name(), + _accountMgr.isRootAdmin(CallContext.current().getCallingAccount().getId()))); podResponse.setObjectName("pod"); return podResponse; } @@ -1290,6 +1302,8 @@ public class ApiResponseHelper implements ResponseGenerator { capacityResponses.addAll(getStatsCapacityresponse(null, cluster.getId(), pod.getId(), pod.getDataCenterId())); clusterResponse.setCapacitites(new ArrayList(capacityResponses)); } + clusterResponse.setHasAnnotation(annotationDao.hasAnnotations(cluster.getUuid(), AnnotationService.EntityType.CLUSTER.name(), + _accountMgr.isRootAdmin(CallContext.current().getCallingAccount().getId()))); clusterResponse.setObjectName("cluster"); return clusterResponse; } @@ -1493,6 +1507,8 @@ public class ApiResponseHelper implements ResponseGenerator { vmResponse.setDns2(zone.getDns2()); } + vmResponse.setHasAnnotation(annotationDao.hasAnnotations(vm.getUuid(), AnnotationService.EntityType.SYSTEM_VM.name(), + _accountMgr.isRootAdmin(CallContext.current().getCallingAccount().getId()))); List nicProfiles = ApiDBUtils.getNics(vm); for (NicProfile singleNicProfile : nicProfiles) { Network network = ApiDBUtils.findNetworkById(singleNicProfile.getNetworkId()); @@ -2125,6 +2141,8 @@ public class ApiResponseHelper implements ResponseGenerator { if (details != null && !details.isEmpty()) { response.setDetails(details); } + response.setHasAnnotation(annotationDao.hasAnnotations(offering.getUuid(), AnnotationService.EntityType.NETWORK_OFFERING.name(), + _accountMgr.isRootAdmin(CallContext.current().getCallingAccount().getId()))); return response; } @@ -2336,6 +2354,8 @@ public class ApiResponseHelper implements ResponseGenerator { CollectionUtils.addIgnoreNull(tagResponses, tagResponse); } response.setTags(tagResponses); + response.setHasAnnotation(annotationDao.hasAnnotations(network.getUuid(), AnnotationService.EntityType.NETWORK.name(), + _accountMgr.isRootAdmin(CallContext.current().getCallingAccount().getId()))); if (network.getNetworkACLId() != null) { NetworkACL acl = ApiDBUtils.findByNetworkACLId(network.getNetworkACLId()); @@ -3047,6 +3067,8 @@ public class ApiResponseHelper implements ResponseGenerator { CollectionUtils.addIgnoreNull(tagResponses, tagResponse); } response.setTags(tagResponses); + response.setHasAnnotation(annotationDao.hasAnnotations(vpc.getUuid(), AnnotationService.EntityType.VPC.name(), + _accountMgr.isRootAdmin(CallContext.current().getCallingAccount().getId()))); response.setObjectName("vpc"); return response; } @@ -3282,6 +3304,8 @@ public class ApiResponseHelper implements ResponseGenerator { response.setIkeVersion(result.getIkeVersion()); response.setSplitConnections(result.getSplitConnections()); response.setObjectName("vpncustomergateway"); + response.setHasAnnotation(annotationDao.hasAnnotations(result.getUuid(), AnnotationService.EntityType.VPN_CUSTOMER_GATEWAY.name(), + _accountMgr.isRootAdmin(CallContext.current().getCallingAccount().getId()))); populateAccount(response, result.getAccountId()); populateDomain(response, result.getDomainId()); @@ -4352,15 +4376,18 @@ public class ApiResponseHelper implements ResponseGenerator { @Override public SSHKeyPairResponse createSSHKeyPairResponse(SSHKeyPair sshkeyPair, boolean privatekey) { - SSHKeyPairResponse response = new SSHKeyPairResponse(sshkeyPair.getName(), sshkeyPair.getFingerprint()); + SSHKeyPairResponse response = new SSHKeyPairResponse(sshkeyPair.getUuid(), sshkeyPair.getName(), sshkeyPair.getFingerprint()); if (privatekey) { - response = new CreateSSHKeyPairResponse(sshkeyPair.getName(), sshkeyPair.getFingerprint(), sshkeyPair.getPrivateKey()); + response = new CreateSSHKeyPairResponse(sshkeyPair.getUuid(), sshkeyPair.getName(), + sshkeyPair.getFingerprint(), sshkeyPair.getPrivateKey()); } Account account = ApiDBUtils.findAccountById(sshkeyPair.getAccountId()); response.setAccountName(account.getAccountName()); Domain domain = ApiDBUtils.findDomainById(sshkeyPair.getDomainId()); response.setDomainId(domain.getUuid()); response.setDomainName(domain.getName()); + response.setHasAnnotation(annotationDao.hasAnnotations(sshkeyPair.getUuid(), AnnotationService.EntityType.SSH_KEYPAIR.name(), + _accountMgr.isRootAdmin(CallContext.current().getCallingAccount().getId()))); return response; } 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 cfe2e31a371..c777e65f171 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,9 @@ import java.util.List; import javax.inject.Inject; +import org.apache.cloudstack.annotation.AnnotationService; +import org.apache.cloudstack.annotation.dao.AnnotationDao; +import org.apache.cloudstack.context.CallContext; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; @@ -45,6 +48,8 @@ public class DataCenterJoinDaoImpl extends GenericDaoBase dofIdSearch; @Inject public AccountManager _accountMgr; + @Inject + private AnnotationDao annotationDao; protected DataCenterJoinDaoImpl() { @@ -103,6 +108,8 @@ public class DataCenterJoinDaoImpl extends GenericDaoBase dofIdSearch; private final Attribute _typeAttr; @@ -100,6 +108,9 @@ public class DiskOfferingJoinDaoImpl extends GenericDaoBase implements DomainJoinDao { public static final Logger s_logger = Logger.getLogger(DomainJoinDaoImpl.class); private SearchBuilder domainIdSearch; + @Inject + private AnnotationDao annotationDao; + @Inject + private AccountManager accountManager; + protected DomainJoinDaoImpl() { domainIdSearch = createSearchBuilder(); @@ -74,6 +85,9 @@ public class DomainJoinDaoImpl extends GenericDaoBase implem domainResponse.setCreated(domain.getCreated()); domainResponse.setNetworkDomain(domain.getNetworkDomain()); + domainResponse.setHasAnnotation(annotationDao.hasAnnotations(domain.getUuid(), AnnotationService.EntityType.DOMAIN.name(), + accountManager.isRootAdmin(CallContext.current().getCallingAccount().getId()))); + if (details.contains(DomainDetails.all) || details.contains(DomainDetails.resource)) { boolean fullView = (view == ResponseView.Full && domain.getId() == Domain.ROOT_DOMAIN); setResourceLimits(domain, fullView, domainResponse); diff --git a/server/src/main/java/com/cloud/api/query/dao/DomainRouterJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/DomainRouterJoinDaoImpl.java index 413ff2a5614..96c129ad068 100644 --- a/server/src/main/java/com/cloud/api/query/dao/DomainRouterJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/DomainRouterJoinDaoImpl.java @@ -21,6 +21,9 @@ import java.util.List; import javax.inject.Inject; +import org.apache.cloudstack.annotation.AnnotationService; +import org.apache.cloudstack.annotation.dao.AnnotationDao; +import org.apache.cloudstack.context.CallContext; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; @@ -52,6 +55,8 @@ public class DomainRouterJoinDaoImpl extends GenericDaoBase vrSearch; @@ -97,6 +102,9 @@ public class DomainRouterJoinDaoImpl extends GenericDaoBase implements private OutOfBandManagementDao outOfBandManagementDao; @Inject private ManagementServerHostDao managementServerHostDao; + @Inject + private AnnotationDao annotationDao; + @Inject + private AccountManager accountManager; private final SearchBuilder hostSearch; @@ -266,6 +274,8 @@ public class HostJoinDaoImpl extends GenericDaoBase implements hostResponse.setJobId(host.getJobUuid()); hostResponse.setJobStatus(host.getJobStatus()); } + hostResponse.setHasAnnotation(annotationDao.hasAnnotations(host.getUuid(), AnnotationService.EntityType.HOST.name(), + accountManager.isRootAdmin(CallContext.current().getCallingAccount().getId()))); hostResponse.setAnnotation(host.getAnnotation()); hostResponse.setLastAnnotated(host.getLastAnnotated ()); hostResponse.setUsername(host.getUsername()); diff --git a/server/src/main/java/com/cloud/api/query/dao/ImageStoreJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/ImageStoreJoinDaoImpl.java index b91398de9b4..9c20d18678b 100644 --- a/server/src/main/java/com/cloud/api/query/dao/ImageStoreJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/ImageStoreJoinDaoImpl.java @@ -23,6 +23,10 @@ import javax.inject.Inject; import com.cloud.api.ApiDBUtils; import com.cloud.storage.StorageStats; +import com.cloud.user.AccountManager; +import org.apache.cloudstack.annotation.AnnotationService; +import org.apache.cloudstack.annotation.dao.AnnotationDao; +import org.apache.cloudstack.context.CallContext; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; @@ -42,6 +46,10 @@ public class ImageStoreJoinDaoImpl extends GenericDaoBase dsSearch; @@ -83,6 +91,8 @@ public class ImageStoreJoinDaoImpl extends GenericDaoBase implements InstanceGroupJoinDao { public static final Logger s_logger = Logger.getLogger(InstanceGroupJoinDaoImpl.class); private SearchBuilder vrIdSearch; + @Inject + private AnnotationDao annotationDao; + @Inject + private AccountManager accountManager; + protected InstanceGroupJoinDaoImpl() { vrIdSearch = createSearchBuilder(); @@ -52,6 +63,8 @@ public class InstanceGroupJoinDaoImpl extends GenericDaoBase sofIdSearch; @@ -134,6 +142,9 @@ public class ServiceOfferingJoinDaoImpl extends GenericDaoBase tmpltIdPairSearch; @@ -262,6 +267,9 @@ public class TemplateJoinDaoImpl extends GenericDaoBaseWithTagInformation VmDetailSearch; @@ -312,6 +317,9 @@ public class UserVmJoinDaoImpl extends GenericDaoBaseWithTagInformation volSearch; @@ -236,6 +241,9 @@ public class VolumeJoinDaoImpl extends GenericDaoBaseWithTagInformation 0) { addTagInformation(vol, volData); } + if (volData.hasAnnotation() == null) { + volData.setHasAnnotation(annotationDao.hasAnnotations(vol.getUuid(), AnnotationService.EntityType.VOLUME.name(), + _accountMgr.isRootAdmin(CallContext.current().getCallingAccount().getId()))); + } return volData; } diff --git a/server/src/main/java/com/cloud/api/query/vo/AsyncJobJoinVO.java b/server/src/main/java/com/cloud/api/query/vo/AsyncJobJoinVO.java index 82887d23aef..d88cbe0af01 100644 --- a/server/src/main/java/com/cloud/api/query/vo/AsyncJobJoinVO.java +++ b/server/src/main/java/com/cloud/api/query/vo/AsyncJobJoinVO.java @@ -207,6 +207,11 @@ public class AsyncJobJoinVO extends BaseViewVO implements ControlledViewEntity { return AsyncJob.class; } + @Override + public String getName() { + return null; + } + @Override public String getProjectUuid() { // TODO Auto-generated method stub diff --git a/server/src/main/java/com/cloud/api/query/vo/EventJoinVO.java b/server/src/main/java/com/cloud/api/query/vo/EventJoinVO.java index 8fba938876d..3584038fab0 100644 --- a/server/src/main/java/com/cloud/api/query/vo/EventJoinVO.java +++ b/server/src/main/java/com/cloud/api/query/vo/EventJoinVO.java @@ -229,4 +229,9 @@ public class EventJoinVO extends BaseViewVO implements ControlledViewEntity { public Class getEntityType() { return Event.class; } + + @Override + public String getName() { + return null; + } } diff --git a/server/src/main/java/com/cloud/api/query/vo/ProjectInvitationJoinVO.java b/server/src/main/java/com/cloud/api/query/vo/ProjectInvitationJoinVO.java index 3c2d21cfdd2..bf28fddea31 100644 --- a/server/src/main/java/com/cloud/api/query/vo/ProjectInvitationJoinVO.java +++ b/server/src/main/java/com/cloud/api/query/vo/ProjectInvitationJoinVO.java @@ -171,4 +171,9 @@ public class ProjectInvitationJoinVO extends BaseViewVO implements ControlledVie public Class getEntityType() { return ProjectInvitation.class; } + + @Override + public String getName() { + return null; + } } diff --git a/server/src/main/java/com/cloud/api/query/vo/ResourceTagJoinVO.java b/server/src/main/java/com/cloud/api/query/vo/ResourceTagJoinVO.java index 6758552ed33..3adec294850 100644 --- a/server/src/main/java/com/cloud/api/query/vo/ResourceTagJoinVO.java +++ b/server/src/main/java/com/cloud/api/query/vo/ResourceTagJoinVO.java @@ -185,6 +185,11 @@ public class ResourceTagJoinVO extends BaseViewVO implements ControlledViewEntit return ResourceTag.class; } + @Override + public String getName() { + return null; + } + public void setId(long id) { this.id = id; } diff --git a/server/src/main/java/com/cloud/api/query/vo/UserAccountJoinVO.java b/server/src/main/java/com/cloud/api/query/vo/UserAccountJoinVO.java index 88f5efa343f..1b2971cd913 100644 --- a/server/src/main/java/com/cloud/api/query/vo/UserAccountJoinVO.java +++ b/server/src/main/java/com/cloud/api/query/vo/UserAccountJoinVO.java @@ -254,6 +254,11 @@ public class UserAccountJoinVO extends BaseViewVO implements InternalIdentity, I return UserAccount.class; } + @Override + public String getName() { + return accountName; + } + @Override public String getProjectUuid() { return null; diff --git a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java index 7c18c600b04..4ccdf7e2d57 100755 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@ -45,6 +45,8 @@ import org.apache.cloudstack.affinity.AffinityGroupService; import org.apache.cloudstack.affinity.dao.AffinityGroupDao; import org.apache.cloudstack.agent.lb.IndirectAgentLB; import org.apache.cloudstack.agent.lb.IndirectAgentLBServiceImpl; +import org.apache.cloudstack.annotation.AnnotationService; +import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.command.admin.config.UpdateCfgCmd; import org.apache.cloudstack.api.command.admin.network.CreateManagementNetworkIpRangeCmd; @@ -399,6 +401,8 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati HostTagsDao hostTagDao; @Inject StoragePoolTagsDao storagePoolTagDao; + @Inject + private AnnotationDao annotationDao; // FIXME - why don't we have interface for DataCenterLinkLocalIpAddressDao? @@ -1234,6 +1238,9 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati if (dr != null) { _dedicatedDao.remove(dr.getId()); } + + // Remove comments (if any) + annotationDao.removeByEntityType(AnnotationService.EntityType.POD.name(), pod.getUuid()); } }); @@ -1888,6 +1895,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati public boolean deleteZone(final DeleteZoneCmd cmd) { final Long zoneId = cmd.getId(); + DataCenterVO zone = _zoneDao.findById(zoneId); // Make sure the zone exists if (!validZone(zoneId)) { @@ -1924,6 +1932,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati _affinityGroupService.deleteAffinityGroup(dr.getAffinityGroupId(), null, null, null, null); } } + annotationDao.removeByEntityType(AnnotationService.EntityType.ZONE.name(), zone.getUuid()); } return success; @@ -3451,6 +3460,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati throw new InvalidParameterValueException(String.format("Unable to delete disk offering: %s by user: %s because it is not root-admin or domain-admin", offering.getUuid(), user.getUuid())); } + annotationDao.removeByEntityType(AnnotationService.EntityType.DISK_OFFERING.name(), offering.getUuid()); offering.setState(DiskOffering.State.Inactive); if (_diskOfferingDao.update(offering.getId(), offering)) { CallContext.current().setEventDetails("Disk offering id=" + diskOfferingId); @@ -3518,6 +3528,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati throw new InvalidParameterValueException(String.format("Unable to delete service offering: %s by user: %s because it is not root-admin or domain-admin", offering.getUuid(), user.getUuid())); } + annotationDao.removeByEntityType(AnnotationService.EntityType.SERVICE_OFFERING.name(), offering.getUuid()); offering.setState(DiskOffering.State.Inactive); if (_serviceOfferingDao.update(offeringId, offering)) { CallContext.current().setEventDetails("Service offering id=" + offeringId); @@ -5866,6 +5877,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati + "To make the network offering unavaiable, disable it"); } + annotationDao.removeByEntityType(AnnotationService.EntityType.NETWORK_OFFERING.name(), offering.getUuid()); if (_networkOfferingDao.remove(offeringId)) { return true; } else { diff --git a/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java b/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java index ebdf6356050..97ef0503b69 100644 --- a/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java +++ b/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java @@ -32,6 +32,8 @@ import javax.inject.Inject; import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.acl.SecurityChecker.AccessType; +import org.apache.cloudstack.annotation.AnnotationService; +import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.response.AcquirePodIpCmdResponse; import org.apache.cloudstack.context.CallContext; @@ -293,6 +295,8 @@ public class IpAddressManagerImpl extends ManagerBase implements IpAddressManage DataCenterIpAddressDao _privateIPAddressDao; @Inject HostPodDao _hpDao; + @Inject + private AnnotationDao annotationDao; SearchBuilder AssignIpAddressSearch; SearchBuilder AssignIpAddressFromPodVlanSearch; @@ -714,6 +718,8 @@ public class IpAddressManagerImpl extends ManagerBase implements IpAddressManage } } + annotationDao.removeByEntityType(AnnotationService.EntityType.PUBLIC_IP_ADDRESS.name(), ip.getUuid()); + if (success) { if (ip.isPortable()) { releasePortableIpAddress(addrId); diff --git a/server/src/main/java/com/cloud/network/vpc/PrivateGatewayProfile.java b/server/src/main/java/com/cloud/network/vpc/PrivateGatewayProfile.java index 3dc984e444c..c9840410d24 100644 --- a/server/src/main/java/com/cloud/network/vpc/PrivateGatewayProfile.java +++ b/server/src/main/java/com/cloud/network/vpc/PrivateGatewayProfile.java @@ -115,4 +115,9 @@ public class PrivateGatewayProfile implements PrivateGateway { public Class getEntityType() { return VpcGateway.class; } + + @Override + public String getName() { + return null; + } } diff --git a/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java b/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java index 080b1f1673b..e72922d514e 100644 --- a/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java +++ b/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java @@ -40,6 +40,8 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; import org.apache.cloudstack.acl.ControlledEntity.ACLType; +import org.apache.cloudstack.annotation.AnnotationService; +import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.command.admin.vpc.CreateVPCOfferingCmd; import org.apache.cloudstack.api.command.admin.vpc.UpdateVPCOfferingCmd; @@ -222,6 +224,8 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis DomainRouterDao _routerDao; @Inject DomainDao domainDao; + @Inject + private AnnotationDao annotationDao; @Inject private VpcPrivateGatewayTransactionCallable vpcTxCallable; @@ -1695,6 +1699,9 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis _networkAclMgr.deleteNetworkACL(networkAcl); } } + + VpcVO vpc = _vpcDao.findById(vpcId); + annotationDao.removeByEntityType(AnnotationService.EntityType.VPC.name(), vpc.getUuid()); return success; } diff --git a/server/src/main/java/com/cloud/network/vpn/Site2SiteVpnManagerImpl.java b/server/src/main/java/com/cloud/network/vpn/Site2SiteVpnManagerImpl.java index 5b444c292a6..461eb49eee4 100644 --- a/server/src/main/java/com/cloud/network/vpn/Site2SiteVpnManagerImpl.java +++ b/server/src/main/java/com/cloud/network/vpn/Site2SiteVpnManagerImpl.java @@ -23,6 +23,8 @@ import java.util.Map; import javax.inject.Inject; import javax.naming.ConfigurationException; +import org.apache.cloudstack.annotation.AnnotationService; +import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; @@ -103,6 +105,8 @@ public class Site2SiteVpnManagerImpl extends ManagerBase implements Site2SiteVpn VpcManager _vpcMgr; @Inject AccountManager _accountMgr; + @Inject + private AnnotationDao annotationDao; String _name; int _connLimit; @@ -387,6 +391,7 @@ public class Site2SiteVpnManagerImpl extends ManagerBase implements Site2SiteVpn if (vpnConnections != null && vpnConnections.size() != 0) { throw new InvalidParameterValueException("Unable to delete VPN customer gateway with id " + id + " because there is still related VPN connections!"); } + annotationDao.removeByEntityType(AnnotationService.EntityType.VPN_CUSTOMER_GATEWAY.name(), gw.getUuid()); _customerGatewayDao.remove(id); return true; } diff --git a/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java b/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java index 2b4e2334871..82f0c8141ac 100755 --- a/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java +++ b/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java @@ -47,6 +47,8 @@ import com.cloud.storage.dao.DiskOfferingDao; import com.cloud.vm.UserVmManager; import com.cloud.vm.VirtualMachineProfile; import com.cloud.vm.VirtualMachineProfileImpl; +import org.apache.cloudstack.annotation.AnnotationService; +import org.apache.cloudstack.annotation.dao.AnnotationDao; import com.google.common.base.Strings; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.command.admin.cluster.AddClusterCmd; @@ -290,6 +292,8 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager, private ClusterVSMMapDao _clusterVSMMapDao; @Inject private UserVmDetailsDao userVmDetailsDao; + @Inject + private AnnotationDao annotationDao; private final long _nodeId = ManagementServerNode.getManagementServerId(); @@ -976,6 +980,9 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager, if (dr != null) { _dedicatedDao.remove(dr.getId()); } + + // Remove comments (if any) + annotationDao.removeByEntityType(AnnotationService.EntityType.HOST.name(), host.getUuid()); } }); @@ -1056,6 +1063,8 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager, if (dr != null) { _dedicatedDao.remove(dr.getId()); } + // Remove comments (if any) + annotationDao.removeByEntityType(AnnotationService.EntityType.CLUSTER.name(), cluster.getUuid()); } } diff --git a/server/src/main/java/com/cloud/server/ManagementServerImpl.java b/server/src/main/java/com/cloud/server/ManagementServerImpl.java index 5a5751cf6f4..10788b27a44 100644 --- a/server/src/main/java/com/cloud/server/ManagementServerImpl.java +++ b/server/src/main/java/com/cloud/server/ManagementServerImpl.java @@ -47,6 +47,8 @@ import javax.naming.ConfigurationException; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.affinity.AffinityGroupProcessor; import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; +import org.apache.cloudstack.annotation.AnnotationService; +import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.command.admin.account.CreateAccountCmd; import org.apache.cloudstack.api.command.admin.account.DeleteAccountCmd; @@ -881,6 +883,8 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe private NetworkModel _networkMgr; @Inject private VpcDao _vpcDao; + @Inject + private AnnotationDao annotationDao; private LockControllerListener _lockControllerListener; private final ScheduledExecutorService _eventExecutor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("EventChecker")); @@ -4097,6 +4101,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe ex.addProxyObject(domainUuid, "domainId"); throw ex; } + annotationDao.removeByEntityType(AnnotationService.EntityType.SSH_KEYPAIR.name(), s.getUuid()); return _sshKeyPairDao.deleteByName(owner.getAccountId(), owner.getDomainId(), cmd.getName()); } diff --git a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java index 01932169a38..55074752747 100644 --- a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java +++ b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java @@ -45,6 +45,8 @@ import javax.inject.Inject; import com.cloud.agent.api.GetStoragePoolCapabilitiesAnswer; import com.cloud.agent.api.GetStoragePoolCapabilitiesCommand; +import org.apache.cloudstack.annotation.AnnotationService; +import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.command.admin.storage.CancelPrimaryStorageMaintenanceCmd; import org.apache.cloudstack.api.command.admin.storage.CreateSecondaryStagingStoreCmd; @@ -330,6 +332,8 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C ServiceOfferingDetailsDao _serviceOfferingDetailsDao; @Inject VsphereStoragePolicyDao _vsphereStoragePolicyDao; + @Inject + private AnnotationDao annotationDao; protected List _discoverers; @@ -2925,6 +2929,7 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C _snapshotStoreDao.deletePrimaryRecordsForStore(storeId, DataStoreRole.Image); _volumeStoreDao.deletePrimaryRecordsForStore(storeId); _templateStoreDao.deletePrimaryRecordsForStore(storeId); + annotationDao.removeByEntityType(AnnotationService.EntityType.SECONDARY_STORAGE.name(), store.getUuid()); _imageStoreDao.remove(storeId); } }); diff --git a/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java b/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java index 06da5d1f002..47ff898b0b4 100755 --- a/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java +++ b/server/src/main/java/com/cloud/storage/snapshot/SnapshotManagerImpl.java @@ -29,6 +29,8 @@ import java.util.concurrent.TimeUnit; import javax.inject.Inject; import javax.naming.ConfigurationException; +import org.apache.cloudstack.annotation.AnnotationService; +import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.api.command.user.snapshot.CreateSnapshotPolicyCmd; import org.apache.cloudstack.api.command.user.snapshot.DeleteSnapshotPoliciesCmd; import org.apache.cloudstack.api.command.user.snapshot.ListSnapshotPoliciesCmd; @@ -200,6 +202,8 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement StorageStrategyFactory _storageStrategyFactory; @Inject public TaggedResourceService taggedResourceService; + @Inject + private AnnotationDao annotationDao; private int _totalRetries; private int _pauseInterval; @@ -587,6 +591,8 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement boolean result = snapshotStrategy.deleteSnapshot(snapshotId); if (result) { + annotationDao.removeByEntityType(AnnotationService.EntityType.SNAPSHOT.name(), snapshotCheck.getUuid()); + if (snapshotCheck.getState() == Snapshot.State.BackedUp) { UsageEventUtils.publishUsageEvent(EventTypes.EVENT_SNAPSHOT_DELETE, snapshotCheck.getAccountId(), snapshotCheck.getDataCenterId(), snapshotId, snapshotCheck.getName(), null, null, 0L, snapshotCheck.getClass().getName(), snapshotCheck.getUuid()); diff --git a/server/src/main/java/com/cloud/template/HypervisorTemplateAdapter.java b/server/src/main/java/com/cloud/template/HypervisorTemplateAdapter.java index aed1d87b306..00dfee244cc 100644 --- a/server/src/main/java/com/cloud/template/HypervisorTemplateAdapter.java +++ b/server/src/main/java/com/cloud/template/HypervisorTemplateAdapter.java @@ -30,6 +30,8 @@ import javax.inject.Inject; import org.apache.cloudstack.agent.directdownload.CheckUrlAnswer; import org.apache.cloudstack.agent.directdownload.CheckUrlCommand; +import org.apache.cloudstack.annotation.AnnotationService; +import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.api.command.user.iso.DeleteIsoCmd; import org.apache.cloudstack.api.command.user.iso.GetUploadParamsForIsoCmd; import org.apache.cloudstack.api.command.user.iso.RegisterIsoCmd; @@ -136,6 +138,8 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase { private VMTemplateDetailsDao templateDetailsDao; @Inject private TemplateDeployAsIsDetailsDao templateDeployAsIsDetailsDao; + @Inject + private AnnotationDao annotationDao; @Override public String getName() { @@ -651,6 +655,11 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase { // Remove deploy-as-is details (if any) templateDeployAsIsDetailsDao.removeDetails(template.getId()); + // Remove comments (if any) + AnnotationService.EntityType entityType = template.getFormat().equals(ImageFormat.ISO) ? + AnnotationService.EntityType.ISO : AnnotationService.EntityType.TEMPLATE; + annotationDao.removeByEntityType(entityType.name(), template.getUuid()); + } return success; } diff --git a/server/src/main/java/com/cloud/user/DomainManagerImpl.java b/server/src/main/java/com/cloud/user/DomainManagerImpl.java index 918223e46f7..f6569a0c469 100644 --- a/server/src/main/java/com/cloud/user/DomainManagerImpl.java +++ b/server/src/main/java/com/cloud/user/DomainManagerImpl.java @@ -25,6 +25,8 @@ import java.util.UUID; import javax.inject.Inject; import com.cloud.domain.dao.DomainDetailsDao; +import org.apache.cloudstack.annotation.AnnotationService; +import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.command.admin.domain.ListDomainChildrenCmd; import org.apache.cloudstack.api.command.admin.domain.ListDomainsCmd; @@ -127,6 +129,8 @@ public class DomainManagerImpl extends ManagerBase implements DomainManager, Dom private ConfigurationManager _configMgr; @Inject private DomainDetailsDao _domainDetailsDao; + @Inject + private AnnotationDao annotationDao; @Inject MessageBus _messageBus; @@ -338,6 +342,7 @@ public class DomainManagerImpl extends ManagerBase implements DomainManager, Dom cleanupDomainDetails(domain.getId()); cleanupDomainOfferings(domain.getId()); + annotationDao.removeByEntityType(AnnotationService.EntityType.DOMAIN.name(), domain.getUuid()); CallContext.current().putContextParameter(Domain.class, domain.getUuid()); return true; } catch (Exception ex) { diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 2afc0505bcb..d3a67a08865 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -58,6 +58,8 @@ import org.apache.cloudstack.affinity.AffinityGroupVMMapVO; import org.apache.cloudstack.affinity.AffinityGroupVO; import org.apache.cloudstack.affinity.dao.AffinityGroupDao; import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; +import org.apache.cloudstack.annotation.AnnotationService; +import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseCmd.HTTPMethod; import org.apache.cloudstack.api.command.admin.vm.AssignVMCmd; @@ -532,6 +534,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir private BackupDao backupDao; @Inject private BackupManager backupManager; + @Inject + private AnnotationDao annotationDao; private ScheduledExecutorService _executor = null; private ScheduledExecutorService _vmIpFetchExecutor = null; @@ -3154,6 +3158,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir @Override public boolean deleteVmGroup(long groupId) { + InstanceGroupVO group = _vmGroupDao.findById(groupId); + annotationDao.removeByEntityType(AnnotationService.EntityType.INSTANCE_GROUP.name(), group.getUuid()); // delete all the mappings from group_vm_map table List groupVmMaps = _groupVMMapDao.listByGroupId(groupId); for (InstanceGroupVMMapVO groupMap : groupVmMaps) { diff --git a/server/src/main/java/com/cloud/vm/snapshot/VMSnapshotManagerImpl.java b/server/src/main/java/com/cloud/vm/snapshot/VMSnapshotManagerImpl.java index 4a7840eb784..bd66fe89ec6 100644 --- a/server/src/main/java/com/cloud/vm/snapshot/VMSnapshotManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/snapshot/VMSnapshotManagerImpl.java @@ -27,6 +27,8 @@ import java.util.Map; import javax.inject.Inject; import javax.naming.ConfigurationException; +import org.apache.cloudstack.annotation.AnnotationService; +import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.api.ApiConstants; import org.apache.commons.collections.MapUtils; import org.apache.log4j.Logger; @@ -171,6 +173,8 @@ public class VMSnapshotManagerImpl extends MutualExclusiveIdsManagerBase impleme protected VMSnapshotDetailsDao _vmSnapshotDetailsDao; @Inject PrimaryDataStoreDao _storagePoolDao; + @Inject + private AnnotationDao annotationDao; VmWorkJobHandlerProxy _jobHandlerProxy = new VmWorkJobHandlerProxy(this); @@ -703,6 +707,7 @@ public class VMSnapshotManagerImpl extends MutualExclusiveIdsManagerBase impleme throw new InvalidParameterValueException("There is other active vm snapshot tasks on the instance, please try again later"); } + annotationDao.removeByEntityType(AnnotationService.EntityType.VM_SNAPSHOT.name(), vmSnapshot.getUuid()); if (vmSnapshot.getState() == VMSnapshot.State.Allocated) { return _vmSnapshotDao.remove(vmSnapshot.getId()); } else { diff --git a/server/src/main/java/org/apache/cloudstack/annotation/AnnotationManagerImpl.java b/server/src/main/java/org/apache/cloudstack/annotation/AnnotationManagerImpl.java index 2b658d55e35..c151b5cd37e 100644 --- a/server/src/main/java/org/apache/cloudstack/annotation/AnnotationManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/annotation/AnnotationManagerImpl.java @@ -17,100 +17,468 @@ package org.apache.cloudstack.annotation; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import javax.inject.Inject; +import javax.naming.ConfigurationException; +import com.cloud.dc.ClusterVO; +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.HostPodVO; +import com.cloud.dc.dao.ClusterDao; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.dc.dao.HostPodDao; +import com.cloud.domain.DomainVO; +import com.cloud.domain.dao.DomainDao; import com.cloud.event.ActionEvent; import com.cloud.event.EventTypes; +import com.cloud.exception.PermissionDeniedException; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.kubernetes.cluster.KubernetesClusterHelper; +import com.cloud.network.dao.IPAddressDao; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.network.dao.Site2SiteCustomerGatewayDao; +import com.cloud.network.vpc.dao.VpcDao; +import com.cloud.offerings.NetworkOfferingVO; +import com.cloud.offerings.dao.NetworkOfferingDao; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.storage.DiskOfferingVO; +import com.cloud.storage.dao.DiskOfferingDao; +import com.cloud.storage.dao.SnapshotDao; +import com.cloud.storage.dao.VMTemplateDao; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.user.AccountService; +import com.cloud.user.AccountVO; +import com.cloud.user.UserVO; +import com.cloud.user.dao.AccountDao; +import com.cloud.user.dao.SSHKeyPairDao; +import com.cloud.user.dao.UserDao; +import com.cloud.utils.Pair; +import com.cloud.utils.StringUtils; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.component.PluggableService; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.dao.InstanceGroupDao; +import com.cloud.vm.dao.VMInstanceDao; +import com.cloud.vm.snapshot.dao.VMSnapshotDao; +import org.apache.cloudstack.acl.ControlledEntity; +import org.apache.cloudstack.acl.Role; +import org.apache.cloudstack.acl.RoleService; +import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.api.command.admin.annotation.AddAnnotationCmd; import org.apache.cloudstack.api.command.admin.annotation.ListAnnotationsCmd; import org.apache.cloudstack.api.command.admin.annotation.RemoveAnnotationCmd; +import org.apache.cloudstack.api.command.admin.annotation.UpdateAnnotationVisibilityCmd; import org.apache.cloudstack.api.response.AnnotationResponse; import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.Configurable; +import org.apache.cloudstack.storage.datastore.db.ImageStoreDao; +import org.apache.cloudstack.storage.datastore.db.ImageStoreVO; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.log4j.Logger; +import static org.apache.commons.lang.StringUtils.isBlank; +import static org.apache.commons.lang.StringUtils.isNotBlank; + /** * @since 4.11 */ -public final class AnnotationManagerImpl extends ManagerBase implements AnnotationService, PluggableService { +public final class AnnotationManagerImpl extends ManagerBase implements AnnotationService, Configurable, PluggableService { public static final Logger LOGGER = Logger.getLogger(AnnotationManagerImpl.class); @Inject private AnnotationDao annotationDao; + @Inject + private UserDao userDao; + @Inject + private AccountDao accountDao; + @Inject + private RoleService roleService; + @Inject + private AccountService accountService; + @Inject + private VMInstanceDao vmInstanceDao; + @Inject + private VolumeDao volumeDao; + @Inject + private SnapshotDao snapshotDao; + @Inject + private VMSnapshotDao vmSnapshotDao; + @Inject + private InstanceGroupDao instanceGroupDao; + @Inject + private SSHKeyPairDao sshKeyPairDao; + @Inject + private NetworkDao networkDao; + @Inject + private VpcDao vpcDao; + @Inject + private IPAddressDao ipAddressDao; + @Inject + private Site2SiteCustomerGatewayDao customerGatewayDao; + @Inject + private VMTemplateDao templateDao; + @Inject + private DataCenterDao dataCenterDao; + @Inject + private HostPodDao hostPodDao; + @Inject + private ClusterDao clusterDao; + @Inject + private HostDao hostDao; + @Inject + private PrimaryDataStoreDao primaryDataStoreDao; + @Inject + private ImageStoreDao imageStoreDao; + @Inject + private DomainDao domainDao; + @Inject + private ServiceOfferingDao serviceOfferingDao; + @Inject + private DiskOfferingDao diskOfferingDao; + @Inject + private NetworkOfferingDao networkOfferingDao; + + private static final List adminRoles = Collections.singletonList(RoleType.Admin); + private List kubernetesClusterHelpers; + + public List getKubernetesClusterHelpers() { + return kubernetesClusterHelpers; + } + + public void setKubernetesClusterHelpers(final List kubernetesClusterHelpers) { + this.kubernetesClusterHelpers = kubernetesClusterHelpers; + } + + @Override + public boolean start() { + super.start(); + return true; + } + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + return true; + } @Override public ListResponse searchForAnnotations(ListAnnotationsCmd cmd) { - List annotations = getAnnotationsForApiCmd(cmd); - List annotationResponses = convertAnnotationsToResponses(annotations); - return createAnnotationsResponseList(annotationResponses); + Pair, Integer> annotations = getAnnotationsForApiCmd(cmd); + List annotationResponses = convertAnnotationsToResponses(annotations.first()); + return createAnnotationsResponseList(annotationResponses, annotations.second()); } @Override @ActionEvent(eventType = EventTypes.EVENT_ANNOTATION_CREATE, eventDescription = "creating an annotation on an entity") public AnnotationResponse addAnnotation(AddAnnotationCmd addAnnotationCmd) { - return addAnnotation(addAnnotationCmd.getAnnotation(), addAnnotationCmd.getEntityType(), addAnnotationCmd.getEntityUuid()); + return addAnnotation(addAnnotationCmd.getAnnotation(), addAnnotationCmd.getEntityType(), + addAnnotationCmd.getEntityUuid(), addAnnotationCmd.isAdminsOnly()); } - public AnnotationResponse addAnnotation(String text, EntityType type, String uuid) { - CallContext ctx = CallContext.current(); - String userUuid = ctx.getCallingUserUuid(); + public AnnotationResponse addAnnotation(String text, EntityType type, String uuid, boolean adminsOnly) { + UserVO userVO = getCallingUserFromContext(); + String userUuid = userVO.getUuid(); + checkAnnotationPermissions(type, userVO); + isEntityOwnedByTheUser(type.name(), uuid, userVO); - AnnotationVO annotation = new AnnotationVO(text, type, uuid); + AnnotationVO annotation = new AnnotationVO(text, type, uuid, adminsOnly); annotation.setUserUuid(userUuid); annotation = annotationDao.persist(annotation); return createAnnotationResponse(annotation); } + private boolean isDomainAdminAllowedType(EntityType type) { + return type == EntityType.DOMAIN || type == EntityType.DISK_OFFERING || type == EntityType.SERVICE_OFFERING; + } + + private void checkAnnotationPermissions(EntityType type, UserVO user) { + if (isCallingUserRole(RoleType.Admin)) { + return; + } + List notAllowedTypes = EntityType.getNotAllowedTypesForNonAdmins(getCallingUserRole()); + if (notAllowedTypes.contains(type)) { + throw new CloudRuntimeException(String.format("User: %s is not allowed to add annotations on type: %s", + user.getUsername(), type.name())); + } + } + @Override @ActionEvent(eventType = EventTypes.EVENT_ANNOTATION_REMOVE, eventDescription = "removing an annotation on an entity") public AnnotationResponse removeAnnotation(RemoveAnnotationCmd removeAnnotationCmd) { String uuid = removeAnnotationCmd.getUuid(); - if(LOGGER.isDebugEnabled()) { - LOGGER.debug("marking annotation removed: " + uuid); - } AnnotationVO annotation = annotationDao.findByUuid(uuid); + if (!isCallingUserAllowedToRemoveAnnotation(annotation)) { + throw new CloudRuntimeException(String.format("Only administrators or entity owner users can delete annotations, " + + "cannot remove annotation with uuid: %s - type: %s ", uuid, annotation.getEntityType().name())); + } + if(LOGGER.isDebugEnabled()) { + LOGGER.debug(String.format("Removing annotation uuid: %s - type: %s", uuid, annotation.getEntityType().name())); + } annotationDao.remove(annotation.getId()); + return createAnnotationResponse(annotation); } - private List getAnnotationsForApiCmd(ListAnnotationsCmd cmd) { - List annotations; - if(cmd.getUuid() != null) { - annotations = new ArrayList<>(); - String uuid = cmd.getUuid().toString(); - if(LOGGER.isDebugEnabled()) { - LOGGER.debug("getting single annotation by uuid: " + uuid); - } + @Override + public AnnotationResponse updateAnnotationVisibility(UpdateAnnotationVisibilityCmd cmd) { + String uuid = cmd.getUuid(); + Boolean adminsOnly = cmd.getAdminsOnly(); + AnnotationVO annotation = annotationDao.findByUuid(uuid); + if (annotation == null || !isCallingUserRole(RoleType.Admin)) { + String errDesc = (annotation == null) ? String.format("Annotation id:%s does not exist", uuid) : + String.format("Type: %s", annotation.getEntityType().name()); + throw new CloudRuntimeException(String.format("Only admins can update annotations' visibility. " + + "Cannot update visibility for annotation with id: %s - %s", uuid, errDesc)); + } + if(LOGGER.isDebugEnabled()) { + LOGGER.debug(String.format("Updating annotation with uuid: %s visibility to %B: ", uuid, adminsOnly)); + } + annotation.setAdminsOnly(adminsOnly); + annotationDao.update(annotation.getId(), annotation); + return createAnnotationResponse(annotation); + } - annotations.add(annotationDao.findByUuid(uuid)); - } else if( ! (cmd.getEntityType() == null || cmd.getEntityType().isEmpty()) ) { - String type = cmd.getEntityType(); - if(LOGGER.isDebugEnabled()) { - LOGGER.debug("getting annotations for type: " + type); - } - if (cmd.getEntityUuid() != null) { - String uuid = cmd.getEntityUuid().toString(); - if(LOGGER.isDebugEnabled()) { - LOGGER.debug("getting annotations for entity: " + uuid); - } - annotations = annotationDao.findByEntity(type,cmd.getEntityUuid().toString()); - } else { - annotations = annotationDao.findByEntityType(type); - } + private boolean isCallingUserAllowedToRemoveAnnotation(AnnotationVO annotation) { + if (annotation == null) { + return false; + } + if (isCallingUserRole(RoleType.Admin)) { + return true; + } + UserVO callingUser = getCallingUserFromContext(); + String annotationOwnerUuid = annotation.getUserUuid(); + return annotationOwnerUuid != null && annotationOwnerUuid.equals(callingUser.getUuid()); + } + + private UserVO getCallingUserFromContext() { + CallContext ctx = CallContext.current(); + long userId = ctx.getCallingUserId(); + UserVO userVO = userDao.findById(userId); + if (userVO == null) { + throw new CloudRuntimeException("Cannot find a user with ID " + userId); + } + return userVO; + } + + private RoleType getCallingUserRole() { + UserVO userVO = getCallingUserFromContext(); + long accountId = userVO.getAccountId(); + AccountVO accountVO = accountDao.findById(accountId); + if (accountVO == null) { + throw new CloudRuntimeException("Cannot find account with ID + " + accountId); + } + Long roleId = accountVO.getRoleId(); + Role role = roleService.findRole(roleId); + if (role == null) { + throw new CloudRuntimeException("Cannot find role with ID " + roleId); + } + return role.getRoleType(); + } + + private boolean isCallingUserRole(RoleType roleType) { + RoleType userRoleType = getCallingUserRole(); + return roleType == userRoleType; + } + + private Pair, Integer> getAnnotationsForApiCmd(ListAnnotationsCmd cmd) { + List annotations; + String userUuid = cmd.getUserUuid(); + String entityUuid = cmd.getEntityUuid(); + String entityType = cmd.getEntityType(); + String annotationFilter = isNotBlank(cmd.getAnnotationFilter()) ? cmd.getAnnotationFilter() : "all"; + boolean isCallerAdmin = isCallingUserRole(RoleType.Admin); + UserVO callingUser = getCallingUserFromContext(); + String callingUserUuid = callingUser.getUuid(); + String keyword = cmd.getKeyword(); + + if (cmd.getUuid() != null) { + annotations = getSingleAnnotationListByUuid(cmd.getUuid(), userUuid, annotationFilter, callingUserUuid, isCallerAdmin); + } else if (isNotBlank(entityType)) { + annotations = getAnnotationsForSpecificEntityType(entityType, entityUuid, userUuid, isCallerAdmin, + annotationFilter, callingUserUuid, keyword, callingUser); + } else if (isNotBlank(entityUuid)) { + annotations = getAnnotationsForSpecificEntityId(entityUuid, userUuid, isCallerAdmin, + annotationFilter, callingUserUuid, keyword, callingUser); } else { - if(LOGGER.isDebugEnabled()) { - LOGGER.debug("getting all annotations"); - } - annotations = annotationDao.listAll(); + annotations = getAllAnnotations(annotationFilter, userUuid, callingUserUuid, isCallerAdmin, keyword); + } + List paginated = StringUtils.applyPagination(annotations, cmd.getStartIndex(), cmd.getPageSizeVal()); + return (paginated != null) ? new Pair<>(paginated, annotations.size()) : + new Pair<>(annotations, annotations.size()); + } + + private List getAllAnnotations(String annotationFilter, String userUuid, String callingUserUuid, + boolean isCallerAdmin, String keyword) { + if(LOGGER.isDebugEnabled()) { + LOGGER.debug("getting all annotations"); + } + if ("self".equalsIgnoreCase(annotationFilter) && isBlank(userUuid)) { + userUuid = callingUserUuid; + } + List annotations = annotationDao.listAllAnnotations(userUuid, getCallingUserRole(), + annotationFilter, keyword); + if (!isCallerAdmin) { + annotations = filterUserOwnedAnnotations(annotations); } return annotations; } + private List filterUserOwnedAnnotations(List annotations) { + UserVO userVO = getCallingUserFromContext(); + return annotations.stream() + .filter(x -> isEntityOwnedByTheUser(x.getEntityType().name(), x.getEntityUuid(), userVO)) + .collect(Collectors.toList()); + } + + private List getAnnotationsForSpecificEntityId(String entityUuid, String userUuid, boolean isCallerAdmin, + String annotationFilter, String callingUserUuid, + String keyword, UserVO callingUser) { + AnnotationVO annotation = annotationDao.findOneByEntityId(entityUuid); + if (annotation != null) { + String type = annotation.getEntityType().name(); + return getAnnotationsByEntityIdAndType(type, entityUuid, userUuid, isCallerAdmin, + annotationFilter, callingUserUuid, keyword, callingUser); + } + return new ArrayList<>(); + } + + private List getAnnotationsForSpecificEntityType(String entityType, String entityUuid, String userUuid, + boolean isCallerAdmin, String annotationFilter, + String callingUserUuid, String keyword, UserVO callingUser) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("getting annotations for type: " + entityType); + } + if ("self".equalsIgnoreCase(annotationFilter) && isBlank(userUuid)) { + userUuid = callingUserUuid; + } + if (isNotBlank(entityUuid)) { + return getAnnotationsByEntityIdAndType(entityType, entityUuid, userUuid, isCallerAdmin, + annotationFilter, callingUserUuid, keyword, callingUser); + } else { + List annotations = annotationDao.listByEntityType(entityType, userUuid, isCallerAdmin, + annotationFilter, callingUserUuid, keyword); + if (!isCallerAdmin) { + annotations = filterUserOwnedAnnotations(annotations); + } + return annotations; + } + } + + private List getSingleAnnotationListByUuid(String uuid, String userUuid, String annotationFilter, + String callingUserUuid, boolean isCallerAdmin) { + List annotations = new ArrayList<>(); + if(LOGGER.isDebugEnabled()) { + LOGGER.debug("getting single annotation by uuid: " + uuid); + } + + AnnotationVO annotationVO = annotationDao.findByUuid(uuid); + if (annotationVO != null && annotationVO.getUserUuid().equals(userUuid) && + (annotationFilter.equalsIgnoreCase("all") || + (annotationFilter.equalsIgnoreCase("self") && annotationVO.getUserUuid().equals(callingUserUuid))) && + annotationVO.isAdminsOnly() == isCallerAdmin) { + annotations.add(annotationVO); + } + return annotations; + } + + private List getAnnotationsByEntityIdAndType(String entityType, String entityUuid, String userUuid, + boolean isCallerAdmin, String annotationFilter, + String callingUserUuid, String keyword, UserVO callingUser) { + isEntityOwnedByTheUser(entityType, entityUuid, callingUser); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("getting annotations for entity: " + entityUuid); + } + return annotationDao.listByEntity(entityType, entityUuid, userUuid, isCallerAdmin, + annotationFilter, callingUserUuid, keyword); + } + + private boolean isEntityOwnedByTheUser(String entityType, String entityUuid, UserVO callingUser) { + try { + if (!isCallingUserRole(RoleType.Admin)) { + EntityType type = EntityType.valueOf(entityType); + List notAllowedTypes = EntityType.getNotAllowedTypesForNonAdmins(getCallingUserRole()); + if (notAllowedTypes.contains(type)) { + return false; + } + if (isCallingUserRole(RoleType.DomainAdmin)) { + if (type == EntityType.SERVICE_OFFERING || type == EntityType.DISK_OFFERING) { + return true; + } else if (type == EntityType.DOMAIN) { + DomainVO domain = domainDao.findByUuid(entityUuid); + AccountVO account = accountDao.findById(callingUser.getAccountId()); + accountService.checkAccess(account, domain); + return true; + } + } + ControlledEntity entity = getEntityFromUuidAndType(entityUuid, type); + if (entity == null) { + String errMsg = String.format("Could not find an entity with type: %s and ID: %s", entityType, entityUuid); + LOGGER.error(errMsg); + throw new CloudRuntimeException(errMsg); + } + if (type == EntityType.NETWORK && entity instanceof NetworkVO && + ((NetworkVO) entity).getAclType() == ControlledEntity.ACLType.Domain) { + NetworkVO network = (NetworkVO) entity; + DomainVO domain = domainDao.findById(network.getDomainId()); + AccountVO account = accountDao.findById(callingUser.getAccountId()); + accountService.checkAccess(account, domain); + } else { + accountService.checkAccess(callingUser, entity); + } + } + } catch (IllegalArgumentException e) { + LOGGER.error("Could not parse entity type " + entityType, e); + return false; + } catch (PermissionDeniedException e) { + LOGGER.debug(e.getMessage(), e); + return false; + } + return true; + } + + private ControlledEntity getEntityFromUuidAndType(String entityUuid, EntityType type) { + switch (type) { + case VM: + return vmInstanceDao.findByUuid(entityUuid); + case VOLUME: + return volumeDao.findByUuid(entityUuid); + case SNAPSHOT: + return snapshotDao.findByUuid(entityUuid); + case VM_SNAPSHOT: + return vmSnapshotDao.findByUuid(entityUuid); + case INSTANCE_GROUP: + return instanceGroupDao.findByUuid(entityUuid); + case SSH_KEYPAIR: + return sshKeyPairDao.findByUuid(entityUuid); + case NETWORK: + return networkDao.findByUuid(entityUuid); + case VPC: + return vpcDao.findByUuid(entityUuid); + case PUBLIC_IP_ADDRESS: + return ipAddressDao.findByUuid(entityUuid); + case VPN_CUSTOMER_GATEWAY: + return customerGatewayDao.findByUuid(entityUuid); + case TEMPLATE: + case ISO: + return templateDao.findByUuid(entityUuid); + case KUBERNETES_CLUSTER: + return kubernetesClusterHelpers.get(0).findByUuid(entityUuid); + default: + throw new CloudRuntimeException("Invalid entity type " + type); + } + } + private List convertAnnotationsToResponses(List annotations) { List annotationResponses = new ArrayList<>(); for (AnnotationVO annotation : annotations) { @@ -119,13 +487,13 @@ public final class AnnotationManagerImpl extends ManagerBase implements Annotati return annotationResponses; } - private ListResponse createAnnotationsResponseList(List annotationResponses) { + private ListResponse createAnnotationsResponseList(List annotationResponses, Integer count) { ListResponse listResponse = new ListResponse<>(); - listResponse.setResponses(annotationResponses); + listResponse.setResponses(annotationResponses, count); return listResponse; } - public static AnnotationResponse createAnnotationResponse(AnnotationVO annotation) { + public AnnotationResponse createAnnotationResponse(AnnotationVO annotation) { AnnotationResponse response = new AnnotationResponse(); response.setUuid(annotation.getUuid()); response.setEntityType(annotation.getEntityType()); @@ -134,16 +502,88 @@ public final class AnnotationManagerImpl extends ManagerBase implements Annotati response.setUserUuid(annotation.getUserUuid()); response.setCreated(annotation.getCreated()); response.setRemoved(annotation.getRemoved()); + UserVO user = userDao.findByUuid(annotation.getUserUuid()); + if (user != null && StringUtils.isNotBlank(user.getUsername())) { + response.setUsername(user.getUsername()); + } + setResponseEntityName(response, annotation.getEntityUuid(), annotation.getEntityType()); + response.setAdminsOnly(annotation.isAdminsOnly()); response.setObjectName("annotation"); return response; } + private String getInfrastructureEntityName(String entityUuid, EntityType entityType) { + switch (entityType) { + case ZONE: + DataCenterVO zone = dataCenterDao.findByUuid(entityUuid); + return zone != null ? zone.getName() : null; + case POD: + HostPodVO pod = hostPodDao.findByUuid(entityUuid); + return pod != null ? pod.getName() : null; + case CLUSTER: + ClusterVO cluster = clusterDao.findByUuid(entityUuid); + return cluster != null ? cluster.getName() : null; + case HOST: + HostVO host = hostDao.findByUuid(entityUuid); + return host != null ? host.getName() : null; + case PRIMARY_STORAGE: + StoragePoolVO primaryStorage = primaryDataStoreDao.findByUuid(entityUuid); + return primaryStorage != null ? primaryStorage.getName() : null; + case SECONDARY_STORAGE: + ImageStoreVO imageStore = imageStoreDao.findByUuid(entityUuid); + return imageStore != null ? imageStore.getName() : null; + case DOMAIN: + DomainVO domain = domainDao.findByUuid(entityUuid); + return domain != null ? domain.getName() : null; + case SERVICE_OFFERING: + ServiceOfferingVO offering = serviceOfferingDao.findByUuid(entityUuid); + return offering != null ? offering.getName() : null; + case DISK_OFFERING: + DiskOfferingVO diskOffering = diskOfferingDao.findByUuid(entityUuid); + return diskOffering != null ? diskOffering.getName() : null; + case NETWORK_OFFERING: + NetworkOfferingVO networkOffering = networkOfferingDao.findByUuid(entityUuid); + return networkOffering != null ? networkOffering.getName() : null; + case VR: + case SYSTEM_VM: + VMInstanceVO instance = vmInstanceDao.findByUuid(entityUuid); + return instance != null ? instance.getInstanceName() : null; + default: + return null; + } + } + + private void setResponseEntityName(AnnotationResponse response, String entityUuid, EntityType entityType) { + String entityName = null; + if (entityType.isUserAllowed()) { + ControlledEntity entity = getEntityFromUuidAndType(entityUuid, entityType); + if (entity != null) { + LOGGER.debug(String.format("Could not find an entity with type: %s and ID: %s", entityType.name(), entityUuid)); + entityName = entity.getName(); + } + } else { + entityName = getInfrastructureEntityName(entityUuid, entityType); + } + response.setEntityName(entityName); + } + @Override public List> getCommands() { final List> cmdList = new ArrayList<>(); cmdList.add(AddAnnotationCmd.class); cmdList.add(ListAnnotationsCmd.class); cmdList.add(RemoveAnnotationCmd.class); + cmdList.add(UpdateAnnotationVisibilityCmd.class); return cmdList; } + + @Override + public String getConfigComponentName() { + return AnnotationManagerImpl.class.getSimpleName(); + } + + @Override + public ConfigKey[] getConfigKeys() { + return new ConfigKey[]{}; + } } 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 e9905c52d93..207270dd79c 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 @@ -297,7 +297,9 @@ - + + + diff --git a/server/src/test/java/com/cloud/user/DomainManagerImplTest.java b/server/src/test/java/com/cloud/user/DomainManagerImplTest.java index 2e9b54049d1..b427d3c70b1 100644 --- a/server/src/test/java/com/cloud/user/DomainManagerImplTest.java +++ b/server/src/test/java/com/cloud/user/DomainManagerImplTest.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.UUID; import com.cloud.domain.dao.DomainDetailsDao; +import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.cloudstack.framework.messagebus.MessageBus; @@ -95,6 +96,8 @@ public class DomainManagerImplTest { ConfigurationManager _configMgr; @Mock DomainDetailsDao _domainDetailsDao; + @Mock + AnnotationDao annotationDao; @Spy @InjectMocks diff --git a/server/src/test/java/org/apache/cloudstack/networkoffering/CreateNetworkOfferingTest.java b/server/src/test/java/org/apache/cloudstack/networkoffering/CreateNetworkOfferingTest.java index 78e70f34d56..c05b732042e 100644 --- a/server/src/test/java/org/apache/cloudstack/networkoffering/CreateNetworkOfferingTest.java +++ b/server/src/test/java/org/apache/cloudstack/networkoffering/CreateNetworkOfferingTest.java @@ -28,6 +28,7 @@ import java.util.Set; import javax.inject.Inject; +import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.framework.config.impl.ConfigurationVO; @@ -100,6 +101,9 @@ public class CreateNetworkOfferingTest extends TestCase { @Inject LoadBalancerVMMapDao _loadBalancerVMMapDao; + @Inject + AnnotationDao annotationDao; + @Override @Before public void setUp() { diff --git a/server/src/test/resources/createNetworkOffering.xml b/server/src/test/resources/createNetworkOffering.xml index 55343ef835d..897d4dac305 100644 --- a/server/src/test/resources/createNetworkOffering.xml +++ b/server/src/test/resources/createNetworkOffering.xml @@ -61,4 +61,5 @@ + diff --git a/test/integration/smoke/test_host_annotations.py b/test/integration/smoke/test_annotations.py similarity index 53% rename from test/integration/smoke/test_host_annotations.py rename to test/integration/smoke/test_annotations.py index a463af9e7fe..8b8694646cb 100644 --- a/test/integration/smoke/test_host_annotations.py +++ b/test/integration/smoke/test_annotations.py @@ -31,39 +31,87 @@ import time _multiprocess_shared_ = True -class TestHostAnnotations(cloudstackTestCase): + +class TestAnnotations(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + testClient = super(TestAnnotations, cls).getClsTestClient() + cls.apiclient = testClient.getApiClient() + cls.services = testClient.getParsedTestDataConfig() + + # Get Zone, Domain and templates + cls.domain = get_domain(cls.apiclient) + cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests()) + cls.hypervisor = testClient.getHypervisorInfo() + cls.services['mode'] = cls.zone.networktype + template = get_test_template( + cls.apiclient, + cls.zone.id, + cls.hypervisor + ) + if template == FAILED: + cls.fail("get_test_template() failed to return template") + + cls.services["virtual_machine"]["zoneid"] = cls.zone.id + + cls._cleanup = [] + + # Create an account, network, VM and IP addresses + cls.account = Account.create( + cls.apiclient, + cls.services["account"], + domainid=cls.domain.id + ) + cls._cleanup.append(cls.account) + cls.userApiClient = testClient.getUserApiClient(cls.account.name, 'ROOT', 'User') + + cls.service_offering = ServiceOffering.create( + cls.apiclient, + cls.services["service_offerings"]["tiny"] + ) + cls._cleanup.append(cls.service_offering) + cls.user_vm = VirtualMachine.create( + cls.apiclient, + cls.services["virtual_machine"], + templateid=template.id, + accountid=cls.account.name, + domainid=cls.account.domainid, + serviceofferingid=cls.service_offering.id + ) + cls._cleanup.append(cls.user_vm) + cls.host = list_hosts(cls.apiclient, + zoneid=cls.zone.id, + type='Routing')[0] + + @classmethod + def tearDownClass(cls): + super(TestAnnotations, cls).tearDownClass() def setUp(self): self.apiclient = self.testClient.getApiClient() self.services = self.testClient.getParsedTestDataConfig() - self.zone = get_zone(self.apiclient, self.testClient.getZoneForTests()) - self.host = list_hosts(self.apiclient, - zoneid=self.zone.id, - type='Routing')[0] self.cleanup = [] self.added_annotations = [] return def tearDown(self): - try: - #Clean up - cleanup_resources(self.apiclient, self.cleanup) - self.cleanAnnotations() - except Exception as e: - raise Exception("Warning: Exception during cleanup : %s" % e) - return + self.cleanAnnotations() + super(TestAnnotations, self).tearDown() def cleanAnnotations(self): """Remove annotations""" for annotation in self.added_annotations: self.removeAnnotation(annotation.annotation.id) - def addAnnotation(self, annotation): + def addAnnotation(self, annotation, entityid, entitytype, adminsonly=None): cmd = addAnnotation.addAnnotationCmd() - cmd.entityid = self.host.id - cmd.entitytype = "HOST" + cmd.entityid = entityid + cmd.entitytype = entitytype cmd.annotation = annotation + if adminsonly: + cmd.adminsonly = adminsonly self.added_annotations.append(self.apiclient.addAnnotation(cmd)) @@ -84,7 +132,7 @@ class TestHostAnnotations(cloudstackTestCase): @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") def test_01_add_annotation(self): """Testing the addAnnotations API ability to add an annoatation per host""" - self.addAnnotation("annotation1") + self.addAnnotation("annotation1", self.host.id, "HOST") self.assertEqual(self.added_annotations[-1].annotation.annotation, "annotation1") @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") @@ -92,17 +140,17 @@ class TestHostAnnotations(cloudstackTestCase): """Testing the addAnnotations API ability to add an annoatation per host when there are annotations already. And only the last one stands as annotation attribute on host level.""" - self.addAnnotation("annotation1") + self.addAnnotation("annotation1", self.host.id, "HOST") self.assertEqual(self.added_annotations[-1].annotation.annotation, "annotation1") # Adds sleep of 1 second just to be sure next annotation will not be created in the same second. time.sleep(1) - self.addAnnotation("annotation2") + self.addAnnotation("annotation2", self.host.id, "HOST") self.assertEqual(self.added_annotations[-1].annotation.annotation, "annotation2") # Adds sleep of 1 second just to be sure next annotation will not be created in the same second. time.sleep(1) - self.addAnnotation("annotation3") + self.addAnnotation("annotation3", self.host.id, "HOST") self.assertEqual(self.added_annotations[-1].annotation.annotation, "annotation3") #Check that the last one is visible in host details @@ -110,35 +158,27 @@ class TestHostAnnotations(cloudstackTestCase): print() @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") - def test_03_user_role_dont_see_annotations(self): - """Testing the annotations api are restricted to users""" + def test_03_user_role_dont_infrastructure_annotations(self): + """Testing the annotations on infrastructure are restricted to users""" - self.addAnnotation("annotation1") + self.addAnnotation("annotation1", self.host.id, "HOST") self.assertEqual(self.added_annotations[-1].annotation.annotation, "annotation1") - self.account = Account.create( - self.apiclient, - self.services["account"], - ) - self.cleanup.append(self.account) - - userApiClient = self.testClient.getUserApiClient(self.account.name, 'ROOT', 'User') - cmd = addAnnotation.addAnnotationCmd() cmd.entityid = self.host.id cmd.entitytype = "HOST" cmd.annotation = "test" try: - self.added_annotations.append(userApiClient.addAnnotation(cmd)) + self.added_annotations.append(self.userApiClient.addAnnotation(cmd)) except Exception: pass else: self.fail("AddAnnotation is allowed for User") - cmd = listAnnotations.listAnnotationsCmd() + try: - userApiClient.listAnnotations(cmd) + self.userApiClient.listAnnotations(cmd) except Exception: pass else: @@ -147,7 +187,7 @@ class TestHostAnnotations(cloudstackTestCase): cmd = removeAnnotation.removeAnnotationCmd() cmd.id = self.added_annotations[-1].annotation.id try: - userApiClient.removeAnnotation(cmd) + self.userApiClient.removeAnnotation(cmd) except Exception: pass else: @@ -156,7 +196,7 @@ class TestHostAnnotations(cloudstackTestCase): @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") def test_04_remove_annotations(self): """Testing the deleteAnnotation API ability to delete annotation""" - self.addAnnotation("annotation1") + self.addAnnotation("annotation1", self.host.id, "HOST") self.removeAnnotation(self.added_annotations[-1].annotation.id) del self.added_annotations[-1] @@ -175,3 +215,42 @@ class TestHostAnnotations(cloudstackTestCase): else: self.fail("AddAnnotation is allowed for on an unknown entityType") + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") + def test_06_add_adminsonly_and_update_annotation_visibility(self): + """Testing admins ability to create private annotations""" + + # Admin creates an annotation only visible to admin + self.addAnnotation("private annotation by admin", self.user_vm.id, "VM", True) + cmd = listAnnotations.listAnnotationsCmd() + cmd.entityid = self.user_vm.id + cmd.entitytype = "VM" + cmd.annotationfilter = "all" + annotation_id = self.added_annotations[-1].annotation.id + + # Verify users cannot see private annotations created by admins + userVisibleAnnotations = self.userApiClient.listAnnotations(cmd) + self.assertIsNone( + userVisibleAnnotations, + "User must not access admin-only annotations" + ) + + # Admin updates the annotation visibility + cmd = updateAnnotationVisibility.updateAnnotationVisibilityCmd() + cmd.id = annotation_id + cmd.adminsonly = False + self.apiclient.updateAnnotationVisibility(cmd) + + # Verify user can see the annotation after updating its visibility + cmd = listAnnotations.listAnnotationsCmd() + cmd.entityid = self.user_vm.id + cmd.entitytype = "VM" + cmd.annotationfilter = "all" + userVisibleAnnotations = self.userApiClient.listAnnotations(cmd) + self.assertIsNotNone( + userVisibleAnnotations, + "User must access public annotations" + ) + + # Remove the annotation + self.removeAnnotation(annotation_id) + del self.added_annotations[-1] \ No newline at end of file diff --git a/tools/apidoc/gen_toc.py b/tools/apidoc/gen_toc.py index 6d7841edb4b..7ed3e98bd54 100644 --- a/tools/apidoc/gen_toc.py +++ b/tools/apidoc/gen_toc.py @@ -185,6 +185,7 @@ known_categories = { 'listAnnotations' : 'Annotations', 'addAnnotation' : 'Annotations', 'removeAnnotation' : 'Annotations', + 'updateAnnotationVisibility' : 'Annotations', 'CA': 'Certificate', 'listElastistorInterface': 'Misc', 'cloudian': 'Cloudian', diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 0be8ae46f92..90fcad857c1 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -349,7 +349,7 @@ "label.add.new.tier": "Add New Tier", "label.add.nfs.secondary.staging.store": "Add NFS Secondary Staging Store", "label.add.niciranvp.device": "Add Nvp Controller", -"label.add.note": "Add Note", +"label.add.note": "Add Comment", "label.add.opendaylight.device": "Add OpenDaylight Controller", "label.add.pa.device": "Add Palo Alto device", "label.add.physical.network": "Add Physical Network", @@ -440,7 +440,12 @@ "label.allow": "Allow", "label.allowuserdrivenbackups": "Allow User Driven Backups", "label.annotated.by": "Annotator", -"label.annotation": "Annotation", +"label.annotation": "Comment", +"label.annotations": "Comments", +"label.annotation.admins.only": "Only visible to Administrators", +"label.annotation.entity": "Entity", +"label.annotation.entity.type": "Entity Type", +"label.annotation.everyone": "Visible to everyone", "label.anti.affinity": "Anti-affinity", "label.anti.affinity.group": "Anti-affinity Group", "label.anti.affinity.groups": "Anti-affinity Groups", @@ -932,6 +937,8 @@ "label.fetch.latest": "Fetch latest", "label.files": "Alternate Files to Retrieve", "label.filter": "Filter", +"label.filter.annotations.self": "Created by me", +"label.filter.annotations.all": "All comments", "label.filterby": "Filter by", "label.fingerprint": "FingerPrint", "label.firewall": "Firewall", @@ -1312,6 +1319,7 @@ "label.macaddress.example": "The MAC Address. Example: 01:23:45:67:89:ab", "label.macaddresschanges": "MAC Address Changes", "label.macos": "MacOS", +"label.make": "Make", "label.make.project.owner": "Make account project owner", "label.make.user.project.owner": "Make user project owner", "label.makeredundant": "Make redundant", @@ -1800,6 +1808,7 @@ "label.remind.later": "Remind me later", "label.remove": "Remove", "label.remove.acl": "Remove ACL", +"label.remove.annotation": "Remove Comment", "label.remove.egress.rule": "Remove egress rule", "label.remove.from.load.balancer": "Removing instance from load balancer", "label.remove.ingress.rule": "Remove ingress rule", @@ -3129,6 +3138,7 @@ "message.releasing.dedicated.host": "Releasing dedicated host...", "message.releasing.dedicated.pod": "Releasing dedicated pod...", "message.releasing.dedicated.zone": "Releasing dedicated zone...", +"message.remove.annotation": "Are you sure you want to delete the comment?", "message.remove.egress.rule.failed": "Removing Egress rule failed", "message.remove.egress.rule.processing": "Deleting Egress rule...", "message.remove.failed": "Removing failed", diff --git a/ui/src/components/view/ActionButton.vue b/ui/src/components/view/ActionButton.vue index 5202cc6001d..093d8198d5d 100644 --- a/ui/src/components/view/ActionButton.vue +++ b/ui/src/components/view/ActionButton.vue @@ -37,7 +37,7 @@ :count="actionBadge[action.api] ? actionBadge[action.api].badgeNum : 0" v-if="action.api in $store.getters.apis && action.showBadge && ( - (!dataView && ((action.listView && ('show' in action ? action.show(resource, $store.getters) : true)) || (action.groupAction && selectedRowKeys.length > 0 && ('groupShow' in action ? action.show(resource, $store.getters) : true)))) || + (!dataView && ((action.listView && ('show' in action ? action.show(resource, $store.getters) : true)) || (action.groupAction && selectedRowKeys.length > 0 && ('groupShow' in action ? action.groupShow(selectedItems, $store.getters) : true)))) || (dataView && action.dataView && ('show' in action ? action.show(resource, $store.getters) : true)) )" :disabled="'disabled' in action ? action.disabled(resource, $store.getters) : false" > @@ -57,7 +57,7 @@ + + + + + + + diff --git a/ui/src/components/view/InfoCard.vue b/ui/src/components/view/InfoCard.vue index 7419d03b5dc..23860768cd0 100644 --- a/ui/src/components/view/InfoCard.vue +++ b/ui/src/components/view/InfoCard.vue @@ -652,57 +652,6 @@ - - @@ -749,51 +698,26 @@ export default { return { ipaddress: '', resourceType: '', - annotationType: '', inputVisible: false, inputKey: '', inputValue: '', tags: [], - notes: [], - annotation: '', showKeys: false, - showNotesInput: false, - loadingTags: false, - loadingAnnotations: false + loadingTags: false } }, watch: { resource: function (newItem, oldItem) { this.resource = newItem this.resourceType = this.$route.meta.resourceType - this.annotationType = '' this.showKeys = false this.setData() - switch (this.resourceType) { - case 'UserVm': - this.annotationType = 'VM' - break - case 'Domain': - this.annotationType = 'DOMAIN' - // Domain resource type is not supported for tags - this.resourceType = '' - break - case 'Host': - this.annotationType = 'HOST' - // Host resource type is not supported for tags - this.resourceType = '' - break - } - if ('tags' in this.resource) { this.tags = this.resource.tags } else if (this.resourceType) { this.getTags() } - if (this.annotationType) { - this.getNotes() - } if ('apikey' in this.resource) { this.getUserKeys() } @@ -866,20 +790,6 @@ export default { this.loadingTags = false }) }, - getNotes () { - if (!('listAnnotations' in this.$store.getters.apis)) { - return - } - this.loadingAnnotations = true - this.notes = [] - api('listAnnotations', { entityid: this.resource.id, entitytype: this.annotationType }).then(json => { - if (json.listannotationsresponse && json.listannotationsresponse.annotation) { - this.notes = json.listannotationsresponse.annotation - } - }).finally(() => { - this.loadingAnnotations = false - }) - }, isAdminOrOwner () { return ['Admin'].includes(this.$store.getters.userInfo.roletype) || (this.resource.domainid === this.$store.getters.userInfo.domainid && this.resource.account === this.$store.getters.userInfo.account) || @@ -924,34 +834,6 @@ export default { }).finally(e => { this.getTags() }) - }, - handleNoteChange (e) { - this.annotation = e.target.value - }, - saveNote () { - if (this.annotation.length < 1) { - return - } - this.loadingAnnotations = true - this.showNotesInput = false - const args = {} - args.entityid = this.resource.id - args.entitytype = this.annotationType - args.annotation = this.annotation - api('addAnnotation', args).then(json => { - }).finally(e => { - this.getNotes() - }) - this.annotation = '' - }, - deleteNote (annotation) { - this.loadingAnnotations = true - const args = {} - args.id = annotation.id - api('removeAnnotation', args).then(json => { - }).finally(e => { - this.getNotes() - }) } } } @@ -1036,31 +918,6 @@ export default { } } -.account-center-team { - .members { - a { - display: block; - margin: 12px 0; - line-height: 24px; - height: 24px; - .member { - font-size: 14px; - color: rgba(0, 0, 0, 0.65); - line-height: 24px; - max-width: 100px; - vertical-align: top; - margin-left: 12px; - transition: all 0.3s; - display: inline-block; - } - &:hover { - span { - color: #1890ff; - } - } - } - } -} .title { margin-bottom: 5px; font-weight: bold; diff --git a/ui/src/components/view/ListView.vue b/ui/src/components/view/ListView.vue index 795a6e4e104..31952400842 100644 --- a/ui/src/components/view/ListView.vue +++ b/ui/src/components/view/ListView.vue @@ -73,7 +73,14 @@ - {{ text }} + + + {{ text }} + + + {{ text }} + + {{ text }} {{ $t(text.toLowerCase()) }} {{ $t(text.toLowerCase()) }} @@ -105,6 +112,16 @@ {{ text }} {{ text }} + + {{ record.entityname }} + + + {{ generateHumanReadableEntityType(record) }} + + + + + {{ text }} {{ text }} @@ -421,7 +438,7 @@ export default { '/guestnetwork', '/vpc', '/vpncustomergateway', '/template', '/iso', '/project', '/account', - '/zone', '/pod', '/cluster', '/host', '/storagepool', '/imagestore', '/systemvm', '/router', '/ilbvm', + '/zone', '/pod', '/cluster', '/host', '/storagepool', '/imagestore', '/systemvm', '/router', '/ilbvm', '/annotation', '/computeoffering', '/systemoffering', '/diskoffering', '/backupoffering', '/networkoffering', '/vpcoffering'].join('|')) .test(this.$route.path) }, @@ -429,7 +446,7 @@ export default { return ['vm', 'alert', 'vmgroup', 'ssh', 'affinitygroup', 'volume', 'snapshot', 'vmsnapshot', 'guestnetwork', 'vpc', 'publicip', 'vpnuser', 'vpncustomergateway', 'project', 'account', 'systemvm', 'router', 'computeoffering', 'systemoffering', - 'diskoffering', 'backupoffering', 'networkoffering', 'vpcoffering', 'ilbvm', 'kubernetes' + 'diskoffering', 'backupoffering', 'networkoffering', 'vpcoffering', 'ilbvm', 'kubernetes', 'comment' ].includes(this.$route.name) }, fetchColumns () { @@ -586,6 +603,80 @@ export default { return record.nic.filter(e => { return e.ip6address }).map(e => { return e.ip6address }).join(', ') || text }, + generateCommentsPath (record) { + return '/' + this.entityTypeToPath(record.entitytype) + '/' + record.entityid + '?tab=comments' + }, + generateHumanReadableEntityType (record) { + switch (record.entitytype) { + case 'VM' : return 'Virtual Machine' + case 'HOST' : return 'Host' + case 'VOLUME' : return 'Volume' + case 'SNAPSHOT' : return 'Snapshot' + case 'VM_SNAPSHOT' : return 'VM Snapshot' + case 'INSTANCE_GROUP' : return 'Instance Group' + case 'NETWORK' : return 'Network' + case 'VPC' : return 'VPC' + case 'PUBLIC_IP_ADDRESS' : return 'Public IP Address' + case 'VPN_CUSTOMER_GATEWAY' : return 'VPC Customer Gateway' + case 'TEMPLATE' : return 'Template' + case 'ISO' : return 'ISO' + case 'SSH_KEYPAIR' : return 'SSH Key Pair' + case 'DOMAIN' : return 'Domain' + case 'SERVICE_OFFERING' : return 'Service Offfering' + case 'DISK_OFFERING' : return 'Disk Offering' + case 'NETWORK_OFFERING' : return 'Network Offering' + case 'POD' : return 'Pod' + case 'ZONE' : return 'Zone' + case 'CLUSTER' : return 'Cluster' + case 'PRIMARY_STORAGE' : return 'Primary Storage' + case 'SECONDARY_STORAGE' : return 'Secondary Storage' + case 'VR' : return 'Virtual Router' + case 'SYSTEM_VM' : return 'System VM' + case 'KUBERNETES_CLUSTER': return 'Kubernetes Cluster' + default: return record.entitytype.toLowerCase().replace('_', '') + } + }, + entityTypeToPath (entitytype) { + switch (entitytype) { + case 'VM' : return 'vm' + case 'HOST' : return 'host' + case 'VOLUME' : return 'volume' + case 'SNAPSHOT' : return 'snapshot' + case 'VM_SNAPSHOT' : return 'vmsnapshot' + case 'INSTANCE_GROUP' : return 'vmgroup' + case 'NETWORK' : return 'guestnetwork' + case 'VPC' : return 'vpc' + case 'PUBLIC_IP_ADDRESS' : return 'publicip' + case 'VPN_CUSTOMER_GATEWAY' : return 'vpncustomergateway' + case 'TEMPLATE' : return 'template' + case 'ISO' : return 'iso' + case 'SSH_KEYPAIR' : return 'ssh' + case 'DOMAIN' : return 'domain' + case 'SERVICE_OFFERING' : return 'computeoffering' + case 'DISK_OFFERING' : return 'diskoffering' + case 'NETWORK_OFFERING' : return 'networkoffering' + case 'POD' : return 'pod' + case 'ZONE' : return 'zone' + case 'CLUSTER' : return 'cluster' + case 'PRIMARY_STORAGE' : return 'storagepool' + case 'SECONDARY_STORAGE' : return 'imagestore' + case 'VR' : return 'router' + case 'SYSTEM_VM' : return 'systemvm' + case 'KUBERNETES_CLUSTER': return 'kubernetes' + default: return entitytype.toLowerCase().replace('_', '') + } + }, + updateAdminsOnly (e) { + api('updateAnnotationVisibility', { + id: e.target.value, + adminsonly: e.target.checked + }).finally(() => { + const data = this.items + const index = data.findIndex(item => item.id === e.target.value) + const elem = data[index] + elem.adminsonly = e.target.checked + }) + }, getHostState (host) { if (host && host.hypervisor === 'KVM' && host.state === 'Up' && host.details && host.details.secured !== 'true') { return 'Unsecure' @@ -604,6 +695,18 @@ export default { /deep/ .ant-table-small > .ant-table-content > .ant-table-body { margin: 0; } + +/deep/ .light-row { + background-color: #fff; +} + +/deep/ .dark-row { + background-color: #f9f9f9; +} + +/deep/ .ant-table-tbody>tr>td, .ant-table-thead>tr>th { + overflow-wrap: anywhere; +}