You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by ro...@apache.org on 2021/09/08 04:44:23 UTC

[cloudstack] branch main updated: server: Extend the Annotations framework (#5103)

This is an automated email from the ASF dual-hosted git repository.

rohit pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/main by this push:
     new 413d10d  server: Extend the Annotations framework (#5103)
413d10d is described below

commit 413d10dd8106af9b3c369c1a393fbcca5e7217ce
Author: Nicolas Vazquez <ni...@gmail.com>
AuthorDate: Wed Sep 8 01:44:06 2021 -0300

    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 <dj...@gmail.com>
---
 .../cluster/KubernetesClusterHelper.java}          |  18 +-
 .../com/cloud/network/vpc/StaticRouteProfile.java  |   5 +
 api/src/main/java/com/cloud/user/SSHKeyPair.java   |   3 +-
 .../apache/cloudstack/acl/ControlledEntity.java    |   1 +
 .../cloudstack/annotation/AnnotationService.java   |  48 +-
 .../org/apache/cloudstack/api/ApiConstants.java    |   8 +-
 ...ation.java => BaseResponseWithAnnotations.java} |  21 +-
 .../api/BaseResponseWithTagInformation.java        |   2 +-
 .../command/admin/annotation/AddAnnotationCmd.java |  11 +
 .../admin/annotation/ListAnnotationsCmd.java       |  21 +
 ...Cmd.java => UpdateAnnotationVisibilityCmd.java} |  52 +--
 .../api/command/admin/host/UpdateHostCmd.java      |   2 +-
 .../user/firewall/CreateEgressFirewallRuleCmd.java |   5 +
 .../user/firewall/CreateFirewallRuleCmd.java       |   5 +
 .../user/firewall/CreatePortForwardingRuleCmd.java |   5 +
 .../user/nat/CreateIpForwardingRuleCmd.java        |   5 +
 .../api/response/AnnotationResponse.java           |  36 ++
 .../cloudstack/api/response/ClusterResponse.java   |   4 +-
 .../api/response/CreateSSHKeyPairResponse.java     |   4 +-
 .../api/response/DiskOfferingResponse.java         |   4 +-
 .../cloudstack/api/response/DomainResponse.java    |   4 +-
 .../api/response/DomainRouterResponse.java         |   4 +-
 .../cloudstack/api/response/HostResponse.java      |   4 +-
 .../cloudstack/api/response/IPAddressResponse.java |   4 +-
 .../api/response/ImageStoreResponse.java           |   4 +-
 .../api/response/InstanceGroupResponse.java        |   4 +-
 .../api/response/NetworkOfferingResponse.java      |   4 +-
 .../cloudstack/api/response/NetworkResponse.java   |   4 +-
 .../cloudstack/api/response/PodResponse.java       |   4 +-
 .../api/response/SSHKeyPairResponse.java           |  19 +-
 .../api/response/ServiceOfferingResponse.java      |   4 +-
 .../response/Site2SiteCustomerGatewayResponse.java |   4 +-
 .../api/response/StoragePoolResponse.java          |   4 +-
 .../cloudstack/api/response/SystemVmResponse.java  |   4 +-
 .../cloudstack/api/response/VpcResponse.java       |   4 +-
 .../cloudstack/api/response/ZoneResponse.java      |   4 +-
 .../core/spring-core-registry-core-context.xml     |   3 +
 .../cloudstack/kubernetes}/module.properties       |   7 +-
 ...re-lifecycle-kubernetes-context-inheritable.xml |  32 ++
 .../main/java/com/cloud/network/addr/PublicIp.java |   5 +
 .../com/cloud/network/rules/StaticNatRuleImpl.java |   5 +
 .../com/cloud/vm/VirtualMachineManagerImpl.java    |   7 +
 .../engine/orchestration/NetworkOrchestrator.java  |   6 +
 .../src/main/java/com/cloud/event/EventVO.java     |   5 +
 .../java/com/cloud/network/UserIpv6AddressVO.java  |   5 +
 .../src/main/java/com/cloud/network/VpnUserVO.java |   5 +
 .../com/cloud/network/as/AutoScalePolicyVO.java    |   5 +
 .../com/cloud/network/as/AutoScaleVmGroupVO.java   |   5 +
 .../com/cloud/network/as/AutoScaleVmProfileVO.java |   5 +
 .../java/com/cloud/network/as/ConditionVO.java     |   5 +
 .../java/com/cloud/network/dao/IPAddressVO.java    |   5 +
 .../com/cloud/network/dao/MonitoringServiceVO.java |   5 +
 .../com/cloud/network/dao/RemoteAccessVpnVO.java   |   5 +
 .../network/dao/Site2SiteVpnConnectionVO.java      |   5 +
 .../cloud/network/dao/Site2SiteVpnGatewayVO.java   |   5 +
 .../com/cloud/network/rules/FirewallRuleVO.java    |   5 +
 .../java/com/cloud/network/vpc/StaticRouteVO.java  |   5 +
 .../java/com/cloud/network/vpc/VpcGatewayVO.java   |   5 +
 .../com/cloud/projects/ProjectInvitationVO.java    |   5 +
 .../main/java/com/cloud/tags/ResourceTagVO.java    |   5 +
 .../com/cloud/upgrade/dao/Upgrade41520to41600.java |  26 ++
 .../src/main/java/com/cloud/user/AccountVO.java    |   5 +
 .../src/main/java/com/cloud/user/SSHKeyPairVO.java |  16 +
 .../src/main/java/com/cloud/vm/ConsoleProxyVO.java |   5 +
 .../src/main/java/com/cloud/vm/DomainRouterVO.java |   4 +
 .../java/com/cloud/vm/SecondaryStorageVmVO.java    |   5 +
 .../src/main/java/com/cloud/vm/UserVmVO.java       |   5 +
 .../src/main/java/com/cloud/vm/VMInstanceVO.java   |   5 +
 .../main/java/com/cloud/vm/dao/NicIpAliasVO.java   |   5 +
 .../java/com/cloud/vm/dao/NicSecondaryIpVO.java    |   5 +
 .../apache/cloudstack/annotation/AnnotationVO.java |  20 +-
 .../cloudstack/annotation/dao/AnnotationDao.java   |   9 +-
 .../annotation/dao/AnnotationDaoImpl.java          | 118 ++++-
 .../org/apache/cloudstack/backup/BackupVO.java     |   5 +
 .../engine/cloud/entity/api/db/VMEntityVO.java     |   5 +
 .../META-INF/db/schema-41520to41600-cleanup.sql    |   1 +
 .../resources/META-INF/db/schema-41520to41600.sql  |  17 +
 .../volume/datastore/PrimaryDataStoreHelper.java   |   5 +
 .../storage/volume/VolumeServiceImpl.java          |   5 +
 .../cluster/KubernetesClusterHelperImpl.java       |  48 ++
 .../cluster/KubernetesClusterManagerImpl.java      |   6 +
 .../KubernetesClusterDestroyWorker.java            |   5 +
 .../api/response/KubernetesClusterResponse.java    |   4 +-
 .../kubernetes-service/module.properties           |   2 +-
 .../spring-kubernetes-service-context.xml          |   4 +
 .../cloudstack/metrics/MetricsServiceImpl.java     |   6 +
 .../main/java/com/cloud/api/ApiResponseHelper.java |  31 +-
 .../cloud/api/query/dao/DataCenterJoinDaoImpl.java |   7 +
 .../api/query/dao/DiskOfferingJoinDaoImpl.java     |  11 +
 .../com/cloud/api/query/dao/DomainJoinDaoImpl.java |  14 +
 .../api/query/dao/DomainRouterJoinDaoImpl.java     |   8 +
 .../com/cloud/api/query/dao/HostJoinDaoImpl.java   |  10 +
 .../cloud/api/query/dao/ImageStoreJoinDaoImpl.java |  14 +
 .../api/query/dao/InstanceGroupJoinDaoImpl.java    |  13 +
 .../api/query/dao/ServiceOfferingJoinDaoImpl.java  |  11 +
 .../api/query/dao/StoragePoolJoinDaoImpl.java      |  14 +
 .../cloud/api/query/dao/TemplateJoinDaoImpl.java   |  29 +-
 .../com/cloud/api/query/dao/UserVmJoinDaoImpl.java |  13 +
 .../com/cloud/api/query/dao/VolumeJoinDaoImpl.java |  12 +
 .../com/cloud/api/query/vo/AsyncJobJoinVO.java     |   5 +
 .../java/com/cloud/api/query/vo/EventJoinVO.java   |   5 +
 .../api/query/vo/ProjectInvitationJoinVO.java      |   5 +
 .../com/cloud/api/query/vo/ResourceTagJoinVO.java  |   5 +
 .../com/cloud/api/query/vo/UserAccountJoinVO.java  |   5 +
 .../configuration/ConfigurationManagerImpl.java    |  12 +
 .../com/cloud/network/IpAddressManagerImpl.java    |   6 +
 .../cloud/network/vpc/PrivateGatewayProfile.java   |   5 +
 .../java/com/cloud/network/vpc/VpcManagerImpl.java |   7 +
 .../cloud/network/vpn/Site2SiteVpnManagerImpl.java |   5 +
 .../com/cloud/resource/ResourceManagerImpl.java    |   9 +
 .../com/cloud/server/ManagementServerImpl.java     |   5 +
 .../java/com/cloud/storage/StorageManagerImpl.java |   5 +
 .../storage/snapshot/SnapshotManagerImpl.java      |   6 +
 .../cloud/template/HypervisorTemplateAdapter.java  |   9 +
 .../java/com/cloud/user/DomainManagerImpl.java     |   5 +
 .../main/java/com/cloud/vm/UserVmManagerImpl.java  |   6 +
 .../cloud/vm/snapshot/VMSnapshotManagerImpl.java   |   5 +
 .../annotation/AnnotationManagerImpl.java          | 518 +++++++++++++++++++--
 .../core/spring-server-core-managers-context.xml   |   4 +-
 .../java/com/cloud/user/DomainManagerImplTest.java |   3 +
 .../networkoffering/CreateNetworkOfferingTest.java |   4 +
 .../src/test/resources/createNetworkOffering.xml   |   1 +
 ...est_host_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 +
 ui/src/config/section/infra/primaryStorages.js     |   4 +
 ui/src/config/section/infra/routers.js             |   4 +
 ui/src/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, 2370 insertions(+), 391 deletions(-)

diff --git a/api/src/main/java/org/apache/cloudstack/acl/ControlledEntity.java b/api/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelper.java
similarity index 69%
copy from api/src/main/java/org/apache/cloudstack/acl/ControlledEntity.java
copy to api/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelper.java
index b8a244f..e445e50 100644
--- a/api/src/main/java/org/apache/cloudstack/acl/ControlledEntity.java
+++ b/api/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelper.java
@@ -14,20 +14,12 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-package org.apache.cloudstack.acl;
+package com.cloud.kubernetes.cluster;
 
-import com.cloud.domain.PartOf;
-import com.cloud.user.OwnedBy;
+import com.cloud.utils.component.Adapter;
+import org.apache.cloudstack.acl.ControlledEntity;
 
-/**
- * ControlledEntity defines an object for which the access from an
- * access must inherit this interface.
- *
- */
-public interface ControlledEntity extends OwnedBy, PartOf {
-    public enum ACLType {
-        Account, Domain
-    }
+public interface KubernetesClusterHelper extends Adapter {
 
-    Class<?> getEntityType();
+    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 737541f..cb4849f 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 aa20c17..4cecc70 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 b8a244f..7bd8037 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 769a753..0aca007 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<AnnotationResponse> 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);
 
+    AnnotationResponse updateAnnotationVisibility(UpdateAnnotationVisibilityCmd cmd);
+
     enum EntityType {
-        HOST("host"), DOMAIN("domain"), VM("vm_instance");
-        private String tableName;
+        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(String tableName) {
-            this.tableName = tableName;
+        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<EntityType> getNotAllowedTypesForNonAdmins(RoleType roleType) {
+            List<EntityType> 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 48386a4..f930800 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 {
             + " \"{<algorithm>}\", not including the double quotes. In this <algorithm> 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/BaseResponseWithTagInformation.java b/api/src/main/java/org/apache/cloudstack/api/BaseResponseWithAnnotations.java
old mode 100755
new mode 100644
similarity index 64%
copy from api/src/main/java/org/apache/cloudstack/api/BaseResponseWithTagInformation.java
copy to api/src/main/java/org/apache/cloudstack/api/BaseResponseWithAnnotations.java
index 3d694a3..f7c0c21
--- a/api/src/main/java/org/apache/cloudstack/api/BaseResponseWithTagInformation.java
+++ b/api/src/main/java/org/apache/cloudstack/api/BaseResponseWithAnnotations.java
@@ -16,25 +16,20 @@
 // under the License.
 package org.apache.cloudstack.api;
 
-import java.util.Set;
-
-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 BaseResponseWithAnnotations extends BaseResponse {
 
-    @SerializedName(ApiConstants.TAGS)
-    @Param(description = "the list of resource tags associated", responseObject = ResourceTagResponse.class)
-    protected Set<ResourceTagResponse> tags;
+    @SerializedName(ApiConstants.HAS_ANNOTATIONS)
+    @Param(description = "true if the entity/resource has annotations")
+    private Boolean hasAnnotation;
 
-    public void addTag(ResourceTagResponse tag) {
-        this.tags.add(tag);
+    public Boolean hasAnnotation() {
+        return hasAnnotation;
     }
 
-    public Set<ResourceTagResponse> getTags(){
-        return this.tags;
+    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 3d694a3..710b9f0 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 07a73ce..eff25d9 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 4657eb9..92e5414 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/AddAnnotationCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/annotation/UpdateAnnotationVisibilityCmd.java
similarity index 51%
copy from api/src/main/java/org/apache/cloudstack/api/command/admin/annotation/AddAnnotationCmd.java
copy to api/src/main/java/org/apache/cloudstack/api/command/admin/annotation/UpdateAnnotationVisibilityCmd.java
index 07a73ce..d152e10 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/UpdateAnnotationVisibilityCmd.java
@@ -21,9 +21,7 @@ import com.cloud.exception.InsufficientCapacityException;
 import com.cloud.exception.NetworkRuleConflictException;
 import com.cloud.exception.ResourceAllocationException;
 import com.cloud.exception.ResourceUnavailableException;
-import com.google.common.base.Preconditions;
 import org.apache.cloudstack.acl.RoleType;
-import org.apache.cloudstack.annotation.AnnotationService;
 import org.apache.cloudstack.api.APICommand;
 import org.apache.cloudstack.api.ApiConstants;
 import org.apache.cloudstack.api.BaseCmd;
@@ -32,51 +30,41 @@ import org.apache.cloudstack.api.ServerApiException;
 import org.apache.cloudstack.api.response.AnnotationResponse;
 import org.apache.cloudstack.context.CallContext;
 
-@APICommand(name = AddAnnotationCmd.APINAME, description = "add an annotation.", responseObject = AnnotationResponse.class,
-        requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.11", authorized = {RoleType.Admin})
-public class AddAnnotationCmd extends BaseCmd {
+@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 = "addAnnotation";
+    public static final String APINAME = "updateAnnotationVisibility";
 
-    @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.ID, type = CommandType.STRING, required = true,
+            description = "the id of the annotation")
+    private String uuid;
 
-    public String getAnnotation() {
-        return annotation;
-    }
+    @Parameter(name = ApiConstants.ADMINS_ONLY, type = CommandType.BOOLEAN, required = true,
+            description = "the annotation is visible for admins only")
+    private Boolean adminsOnly;
 
-    protected void setEntityType(String newType) {
-        entityType = newType;
-    }
-    public AnnotationService.EntityType getEntityType() {
-        return AnnotationService.EntityType.valueOf(entityType);
+    public String getUuid() {
+        return uuid;
     }
 
-    protected void setEntityUuid(String newUuid) {
-        entityUuid = newUuid;
-    }
-    public String getEntityUuid() {
-        return entityUuid;
+    public Boolean getAdminsOnly() {
+        return adminsOnly;
     }
 
     @Override
-    public void execute()
-            throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException,
-            NetworkRuleConflictException {
-        Preconditions.checkNotNull(getEntityUuid(),"I have to have an entity to set an annotation on!");
-        Preconditions.checkState(AnnotationService.EntityType.contains(entityType),(java.lang.String)"'%s' is ot a valid EntityType to put annotations on", entityType);
-        AnnotationResponse annotationResponse = annotationService.addAnnotation(this);
+    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 APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
+        return String.format("%s%s", APINAME.toLowerCase(), BaseCmd.RESPONSE_SUFFIX);
     }
 
     @Override
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 16fc608..40f1938 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 be5a613..3bb1400 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 ea5657c..ca66ee7 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 f3c9e59..6a4e802 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 1e65a41..de7f68d 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 c16971a..f3aed3e 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 c27ee3d..72dab3d 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 410719f..b43eb45 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 5398dd3..004a702 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 818822b..556fe04 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 563d6a9..66f4030 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 526dffa..fcf0870 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 f66a61a..13497d8 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 190181e..2997c50 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 39f4b2f..e1241cc 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 e9bda5d..a3e979e 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 445321b..80b1999 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 27ebf71..038e3a2 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 f494ad2..5a4d69b 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 42f89b1..ea9d8ee 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 88e0e16..babc9bf 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 a03c2d8..89256c2 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 bfc9f9a..ce2b406 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 9c9f059..f91945f 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 e95333f..47f3b0d 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 affd441..cb5e965 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">
     </bean>
 
+    <bean id="kubernetesClusterHelperRegistry"
+          class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry">
+    </bean>
 </beans>
diff --git a/plugins/integrations/kubernetes-service/src/main/resources/META-INF/cloudstack/kubernetes-service/module.properties b/core/src/main/resources/META-INF/cloudstack/kubernetes/module.properties
similarity index 95%
copy from plugins/integrations/kubernetes-service/src/main/resources/META-INF/cloudstack/kubernetes-service/module.properties
copy to core/src/main/resources/META-INF/cloudstack/kubernetes/module.properties
index e6f02da..ea954a9 100644
--- a/plugins/integrations/kubernetes-service/src/main/resources/META-INF/cloudstack/kubernetes-service/module.properties
+++ b/core/src/main/resources/META-INF/cloudstack/kubernetes/module.properties
@@ -1,3 +1,4 @@
+#
 # 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
@@ -14,5 +15,7 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-name=kubernetes-service
-parent=compute
+#
+
+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 0000000..df1a4b5
--- /dev/null
+++ b/core/src/main/resources/META-INF/cloudstack/kubernetes/spring-core-lifecycle-kubernetes-context-inheritable.xml
@@ -0,0 +1,32 @@
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+                      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
+>
+
+    <bean class="org.apache.cloudstack.spring.lifecycle.registry.RegistryLifecycle">
+        <property name="registry" ref="kubernetesClusterHelperRegistry" />
+        <property name="typeClass" value="com.cloud.kubernetes.cluster.KubernetesClusterHelper" />
+    </bean>
+
+</beans>
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 8e9d799..d1153a2 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
@@ -256,6 +256,11 @@ public class PublicIp implements PublicIpAddress {
     }
 
     @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 96950a1..4d8270c 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 4c498d1..69c5cc6 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<Command> 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 3c5dd41..8fdf30b 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<NetworkGuru> 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 9be37dd..e5cf2a2 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 e97961a..6568817 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 9e403e4..aadbd3f 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 1842533..6379f65 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 d32e7f8..ea8eaf6 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 69e4c81..eb7e34a 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 5035f73..5a512ab 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 686f497..7c4d56b 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
@@ -367,6 +367,11 @@ public class IPAddressVO implements IpAddress {
     }
 
     @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 01fba00..97e8a04 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 366a6cc..95e3693 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 04a9d1c..b032966 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 184162a..703c78c 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 282fa74..07b25e7 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 2c2d53e..586f9a4 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 9919ba3..72f6a89 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 4f794a8..36e772e 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 cc29d8e..1db9a61 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 eea3a58..5c5523c 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 36b2508..2a285c2 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 fd7173e..00feda5 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 7294992..8f47ce0 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 2a7aa49..8bd973a 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 9ffc918..37a312f 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 f02380a..311a6c5 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 afc7134..886933a 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<State, Vi
         return VirtualMachine.class;
     }
 
+    @Override
+    public String getName() {
+        return instanceName;
+    }
+
     public VirtualMachine.PowerState getPowerState() {
         return powerState;
     }
diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/NicIpAliasVO.java b/engine/schema/src/main/java/com/cloud/vm/dao/NicIpAliasVO.java
index 7e81495..05d73c8 100644
--- a/engine/schema/src/main/java/com/cloud/vm/dao/NicIpAliasVO.java
+++ b/engine/schema/src/main/java/com/cloud/vm/dao/NicIpAliasVO.java
@@ -236,4 +236,9 @@ public class NicIpAliasVO implements NicIpAlias  {
     public Class<?> 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 d60ac92..0934340 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 982dd6d..0d34bc0 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 6bf8484..7370791 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<AnnotationVO, Long> {
-    public List<AnnotationVO> findByEntityType(String entityType);
-    public List<AnnotationVO> findByEntity(String entityType, String entityUuid);
+    List<AnnotationVO> listByEntityType(String entityType, String userUuid, boolean isCallerAdmin, String annotationFilter, String callingUserUuid, String keyword);
+    List<AnnotationVO> listByEntity(String entityType, String entityUuid, String userUuid, boolean isCallerAdmin, String annotationFilter, String callingUserUuid, String keyword);
+    List<AnnotationVO> 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 e2fcc90..0bb47a5 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<AnnotationVO, Long> implements AnnotationDao {
-    private final SearchBuilder<AnnotationVO> AnnotationSearchByType;
-    private final SearchBuilder<AnnotationVO> AnnotationSearchByTypeAndUuid;
+    private final SearchBuilder<AnnotationVO> 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();
+    }
+
+    private List<AnnotationVO> listAnnotationsOrderedByCreatedDate(SearchCriteria<AnnotationVO> sc) {
+        Filter filter = new Filter(AnnotationVO.class, "created", false, null, null);
+        return listBy(sc, filter);
+    }
 
+    @Override
+    public List<AnnotationVO> listByEntityType(String entityType, String userUuid, boolean isCallerAdmin,
+                                                         String annotationFilter, String callingUserUuid, String keyword) {
+        SearchCriteria<AnnotationVO> sc = AnnotationSearchBuilder.create();
+        sc.addAnd("entityType", SearchCriteria.Op.EQ, entityType);
+        if (StringUtils.isNotBlank(userUuid)) {
+            sc.addAnd("userUuid", SearchCriteria.Op.EQ, userUuid);
+        }
+        if (!isCallerAdmin) {
+            List<EntityType> 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<AnnotationVO> findByEntityType(String entityType) {
-        SearchCriteria<AnnotationVO> sc = createSearchCriteria();
+    @Override
+    public List<AnnotationVO> listByEntity(String entityType, String entityUuid, String userUuid, boolean isCallerAdmin,
+                                           String annotationFilter, String callingUserUuid, String keyword) {
+        SearchCriteria<AnnotationVO> sc = AnnotationSearchBuilder.create();
         sc.addAnd("entityType", SearchCriteria.Op.EQ, entityType);
-        return listBy(sc);
+        sc.addAnd("entityUuid", SearchCriteria.Op.EQ, entityUuid);
+        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<AnnotationVO> listAllAnnotations(String userUuid, RoleType roleType, String annotationFilter, String keyword) {
+        SearchCriteria<AnnotationVO> 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<EntityType> notAllowedTypes = EntityType.getNotAllowedTypesForNonAdmins(roleType);
+            sc.setParameters("entityTypeNotIn", notAllowedTypes.toArray());
+        }
+        return listAnnotationsOrderedByCreatedDate(sc);
+    }
+
+    @Override
+    public boolean hasAnnotations(String entityUuid, String entityType, boolean isCallerAdmin) {
+        List<AnnotationVO> annotations = listByEntity(entityType, entityUuid, null,
+                isCallerAdmin, "all", null, null);
+        return CollectionUtils.isNotEmpty(annotations);
     }
 
-    @Override public List<AnnotationVO> findByEntity(String entityType, String entityUuid) {
-        SearchCriteria<AnnotationVO> sc = createSearchCriteria();
+    @Override
+    public boolean removeByEntityType(String entityType, String entityUuid) {
+        SearchCriteria<AnnotationVO> sc = AnnotationSearchBuilder.create();
         sc.addAnd("entityType", SearchCriteria.Op.EQ, entityType);
         sc.addAnd("entityUuid", SearchCriteria.Op.EQ, entityUuid);
-        return listBy(sc, null);
+        return remove(sc) > 0;
+    }
+
+    @Override
+    public AnnotationVO findOneByEntityId(String entityUuid) {
+        SearchCriteria<AnnotationVO> 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 e56f55c..dc47fcb 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 67af516..286f05d 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
@@ -560,6 +560,11 @@ public class VMEntityVO implements VirtualMachine, FiniteStateObject<State, Virt
     }
 
     @Override
+    public String getName() {
+        return instanceName;
+    }
+
+    @Override
     public boolean isDisplay() {
         return display;
     }
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41520to41600-cleanup.sql b/engine/schema/src/main/resources/META-INF/db/schema-41520to41600-cleanup.sql
index e69d34f..32b4766 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-41520to41600-cleanup.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-41520to41600-cleanup.sql
@@ -19,3 +19,4 @@
 -- Schema upgrade cleanup from 4.15.2.0 to 4.16.0.0
 --;
 
+ALTER TABLE `cloud`.`ssh_keypairs` MODIFY COLUMN `uuid` varchar(40) NOT NULL UNIQUE;
\ No newline at end of file
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41520to41600.sql b/engine/schema/src/main/resources/META-INF/db/schema-41520to41600.sql
index 9a30ed5..0493c07 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-41520to41600.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-41520to41600.sql
@@ -697,6 +697,22 @@ CREATE VIEW `cloud`.`host_view` AS
     GROUP BY
         `host`.`id`;
 
+ALTER TABLE `cloud`.`annotations` ADD COLUMN `admins_only` tinyint(1) unsigned NOT NULL DEFAULT 1;
+
+-- Allow annotations for resource admins, domain admins and users
+INSERT INTO `cloud`.`role_permissions` (uuid, role_id, rule, permission) VALUES (UUID(), 2, 'listAnnotations', 'ALLOW');
+INSERT INTO `cloud`.`role_permissions` (uuid, role_id, rule, permission) VALUES (UUID(), 2, 'addAnnotation', 'ALLOW');
+INSERT INTO `cloud`.`role_permissions` (uuid, role_id, rule, permission) VALUES (UUID(), 2, 'removeAnnotation', 'ALLOW');
+INSERT INTO `cloud`.`role_permissions` (uuid, role_id, rule, permission) VALUES (UUID(), 3, 'listAnnotations', 'ALLOW');
+INSERT INTO `cloud`.`role_permissions` (uuid, role_id, rule, permission) VALUES (UUID(), 3, 'addAnnotation', 'ALLOW');
+INSERT INTO `cloud`.`role_permissions` (uuid, role_id, rule, permission) VALUES (UUID(), 3, 'removeAnnotation', 'ALLOW');
+INSERT INTO `cloud`.`role_permissions` (uuid, role_id, rule, permission) VALUES (UUID(), 4, 'listAnnotations', 'ALLOW');
+INSERT INTO `cloud`.`role_permissions` (uuid, role_id, rule, permission) VALUES (UUID(), 4, 'addAnnotation', 'ALLOW');
+INSERT INTO `cloud`.`role_permissions` (uuid, role_id, rule, permission) VALUES (UUID(), 4, 'removeAnnotation', 'ALLOW');
+
+-- Add uuid for ssh keypairs
+ALTER TABLE `cloud`.`ssh_keypairs` ADD COLUMN `uuid` varchar(40) AFTER `id`;
+
 -- PR#4699 Drop the procedure `ADD_GUEST_OS_AND_HYPERVISOR_MAPPING` if it already exist.
 DROP PROCEDURE IF EXISTS `cloud`.`ADD_GUEST_OS_AND_HYPERVISOR_MAPPING`;
 
@@ -757,3 +773,4 @@ UPDATE cloud.user_vm_deploy_as_is_details SET value='' WHERE value IS NULL;
 ALTER TABLE cloud.user_vm_deploy_as_is_details MODIFY value text NOT NULL;
 UPDATE cloud.user_vm_details SET value='' WHERE value IS NULL;
 ALTER TABLE cloud.user_vm_details MODIFY value varchar(5120) NOT NULL;
+
diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/volume/datastore/PrimaryDataStoreHelper.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/volume/datastore/PrimaryDataStoreHelper.java
index 08a3a39..c3379ad 100644
--- a/engine/storage/src/main/java/org/apache/cloudstack/storage/volume/datastore/PrimaryDataStoreHelper.java
+++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/volume/datastore/PrimaryDataStoreHelper.java
@@ -26,6 +26,8 @@ import java.util.Map;
 
 import javax.inject.Inject;
 
+import org.apache.cloudstack.annotation.AnnotationService;
+import org.apache.cloudstack.annotation.dao.AnnotationDao;
 import org.apache.log4j.Logger;
 import org.springframework.stereotype.Component;
 
@@ -66,6 +68,8 @@ public class PrimaryDataStoreHelper {
     protected CapacityDao _capacityDao;
     @Inject
     protected StoragePoolHostDao storagePoolHostDao;
+    @Inject
+    private AnnotationDao annotationDao;
 
     public DataStore createPrimaryDataStore(PrimaryDataStoreParameters params) {
         if(params == null)
@@ -251,6 +255,7 @@ public class PrimaryDataStoreHelper {
         this.dataStoreDao.update(poolVO.getId(), poolVO);
         dataStoreDao.remove(poolVO.getId());
         dataStoreDao.deletePoolTags(poolVO.getId());
+        annotationDao.removeByEntityType(AnnotationService.EntityType.PRIMARY_STORAGE.name(), poolVO.getUuid());
         deletePoolStats(poolVO.getId());
         // Delete op_host_capacity entries
         this._capacityDao.removeBy(Capacity.CAPACITY_TYPE_STORAGE_ALLOCATED, null, null, null, poolVO.getId());
diff --git a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java
index 348c142..2fb5618 100644
--- a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java
+++ b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java
@@ -30,6 +30,8 @@ import javax.inject.Inject;
 
 import com.cloud.storage.VMTemplateVO;
 import com.cloud.storage.dao.VMTemplateDao;
+import org.apache.cloudstack.annotation.AnnotationService;
+import org.apache.cloudstack.annotation.dao.AnnotationDao;
 import org.apache.cloudstack.engine.cloud.entity.api.VolumeEntity;
 import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
 import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
@@ -189,6 +191,8 @@ public class VolumeServiceImpl implements VolumeService {
     private VolumeOrchestrationService _volumeMgr;
     @Inject
     private StorageManager _storageMgr;
+    @Inject
+    private AnnotationDao annotationDao;
 
     private final static String SNAPSHOT_ID = "SNAPSHOT_ID";
 
@@ -1540,6 +1544,7 @@ public class VolumeServiceImpl implements VolumeService {
         VolumeInfo vol = volFactory.getVolume(volumeId);
         vol.stateTransit(Volume.Event.DestroyRequested);
         snapshotMgr.deletePoliciesForVolume(volumeId);
+        annotationDao.removeByEntityType(AnnotationService.EntityType.VOLUME.name(), vol.getUuid());
 
         vol.stateTransit(Volume.Event.OperationSucceeded);
 
diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelperImpl.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelperImpl.java
new file mode 100644
index 0000000..0ef916a
--- /dev/null
+++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelperImpl.java
@@ -0,0 +1,48 @@
+// 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.kubernetes.cluster.dao.KubernetesClusterDao;
+import com.cloud.utils.component.AdapterBase;
+import org.apache.cloudstack.acl.ControlledEntity;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.framework.config.Configurable;
+import org.springframework.stereotype.Component;
+
+import javax.inject.Inject;
+
+@Component
+public class KubernetesClusterHelperImpl extends AdapterBase implements KubernetesClusterHelper, Configurable {
+
+    @Inject
+    private KubernetesClusterDao kubernetesClusterDao;
+
+    @Override
+    public ControlledEntity findByUuid(String uuid) {
+        return kubernetesClusterDao.findByUuid(uuid);
+    }
+
+    @Override
+    public String getConfigComponentName() {
+        return KubernetesClusterHelper.class.getSimpleName();
+    }
+
+    @Override
+    public ConfigKey<?>[] 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 7e52d98..01ac63f 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 01ed9aa..47acf31 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<KubernetesClusterVmMapVO> 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 cbfa6ac..8324771 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 e6f02da..b149a41 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 12f2a46..cf9faee 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 @@
     <bean id="kubernetesClusterVmMapDaoImpl" class="com.cloud.kubernetes.cluster.dao.KubernetesClusterVmMapDaoImpl" />
     <bean id="kubernetesClusterManagerImpl" class="com.cloud.kubernetes.cluster.KubernetesClusterManagerImpl" />
 
+    <bean id="kubernetesClusterHelper" class="com.cloud.kubernetes.cluster.KubernetesClusterHelperImpl" >
+        <property name="name" value="KubernetesClusterHelper" />
+    </bean>
+
 </beans>
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 1ce58c5..cb16501 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 5dc6412..09057e6 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<CapacityResponse>(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<CapacityResponse>(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<NicProfile> 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 cfe2e31..c777e65 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<DataCenterJoinVO, Long
     private SearchBuilder<DataCenterJoinVO> dofIdSearch;
     @Inject
     public AccountManager _accountMgr;
+    @Inject
+    private AnnotationDao annotationDao;
 
     protected DataCenterJoinDaoImpl() {
 
@@ -103,6 +108,8 @@ public class DataCenterJoinDaoImpl extends GenericDaoBase<DataCenterJoinVO, Long
         }
 
         zoneResponse.setResourceDetails(ApiDBUtils.getResourceDetails(dataCenter.getId(), ResourceObjectType.Zone));
+        zoneResponse.setHasAnnotation(annotationDao.hasAnnotations(dataCenter.getUuid(), AnnotationService.EntityType.ZONE.name(),
+                _accountMgr.isRootAdmin(CallContext.current().getCallingAccount().getId())));
 
         zoneResponse.setObjectName("zone");
         return zoneResponse;
diff --git a/server/src/main/java/com/cloud/api/query/dao/DiskOfferingJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/DiskOfferingJoinDaoImpl.java
index a62fbcd..685f301 100644
--- a/server/src/main/java/com/cloud/api/query/dao/DiskOfferingJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/DiskOfferingJoinDaoImpl.java
@@ -23,8 +23,12 @@ import com.cloud.api.ApiDBUtils;
 import com.cloud.dc.VsphereStoragePolicyVO;
 import com.cloud.dc.dao.VsphereStoragePolicyDao;
 import com.cloud.server.ResourceTag;
+import com.cloud.user.AccountManager;
+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.DiskOfferingResponse;
+import org.apache.cloudstack.context.CallContext;
 import org.apache.log4j.Logger;
 import org.springframework.stereotype.Component;
 
@@ -44,6 +48,10 @@ public class DiskOfferingJoinDaoImpl extends GenericDaoBase<DiskOfferingJoinVO,
 
     @Inject
     VsphereStoragePolicyDao _vsphereStoragePolicyDao;
+    @Inject
+    private AnnotationDao annotationDao;
+    @Inject
+    private AccountManager accountManager;
 
     private final SearchBuilder<DiskOfferingJoinVO> dofIdSearch;
     private final Attribute _typeAttr;
@@ -100,6 +108,9 @@ public class DiskOfferingJoinDaoImpl extends GenericDaoBase<DiskOfferingJoinVO,
         diskOfferingResponse.setZoneId(offering.getZoneUuid());
         diskOfferingResponse.setZone(offering.getZoneName());
 
+        diskOfferingResponse.setHasAnnotation(annotationDao.hasAnnotations(offering.getUuid(), AnnotationService.EntityType.DISK_OFFERING.name(),
+                accountManager.isRootAdmin(CallContext.current().getCallingAccount().getId())));
+
         diskOfferingResponse.setTags(offering.getTags());
         diskOfferingResponse.setCustomized(offering.isCustomized());
         diskOfferingResponse.setCustomizedIops(offering.isCustomizedIops());
diff --git a/server/src/main/java/com/cloud/api/query/dao/DomainJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/DomainJoinDaoImpl.java
index d65afd2..ff0736e 100644
--- a/server/src/main/java/com/cloud/api/query/dao/DomainJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/DomainJoinDaoImpl.java
@@ -20,10 +20,14 @@ import java.util.EnumSet;
 import java.util.List;
 
 
+import com.cloud.user.AccountManager;
+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.ResponseObject.ResponseView;
 import org.apache.cloudstack.api.response.DomainResponse;
 import org.apache.cloudstack.api.response.ResourceLimitAndCountResponse;
+import org.apache.cloudstack.context.CallContext;
 import org.apache.log4j.Logger;
 import org.springframework.stereotype.Component;
 
@@ -35,12 +39,19 @@ import com.cloud.utils.db.GenericDaoBase;
 import com.cloud.utils.db.SearchBuilder;
 import com.cloud.utils.db.SearchCriteria;
 
+import javax.inject.Inject;
+
 @Component
 public class DomainJoinDaoImpl extends GenericDaoBase<DomainJoinVO, Long> implements DomainJoinDao {
     public static final Logger s_logger = Logger.getLogger(DomainJoinDaoImpl.class);
 
     private SearchBuilder<DomainJoinVO> domainIdSearch;
 
+    @Inject
+    private AnnotationDao annotationDao;
+    @Inject
+    private AccountManager accountManager;
+
     protected DomainJoinDaoImpl() {
 
         domainIdSearch = createSearchBuilder();
@@ -74,6 +85,9 @@ public class DomainJoinDaoImpl extends GenericDaoBase<DomainJoinVO, Long> 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 413ff2a..96c129a 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<DomainRouterJoinVO,
     private ConfigurationDao _configDao;
     @Inject
     public AccountManager _accountMgr;
+    @Inject
+    private AnnotationDao annotationDao;
 
     private final SearchBuilder<DomainRouterJoinVO> vrSearch;
 
@@ -97,6 +102,9 @@ public class DomainRouterJoinDaoImpl extends GenericDaoBase<DomainRouterJoinVO,
             routerResponse.setRequiresUpgrade(true);
         }
 
+        routerResponse.setHasAnnotation(annotationDao.hasAnnotations(router.getUuid(), AnnotationService.EntityType.VR.name(),
+                _accountMgr.isRootAdmin(CallContext.current().getCallingAccount().getId())));
+
         if (caller.getType() == Account.ACCOUNT_TYPE_RESOURCE_DOMAIN_ADMIN
                 || _accountMgr.isRootAdmin(caller.getId())) {
             if (router.getHostId() != null) {
diff --git a/server/src/main/java/com/cloud/api/query/dao/HostJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/HostJoinDaoImpl.java
index af392a2..7571ffb 100644
--- a/server/src/main/java/com/cloud/api/query/dao/HostJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/HostJoinDaoImpl.java
@@ -27,11 +27,15 @@ import java.util.Set;
 
 import javax.inject.Inject;
 
+import com.cloud.user.AccountManager;
+import org.apache.cloudstack.annotation.AnnotationService;
+import org.apache.cloudstack.annotation.dao.AnnotationDao;
 import org.apache.cloudstack.api.ApiConstants.HostDetails;
 import org.apache.cloudstack.api.response.GpuResponse;
 import org.apache.cloudstack.api.response.HostForMigrationResponse;
 import org.apache.cloudstack.api.response.HostResponse;
 import org.apache.cloudstack.api.response.VgpuResponse;
+import org.apache.cloudstack.context.CallContext;
 import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
 import org.apache.cloudstack.ha.HAResource;
 import org.apache.cloudstack.ha.dao.HAConfigDao;
@@ -69,6 +73,10 @@ public class HostJoinDaoImpl extends GenericDaoBase<HostJoinVO, Long> implements
     private OutOfBandManagementDao outOfBandManagementDao;
     @Inject
     private ManagementServerHostDao managementServerHostDao;
+    @Inject
+    private AnnotationDao annotationDao;
+    @Inject
+    private AccountManager accountManager;
 
     private final SearchBuilder<HostJoinVO> hostSearch;
 
@@ -266,6 +274,8 @@ public class HostJoinDaoImpl extends GenericDaoBase<HostJoinVO, Long> 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 b91398d..9c20d18 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<ImageStoreJoinVO, Long
 
     @Inject
     private ConfigurationDao _configDao;
+    @Inject
+    private AnnotationDao annotationDao;
+    @Inject
+    private AccountManager accountManager;
 
     private final SearchBuilder<ImageStoreJoinVO> dsSearch;
 
@@ -83,6 +91,8 @@ public class ImageStoreJoinDaoImpl extends GenericDaoBase<ImageStoreJoinVO, Long
             osResponse.setDiskSizeTotal(secStorageStats.getCapacityBytes());
             osResponse.setDiskSizeUsed(secStorageStats.getByteUsed());
         }
+        osResponse.setHasAnnotation(annotationDao.hasAnnotations(ids.getUuid(), AnnotationService.EntityType.SECONDARY_STORAGE.name(),
+                accountManager.isRootAdmin(CallContext.current().getCallingAccount().getId())));
 
         osResponse.setObjectName("imagestore");
         return osResponse;
@@ -90,6 +100,10 @@ public class ImageStoreJoinDaoImpl extends GenericDaoBase<ImageStoreJoinVO, Long
 
     @Override
     public ImageStoreResponse setImageStoreResponse(ImageStoreResponse response, ImageStoreJoinVO ids) {
+        if (response.hasAnnotation() == null) {
+            response.setHasAnnotation(annotationDao.hasAnnotations(ids.getUuid(), AnnotationService.EntityType.SECONDARY_STORAGE.name(),
+                    accountManager.isRootAdmin(CallContext.current().getCallingAccount().getId())));
+        }
         return response;
     }
 
diff --git a/server/src/main/java/com/cloud/api/query/dao/InstanceGroupJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/InstanceGroupJoinDaoImpl.java
index d37ece0..61e73d4 100644
--- a/server/src/main/java/com/cloud/api/query/dao/InstanceGroupJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/InstanceGroupJoinDaoImpl.java
@@ -19,6 +19,10 @@ package com.cloud.api.query.dao;
 import java.util.List;
 
 
+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;
 
@@ -31,12 +35,19 @@ import com.cloud.utils.db.SearchBuilder;
 import com.cloud.utils.db.SearchCriteria;
 import com.cloud.vm.InstanceGroup;
 
+import javax.inject.Inject;
+
 @Component
 public class InstanceGroupJoinDaoImpl extends GenericDaoBase<InstanceGroupJoinVO, Long> implements InstanceGroupJoinDao {
     public static final Logger s_logger = Logger.getLogger(InstanceGroupJoinDaoImpl.class);
 
     private SearchBuilder<InstanceGroupJoinVO> vrIdSearch;
 
+    @Inject
+    private AnnotationDao annotationDao;
+    @Inject
+    private AccountManager accountManager;
+
     protected InstanceGroupJoinDaoImpl() {
 
         vrIdSearch = createSearchBuilder();
@@ -52,6 +63,8 @@ public class InstanceGroupJoinDaoImpl extends GenericDaoBase<InstanceGroupJoinVO
         groupResponse.setId(group.getUuid());
         groupResponse.setName(group.getName());
         groupResponse.setCreated(group.getCreated());
+        groupResponse.setHasAnnotation(annotationDao.hasAnnotations(group.getUuid(), AnnotationService.EntityType.INSTANCE_GROUP.name(),
+                accountManager.isRootAdmin(CallContext.current().getCallingAccount().getId())));
 
         ApiResponseHelper.populateOwner(groupResponse, group);
 
diff --git a/server/src/main/java/com/cloud/api/query/dao/ServiceOfferingJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/ServiceOfferingJoinDaoImpl.java
index ff24b22..3381848 100644
--- a/server/src/main/java/com/cloud/api/query/dao/ServiceOfferingJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/ServiceOfferingJoinDaoImpl.java
@@ -21,8 +21,12 @@ import java.util.Map;
 
 import com.cloud.dc.VsphereStoragePolicyVO;
 import com.cloud.dc.dao.VsphereStoragePolicyDao;
+import com.cloud.user.AccountManager;
+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.ServiceOfferingResponse;
+import org.apache.cloudstack.context.CallContext;
 import org.apache.log4j.Logger;
 import org.springframework.stereotype.Component;
 
@@ -42,6 +46,10 @@ public class ServiceOfferingJoinDaoImpl extends GenericDaoBase<ServiceOfferingJo
 
     @Inject
     VsphereStoragePolicyDao _vsphereStoragePolicyDao;
+    @Inject
+    private AnnotationDao annotationDao;
+    @Inject
+    private AccountManager accountManager;
 
     private SearchBuilder<ServiceOfferingJoinVO> sofIdSearch;
 
@@ -134,6 +142,9 @@ public class ServiceOfferingJoinDaoImpl extends GenericDaoBase<ServiceOfferingJo
         long rootDiskSizeInGb = (long) offering.getRootDiskSize() / GB_TO_BYTES;
         offeringResponse.setRootDiskSize(rootDiskSizeInGb);
 
+        offeringResponse.setHasAnnotation(annotationDao.hasAnnotations(offering.getUuid(), AnnotationService.EntityType.SERVICE_OFFERING.name(),
+                accountManager.isRootAdmin(CallContext.current().getCallingAccount().getId())));
+
         return offeringResponse;
     }
 
diff --git a/server/src/main/java/com/cloud/api/query/dao/StoragePoolJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/StoragePoolJoinDaoImpl.java
index d2fe6a5..28ba1f6 100644
--- a/server/src/main/java/com/cloud/api/query/dao/StoragePoolJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/StoragePoolJoinDaoImpl.java
@@ -23,11 +23,15 @@ import com.cloud.storage.DataStoreRole;
 import com.cloud.storage.Storage;
 import com.cloud.storage.StoragePool;
 import com.cloud.storage.StorageStats;
+import com.cloud.user.AccountManager;
 import com.cloud.utils.StringUtils;
 import com.cloud.utils.db.GenericDaoBase;
 import com.cloud.utils.db.SearchBuilder;
 import com.cloud.utils.db.SearchCriteria;
+import org.apache.cloudstack.annotation.AnnotationService;
+import org.apache.cloudstack.annotation.dao.AnnotationDao;
 import org.apache.cloudstack.api.response.StoragePoolResponse;
+import org.apache.cloudstack.context.CallContext;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
 import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
@@ -55,6 +59,10 @@ public class StoragePoolJoinDaoImpl extends GenericDaoBase<StoragePoolJoinVO, Lo
 
     @Inject
     protected PrimaryDataStoreDao storagePoolDao;
+    @Inject
+    private AnnotationDao annotationDao;
+    @Inject
+    private AccountManager accountManager;
 
     @Inject
     private StoragePoolDetailsDao storagePoolDetailsDao;
@@ -144,6 +152,8 @@ public class StoragePoolJoinDaoImpl extends GenericDaoBase<StoragePoolJoinVO, Lo
             poolResponse.setJobId(pool.getJobUuid());
             poolResponse.setJobStatus(pool.getJobStatus());
         }
+        poolResponse.setHasAnnotation(annotationDao.hasAnnotations(pool.getUuid(), AnnotationService.EntityType.PRIMARY_STORAGE.name(),
+                accountManager.isRootAdmin(CallContext.current().getCallingAccount().getId())));
 
         poolResponse.setObjectName("storagepool");
         return poolResponse;
@@ -159,6 +169,10 @@ public class StoragePoolJoinDaoImpl extends GenericDaoBase<StoragePoolJoinVO, Lo
                 response.setTags(tag);
             }
         }
+        if (response.hasAnnotation() == null) {
+            response.setHasAnnotation(annotationDao.hasAnnotations(sp.getUuid(), AnnotationService.EntityType.PRIMARY_STORAGE.name(),
+                    accountManager.isRootAdmin(CallContext.current().getCallingAccount().getId())));
+        }
         return response;
     }
 
diff --git a/server/src/main/java/com/cloud/api/query/dao/TemplateJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/TemplateJoinDaoImpl.java
index 4728d4d..15d02ce 100644
--- a/server/src/main/java/com/cloud/api/query/dao/TemplateJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/TemplateJoinDaoImpl.java
@@ -26,7 +26,17 @@ import java.util.Set;
 
 import javax.inject.Inject;
 
+import com.cloud.deployasis.DeployAsIsConstants;
+import com.cloud.deployasis.TemplateDeployAsIsDetailVO;
+import com.cloud.deployasis.dao.TemplateDeployAsIsDetailsDao;
+import org.apache.cloudstack.annotation.AnnotationService;
+import org.apache.cloudstack.annotation.dao.AnnotationDao;
 import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.storage.datastore.db.ImageStoreVO;
+import org.apache.cloudstack.utils.security.DigestHelper;
+import org.apache.log4j.Logger;
+import org.springframework.stereotype.Component;
+
 import org.apache.cloudstack.api.ResponseObject.ResponseView;
 import org.apache.cloudstack.api.response.ChildTemplateResponse;
 import org.apache.cloudstack.api.response.TemplateResponse;
@@ -36,20 +46,13 @@ import org.apache.cloudstack.engine.subsystem.api.storage.TemplateState;
 import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
 import org.apache.cloudstack.query.QueryService;
 import org.apache.cloudstack.storage.datastore.db.ImageStoreDao;
-import org.apache.cloudstack.storage.datastore.db.ImageStoreVO;
 import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
-import org.apache.cloudstack.utils.security.DigestHelper;
-import org.apache.log4j.Logger;
-import org.springframework.stereotype.Component;
 
 import com.cloud.api.ApiDBUtils;
 import com.cloud.api.ApiResponseHelper;
 import com.cloud.api.query.vo.ResourceTagJoinVO;
 import com.cloud.api.query.vo.TemplateJoinVO;
-import com.cloud.deployasis.DeployAsIsConstants;
-import com.cloud.deployasis.TemplateDeployAsIsDetailVO;
-import com.cloud.deployasis.dao.TemplateDeployAsIsDetailsDao;
 import com.cloud.hypervisor.Hypervisor.HypervisorType;
 import com.cloud.storage.Storage;
 import com.cloud.storage.Storage.TemplateType;
@@ -85,6 +88,8 @@ public class TemplateJoinDaoImpl extends GenericDaoBaseWithTagInformation<Templa
     private VMTemplateDetailsDao _templateDetailsDao;
     @Inject
     private TemplateDeployAsIsDetailsDao templateDeployAsIsDetailsDao;
+    @Inject
+    private AnnotationDao annotationDao;
 
     private final SearchBuilder<TemplateJoinVO> tmpltIdPairSearch;
 
@@ -262,6 +267,9 @@ public class TemplateJoinDaoImpl extends GenericDaoBaseWithTagInformation<Templa
             addTagInformation(template, templateResponse);
         }
 
+        templateResponse.setHasAnnotation(annotationDao.hasAnnotations(template.getUuid(), AnnotationService.EntityType.TEMPLATE.name(),
+                _accountService.isRootAdmin(CallContext.current().getCallingAccount().getId())));
+
         templateResponse.setDirectDownload(template.isDirectDownload());
         templateResponse.setDeployAsIs(template.isDeployAsIs());
         templateResponse.setRequiresHvm(template.isRequiresHvm());
@@ -359,6 +367,11 @@ public class TemplateJoinDaoImpl extends GenericDaoBaseWithTagInformation<Templa
             addTagInformation(template, templateResponse);
         }
 
+        if (templateResponse.hasAnnotation() == null) {
+            templateResponse.setHasAnnotation(annotationDao.hasAnnotations(template.getUuid(), AnnotationService.EntityType.TEMPLATE.name(),
+                    _accountService.isRootAdmin(CallContext.current().getCallingAccount().getId())));
+        }
+
         return templateResponse;
     }
 
@@ -446,6 +459,8 @@ public class TemplateJoinDaoImpl extends GenericDaoBaseWithTagInformation<Templa
                 isoResponse.addTag(ApiDBUtils.newResourceTagResponse(vtag, false));
             }
         }
+        isoResponse.setHasAnnotation(annotationDao.hasAnnotations(iso.getUuid(), AnnotationService.EntityType.ISO.name(),
+                _accountService.isRootAdmin(CallContext.current().getCallingAccount().getId())));
 
         isoResponse.setDirectDownload(iso.isDirectDownload());
 
diff --git a/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java
index ff4511b..7a86762 100644
--- a/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java
@@ -29,6 +29,8 @@ import java.util.stream.Collectors;
 import javax.inject.Inject;
 
 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;
 import org.apache.cloudstack.api.ApiConstants.VMDetails;
 import org.apache.cloudstack.api.ResponseObject.ResponseView;
@@ -37,6 +39,7 @@ import org.apache.cloudstack.api.response.NicResponse;
 import org.apache.cloudstack.api.response.NicSecondaryIpResponse;
 import org.apache.cloudstack.api.response.SecurityGroupResponse;
 import org.apache.cloudstack.api.response.UserVmResponse;
+import org.apache.cloudstack.context.CallContext;
 import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
 import org.apache.cloudstack.query.QueryService;
 import org.apache.log4j.Logger;
@@ -82,6 +85,8 @@ public class UserVmJoinDaoImpl extends GenericDaoBaseWithTagInformation<UserVmJo
     @Inject
     private NicExtraDhcpOptionDao _nicExtraDhcpOptionDao;
     @Inject
+    private AnnotationDao annotationDao;
+    @Inject
     UserStatisticsDao userStatsDao;
 
     private final SearchBuilder<UserVmJoinVO> VmDetailSearch;
@@ -312,6 +317,9 @@ public class UserVmJoinDaoImpl extends GenericDaoBaseWithTagInformation<UserVmJo
             addTagInformation(userVm, userVmResponse);
         }
 
+        userVmResponse.setHasAnnotation(annotationDao.hasAnnotations(userVm.getUuid(),
+                AnnotationService.EntityType.VM.name(), _accountMgr.isRootAdmin(caller.getId())));
+
         if (details.contains(VMDetails.all) || details.contains(VMDetails.affgrp)) {
             Long affinityGroupId = userVm.getAffinityGroupId();
             if (affinityGroupId != null && affinityGroupId.longValue() != 0) {
@@ -489,6 +497,11 @@ public class UserVmJoinDaoImpl extends GenericDaoBaseWithTagInformation<UserVmJo
             addTagInformation(uvo, userVmData);
         }
 
+        if (userVmData.hasAnnotation() == null) {
+            userVmData.setHasAnnotation(annotationDao.hasAnnotations(uvo.getUuid(),
+                    AnnotationService.EntityType.VM.name(), _accountMgr.isRootAdmin(CallContext.current().getCallingAccount().getId())));
+        }
+
         Long affinityGroupId = uvo.getAffinityGroupId();
         if (affinityGroupId != null && affinityGroupId.longValue() != 0) {
             AffinityGroupResponse resp = new AffinityGroupResponse();
diff --git a/server/src/main/java/com/cloud/api/query/dao/VolumeJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/VolumeJoinDaoImpl.java
index 6d46d09..c60e27c 100644
--- a/server/src/main/java/com/cloud/api/query/dao/VolumeJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/VolumeJoinDaoImpl.java
@@ -21,8 +21,11 @@ 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.api.ResponseObject.ResponseView;
 import org.apache.cloudstack.api.response.VolumeResponse;
+import org.apache.cloudstack.context.CallContext;
 import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
 import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
@@ -54,6 +57,8 @@ public class VolumeJoinDaoImpl extends GenericDaoBaseWithTagInformation<VolumeJo
     private VmDiskStatisticsDao vmDiskStatsDao;
     @Inject
     private PrimaryDataStoreDao primaryDataStoreDao;
+    @Inject
+    private AnnotationDao annotationDao;
 
     private final SearchBuilder<VolumeJoinVO> volSearch;
 
@@ -236,6 +241,9 @@ public class VolumeJoinDaoImpl extends GenericDaoBaseWithTagInformation<VolumeJo
             addTagInformation(volume, volResponse);
         }
 
+        volResponse.setHasAnnotation(annotationDao.hasAnnotations(volume.getUuid(), AnnotationService.EntityType.VOLUME.name(),
+                _accountMgr.isRootAdmin(CallContext.current().getCallingAccount().getId())));
+
         volResponse.setExtractable(isExtractable);
         volResponse.setDisplayVolume(volume.isDisplayVolume());
         volResponse.setChainInfo(volume.getChainInfo());
@@ -264,6 +272,10 @@ public class VolumeJoinDaoImpl extends GenericDaoBaseWithTagInformation<VolumeJo
         if (tag_id > 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 82887d2..d88cbe0 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
@@ -208,6 +208,11 @@ public class AsyncJobJoinVO extends BaseViewVO implements ControlledViewEntity {
     }
 
     @Override
+    public String getName() {
+        return null;
+    }
+
+    @Override
     public String getProjectUuid() {
         // TODO Auto-generated method stub
         return null;
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 8fba938..3584038 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 3c2d21c..bf28fdd 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 6758552..3adec29 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 88f5efa..1b2971c 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
@@ -255,6 +255,11 @@ public class UserAccountJoinVO extends BaseViewVO implements InternalIdentity, I
     }
 
     @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 7c18c60..4ccdf7e 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 ebdf635..97ef050 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<IPAddressVO> AssignIpAddressSearch;
     SearchBuilder<IPAddressVO> 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 3dc984e..c984041 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 080b1f1..e72922d 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 5b444c2..461eb49 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 2b4e233..82f0c81 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 5a5751c..10788b2 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 0193216..5507475 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<StoragePoolDiscoverer> _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 06da5d1..47ff898 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 aed1d87..00dfee2 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 918223e..f6569a0 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 2afc050..d3a67a0 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<InstanceGroupVMMapVO> 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 4a7840e..bd66fe8 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 2b658d5..c151b5c 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<RoleType> adminRoles = Collections.singletonList(RoleType.Admin);
+    private List<KubernetesClusterHelper> kubernetesClusterHelpers;
+
+    public List<KubernetesClusterHelper> getKubernetesClusterHelpers() {
+        return kubernetesClusterHelpers;
+    }
+
+    public void setKubernetesClusterHelpers(final List<KubernetesClusterHelper> kubernetesClusterHelpers) {
+        this.kubernetesClusterHelpers = kubernetesClusterHelpers;
+    }
+
+    @Override
+    public boolean start() {
+        super.start();
+        return true;
+    }
+
+    @Override
+    public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
+        return true;
+    }
 
     @Override
     public ListResponse<AnnotationResponse> searchForAnnotations(ListAnnotationsCmd cmd) {
-        List<AnnotationVO> annotations = getAnnotationsForApiCmd(cmd);
-        List<AnnotationResponse> annotationResponses = convertAnnotationsToResponses(annotations);
-        return createAnnotationsResponseList(annotationResponses);
+        Pair<List<AnnotationVO>, Integer> annotations = getAnnotationsForApiCmd(cmd);
+        List<AnnotationResponse> 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<EntityType> 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();
+        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("marking annotation removed: " + uuid);
+            LOGGER.debug(String.format("Removing annotation uuid: %s - type: %s", uuid, annotation.getEntityType().name()));
         }
-        AnnotationVO annotation = annotationDao.findByUuid(uuid);
         annotationDao.remove(annotation.getId());
+
         return createAnnotationResponse(annotation);
     }
 
-    private List<AnnotationVO> getAnnotationsForApiCmd(ListAnnotationsCmd cmd) {
+    @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);
+    }
+
+    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<List<AnnotationVO>, Integer> getAnnotationsForApiCmd(ListAnnotationsCmd cmd) {
         List<AnnotationVO> annotations;
-        if(cmd.getUuid() != null) {
-            annotations = new ArrayList<>();
-            String uuid = cmd.getUuid().toString();
-            if(LOGGER.isDebugEnabled()) {
-                LOGGER.debug("getting single annotation by uuid: " + uuid);
-            }
+        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();
 
-            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);
-            }
+        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 {
+            annotations = getAllAnnotations(annotationFilter, userUuid, callingUserUuid, isCallerAdmin, keyword);
+        }
+        List<AnnotationVO> paginated = StringUtils.applyPagination(annotations, cmd.getStartIndex(), cmd.getPageSizeVal());
+        return (paginated != null) ? new Pair<>(paginated, annotations.size()) :
+                new Pair<>(annotations, annotations.size());
+    }
+
+    private List<AnnotationVO> 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<AnnotationVO> annotations = annotationDao.listAllAnnotations(userUuid, getCallingUserRole(),
+                annotationFilter, keyword);
+        if (!isCallerAdmin) {
+            annotations = filterUserOwnedAnnotations(annotations);
+        }
+        return annotations;
+    }
+
+    private List<AnnotationVO> filterUserOwnedAnnotations(List<AnnotationVO> annotations) {
+        UserVO userVO = getCallingUserFromContext();
+        return annotations.stream()
+                .filter(x -> isEntityOwnedByTheUser(x.getEntityType().name(), x.getEntityUuid(), userVO))
+                .collect(Collectors.toList());
+    }
+
+    private List<AnnotationVO> 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<AnnotationVO> 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 {
-            if(LOGGER.isDebugEnabled()) {
-                LOGGER.debug("getting all annotations");
+            List<AnnotationVO> annotations = annotationDao.listByEntityType(entityType, userUuid, isCallerAdmin,
+                    annotationFilter, callingUserUuid, keyword);
+            if (!isCallerAdmin) {
+                annotations = filterUserOwnedAnnotations(annotations);
             }
-            annotations = annotationDao.listAll();
+            return annotations;
+        }
+    }
+
+    private List<AnnotationVO> getSingleAnnotationListByUuid(String uuid, String userUuid, String annotationFilter,
+                                                             String callingUserUuid, boolean isCallerAdmin) {
+        List<AnnotationVO> 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<AnnotationVO> 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<EntityType> 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<AnnotationResponse> convertAnnotationsToResponses(List<AnnotationVO> annotations) {
         List<AnnotationResponse> annotationResponses = new ArrayList<>();
         for (AnnotationVO annotation : annotations) {
@@ -119,13 +487,13 @@ public final class AnnotationManagerImpl extends ManagerBase implements Annotati
         return annotationResponses;
     }
 
-    private ListResponse<AnnotationResponse> createAnnotationsResponseList(List<AnnotationResponse> annotationResponses) {
+    private ListResponse<AnnotationResponse> createAnnotationsResponseList(List<AnnotationResponse> annotationResponses, Integer count) {
         ListResponse<AnnotationResponse> 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<Class<?>> getCommands() {
         final List<Class<?>> 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 e9905c5..207270d 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 @@
         <property name="caProviders" value="#{caProvidersRegistry.registered}" />
     </bean>
 
-    <bean id="annotationService" class="org.apache.cloudstack.annotation.AnnotationManagerImpl" />
+    <bean id="annotationService" class="org.apache.cloudstack.annotation.AnnotationManagerImpl">
+        <property name="kubernetesClusterHelpers" value="#{kubernetesClusterHelperRegistry.registered}" />
+    </bean>
 
     <bean id="indirectAgentLBService" class="org.apache.cloudstack.agent.lb.IndirectAgentLBServiceImpl" />
 
diff --git a/server/src/test/java/com/cloud/user/DomainManagerImplTest.java b/server/src/test/java/com/cloud/user/DomainManagerImplTest.java
index 2e9b540..b427d3c 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 78e70f3..c05b732 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 55343ef..897d4da 100644
--- a/server/src/test/resources/createNetworkOffering.xml
+++ b/server/src/test/resources/createNetworkOffering.xml
@@ -61,4 +61,5 @@
     <bean id="vMTemplateZoneDaoImpl" class="com.cloud.storage.dao.VMTemplateZoneDaoImpl" />
     <bean id="indirectAgentLBImpl" class="org.apache.cloudstack.agent.lb.IndirectAgentLBServiceImpl" />
     <bean id="VsphereStoragePolicyDaoImpl" class="com.cloud.dc.dao.VsphereStoragePolicyDaoImpl" />
+    <bean id="annotationDaoImpl" class="org.apache.cloudstack.annotation.dao.AnnotationDaoImpl" />
 </beans>
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 a463af9..8b86946 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 6d7841e..7ed3e98 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 0be8ae4..90fcad8 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 5202cc6..093d819 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 @@
       <a-button
         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"
@@ -116,6 +116,12 @@ export default {
         return []
       }
     },
+    selectedItems: {
+      type: Array,
+      default () {
+        return []
+      }
+    },
     loading: {
       type: Boolean,
       default: false
diff --git a/ui/src/components/view/AnnotationsTab.vue b/ui/src/components/view/AnnotationsTab.vue
new file mode 100644
index 0000000..5223c40
--- /dev/null
+++ b/ui/src/components/view/AnnotationsTab.vue
@@ -0,0 +1,312 @@
+// 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.
+
+<template>
+
+  <div class="account-center-team" v-if="annotationType && 'listAnnotations' in $store.getters.apis">
+    <a-spin :spinning="loadingAnnotations">
+      <div class="title">
+        {{ $t('label.comments') }} ({{ this.itemCount }})
+      </div>
+      <a-divider :dashed="true" />
+      <a-list
+        v-if="notes.length"
+        :dataSource="notes"
+        itemLayout="horizontal"
+        :pagination="false"
+        size="small" >
+        <a-list-item slot="renderItem" slot-scope="item">
+          <a-comment
+            class="comment"
+            :content="item.annotation"
+            :datetime="$toLocaleDate(item.created)"
+            :author="item.username" >
+            <a-avatar
+              slot="avatar"
+              icon="message" />
+            <a-popconfirm
+              :title="$t('label.make') + ' ' + (item.adminsonly ? $t('label.annotation.everyone') : $t('label.annotation.admins.only')) + ' ?'"
+              v-if="['Admin'].includes($store.getters.userInfo.roletype)"
+              slot="actions"
+              key="visibility"
+              @confirm="updateVisibility(item)"
+              :okText="$t('label.yes')"
+              :cancelText="$t('label.no')" >
+              <a-icon
+                type="eye"
+                :style="[{
+                  color: item.adminsonly ? $config.theme['@primary-color'] : $config.theme['@disabled-color']
+                }]" />
+              <span>
+                {{ item.adminsonly ? $t('label.annotation.admins.only') : $t('label.annotation.everyone') }}
+              </span>
+            </a-popconfirm>
+          </a-comment>
+          <a-popconfirm
+            :title="$t('label.remove.annotation')"
+            v-if="'removeAnnotation' in $store.getters.apis && isAdminOrAnnotationOwner(item)"
+            slot="actions"
+            key="visibility"
+            @confirm="deleteNote(item)"
+            :okText="$t('label.yes')"
+            :cancelText="$t('label.no')" >
+            <a-icon
+              type="delete"
+              shape="circle"
+              theme="twoTone"
+              two-tone-color="#eb2f96" />
+          </a-popconfirm>
+        </a-list-item>
+      </a-list>
+      <a-pagination
+        class="row-element"
+        size="small"
+        :current="page"
+        :pageSize="pageSize"
+        :total="itemCount"
+        :showTotal="total => `${$t('label.showing')} ${Math.min(total, 1+((page-1)*pageSize))}-${Math.min(page*pageSize, total)} ${$t('label.of')} ${total} ${$t('label.items')}`"
+        :pageSizeOptions="['10']"
+        @change="changePage"
+        showQuickJumper>
+        <template slot="buildOptionText" slot-scope="props">
+          <span>{{ props.value }} / {{ $t('label.page') }}</span>
+        </template>
+      </a-pagination>
+
+      <a-divider :dashed="true" />
+      <a-comment v-if="'addAnnotation' in $store.getters.apis">
+        <a-avatar
+          slot="avatar"
+          icon="edit"
+          @click="showNotesInput = true" />
+        <div slot="content" v-ctrl-enter="saveNote">
+          <a-textarea
+            rows="4"
+            @change="handleNoteChange"
+            :value="annotation"
+            :placeholder="$t('label.add.note')" />
+          <a-checkbox @change="toggleNoteVisibility" v-if="['Admin'].includes(this.$store.getters.userInfo.roletype)" style="margin-top: 10px">
+            {{ $t('label.annotation.admins.only') }}
+          </a-checkbox>
+          <a-button
+            style="margin-top: 10px; float: right"
+            @click="saveNote"
+            type="primary" >
+            {{ $t('label.submit') }}
+          </a-button>
+        </div>
+      </a-comment>
+    </a-spin>
+  </div>
+</template>
+
+<script>
+
+import { api } from '@/api'
+
+export default {
+  name: 'AnnotationsTab',
+  props: {
+    resource: {
+      type: Object,
+      required: true
+    },
+    items: {
+      type: Array,
+      default: () => []
+    }
+  },
+  inject: ['parentFetchData'],
+  data () {
+    return {
+      loadingAnnotations: false,
+      notes: [],
+      annotation: '',
+      annotationType: '',
+      annotationAdminsOnly: false,
+      showNotesInput: false,
+      page: 1,
+      pageSize: 10,
+      itemCount: 0
+    }
+  },
+  watch: {
+    resource: function (newItem, oldItem) {
+      this.resource = newItem
+      this.resourceType = this.$route.meta.resourceType
+      this.annotationType = this.generateAnnotationType()
+      if (this.annotationType) {
+        this.getAnnotations()
+      }
+    }
+  },
+  created () {
+    this.fetchData()
+  },
+  methods: {
+    generateAnnotationType () {
+      switch (this.resourceType) {
+        case 'UserVm': return 'VM'
+        case 'Domain': return 'DOMAIN'
+        case 'Host': return 'HOST'
+        case 'Volume': return 'VOLUME'
+        case 'Snapshot': return 'SNAPSHOT'
+        case 'VMSnapshot': return 'VM_SNAPSHOT'
+        case 'VMInstanceGroup': return 'INSTANCE_GROUP'
+        case 'SSHKeyPair': return 'SSH_KEYPAIR'
+        case 'KubernetesCluster': return 'KUBERNETES_CLUSTER'
+        case 'Network': return 'NETWORK'
+        case 'Vpc': return 'VPC'
+        case 'PublicIpAddress': return 'PUBLIC_IP_ADDRESS'
+        case 'VPNCustomerGateway': return 'VPN_CUSTOMER_GATEWAY'
+        case 'Template': return 'TEMPLATE'
+        case 'ISO': return 'ISO'
+        case 'ServiceOffering': return 'SERVICE_OFFERING'
+        case 'DiskOffering': return 'DISK_OFFERING'
+        case 'NetworkOffering': return 'NETWORK_OFFERING'
+        case 'Zone': return 'ZONE'
+        case 'Pod': return 'POD'
+        case 'Cluster': return 'CLUSTER'
+        case 'PrimaryStorage': return 'PRIMARY_STORAGE'
+        case 'SecondaryStorage': return 'SECONDARY_STORAGE'
+        case 'SystemVm': return 'SYSTEM_VM'
+        case 'VirtualRouter': return 'VR'
+        default: return ''
+      }
+    },
+    fetchData () {
+      this.resourceType = this.$route.meta.resourceType
+      this.annotationType = this.generateAnnotationType()
+      if (this.items.length) {
+        this.notes = this.items
+      } else {
+        this.getAnnotations()
+      }
+    },
+    changePage (page, pageSize) {
+      this.page = page
+      this.pagesize = pageSize
+      this.getAnnotations()
+    },
+    getAnnotations () {
+      if (!('listAnnotations' in this.$store.getters.apis) || !this.resource || !this.resource.id) {
+        return
+      }
+      this.loadingAnnotations = true
+      this.notes = []
+      api('listAnnotations', { entityid: this.resource.id, entitytype: this.annotationType, annotationfilter: 'all', page: this.page, pagesize: this.pageSize }).then(json => {
+        if (json.listannotationsresponse && json.listannotationsresponse.annotation) {
+          this.notes = json.listannotationsresponse.annotation
+          this.itemCount = json.listannotationsresponse.count
+        }
+      }).finally(() => {
+        this.loadingAnnotations = false
+      })
+    },
+    handleNoteChange (e) {
+      this.annotation = e.target.value
+    },
+    toggleNoteVisibility () {
+      this.annotationAdminsOnly = !this.annotationAdminsOnly
+    },
+    isAdminOrAnnotationOwner (annotation) {
+      return ['Admin'].includes(this.$store.getters.userInfo.roletype) || this.$store.getters.userInfo.id === annotation.userid
+    },
+    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
+      args.adminsonly = this.annotationAdminsOnly
+      api('addAnnotation', args).catch(error => {
+        this.$notifyError(error)
+      }).finally(e => {
+        this.getAnnotations()
+      })
+      this.annotation = ''
+      this.annotationAdminsOnly = false
+    },
+    deleteNote (annotation) {
+      this.loadingAnnotations = true
+      const args = {}
+      args.id = annotation.id
+      api('removeAnnotation', args).catch(error => {
+        this.$notifyError(error)
+      }).finally(e => {
+        this.getAnnotations()
+      })
+    },
+    updateVisibility (annotation) {
+      this.loadingAnnotations = true
+      const args = {
+        id: annotation.id,
+        adminsonly: !annotation.adminsonly
+      }
+      api('updateAnnotationVisibility', args).catch(error => {
+        this.$notifyError(error)
+      }).finally(e => {
+        this.getAnnotations()
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+
+.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;
+}
+
+.comment {
+  display: inline-block;
+  text-overflow: ellipsis;
+  width: calc(95%);
+}
+</style>
diff --git a/ui/src/components/view/InfoCard.vue b/ui/src/components/view/InfoCard.vue
index 7419d03..2386076 100644
--- a/ui/src/components/view/InfoCard.vue
+++ b/ui/src/components/view/InfoCard.vue
@@ -652,57 +652,6 @@
           </div>
         </a-spin>
       </div>
-
-      <div class="account-center-team" v-if="!isStatic && annotationType && 'listAnnotations' in $store.getters.apis">
-        <a-divider :dashed="true"/>
-        <a-spin :spinning="loadingAnnotations">
-          <div class="title">
-            {{ $t('label.comments') }} ({{ notes.length }})
-          </div>
-          <a-list
-            v-if="notes.length"
-            :dataSource="notes"
-            itemLayout="horizontal"
-            size="small" >
-            <a-list-item slot="renderItem" slot-scope="item">
-              <a-comment
-                :content="item.annotation"
-                :datetime="$toLocaleDate(item.created)" >
-                <a-button
-                  v-if="'removeAnnotation' in $store.getters.apis"
-                  slot="avatar"
-                  type="danger"
-                  shape="circle"
-                  size="small"
-                  @click="deleteNote(item)">
-                  <a-icon type="delete"/>
-                </a-button>
-              </a-comment>
-            </a-list-item>
-          </a-list>
-
-          <a-comment v-if="'addAnnotation' in $store.getters.apis">
-            <a-avatar
-              slot="avatar"
-              icon="edit"
-              @click="showNotesInput = true" />
-            <div slot="content">
-              <a-textarea
-                rows="4"
-                @change="handleNoteChange"
-                :value="annotation"
-                :placeholder="$t('label.add.note')" />
-              <a-button
-                style="margin-top: 10px"
-                @click="saveNote"
-                type="primary"
-              >
-                {{ $t('label.save') }}
-              </a-button>
-            </div>
-          </a-comment>
-        </a-spin>
-      </div>
     </a-card>
   </a-spin>
 </template>
@@ -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 795a6e4..3195240 100644
--- a/ui/src/components/view/ListView.vue
+++ b/ui/src/components/view/ListView.vue
@@ -73,7 +73,14 @@
         </span>
         <os-logo v-if="record.ostypename" :osName="record.ostypename" size="1x" style="margin-right: 5px" />
 
-        <span v-if="$route.path.startsWith('/globalsetting')">{{ text }}</span>
+        <span v-if="record.hasannotations">
+          <span v-if="record.id">
+            <router-link :to="{ path: $route.path + '/' + record.id }">{{ text }}</router-link>
+            <router-link :to="{ path: $route.path + '/' + record.id + '?tab=comments' }"><a-icon style="padding-left: 10px" size="small" type="message" theme="filled"/></router-link>
+          </span>
+          <router-link v-else :to="{ path: $route.path + '/' + record.name }" >{{ text }}</router-link>
+        </span>
+        <span v-else-if="$route.path.startsWith('/globalsetting')">{{ text }}</span>
         <span v-else-if="$route.path.startsWith('/alert')">
           <router-link :to="{ path: $route.path + '/' + record.id }" v-if="record.id">{{ $t(text.toLowerCase()) }}</router-link>
           <router-link :to="{ path: $route.path + '/' + record.name }" v-else>{{ $t(text.toLowerCase()) }}</router-link>
@@ -105,6 +112,16 @@
       <router-link :to="{ path: '/accountuser', query: { username: record.username, domainid: record.domainid } }" v-else-if="$store.getters.userInfo.roletype !== 'User'">{{ text }}</router-link>
       <span v-else>{{ text }}</span>
     </span>
+    <span slot="entityid" slot-scope="text, record" href="javascript:;">
+      <router-link :to="{ path: generateCommentsPath(record) }">{{ record.entityname }}</router-link>
+    </span>
+    <span slot="entitytype" slot-scope="text, record" href="javascript:;">
+      {{ generateHumanReadableEntityType(record) }}
+    </span>
+    <span slot="adminsonly" v-if="['Admin'].includes($store.getters.userInfo.roletype)" slot-scope="text, record" href="javascript:;">
+      <a-checkbox :checked="record.adminsonly" :value="record.id" v-if="record.userid === $store.getters.userInfo.id" @change="e => updateAdminsOnly(e)" />
+      <a-checkbox :checked="record.adminsonly" disabled v-else />
+    </span>
     <span slot="ipaddress" slot-scope="text, record" href="javascript:;">
       <router-link v-if="['/publicip', '/privategw'].includes($route.path)" :to="{ path: $route.path + '/' + record.id }">{{ text }}</router-link>
       <span v-else>{{ text }}</span>
@@ -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;
+}
 </style>
 
 <style scoped lang="scss">
diff --git a/ui/src/components/view/SearchView.vue b/ui/src/components/view/SearchView.vue
index def62c9..29e1902 100644
--- a/ui/src/components/view/SearchView.vue
+++ b/ui/src/components/view/SearchView.vue
@@ -49,7 +49,9 @@
               <a-form-item
                 v-for="(field, index) in fields"
                 :key="index"
-                :label="field.name==='keyword' ? $t('label.name') : $t('label.' + field.name)">
+                :label="field.name==='keyword' ?
+                  ('listAnnotations' in $store.getters.apis ? $t('label.annotation') : $t('label.name')) :
+                  (field.name==='entitytype' ? $t('label.annotation.entity.type') : $t('label.' + field.name))">
                 <a-select
                   allowClear
                   v-if="field.type==='list'"
@@ -213,7 +215,7 @@ export default {
         if (item === 'clusterid' && !('listClusters' in this.$store.getters.apis)) {
           return true
         }
-        if (['zoneid', 'domainid', 'state', 'level', 'clusterid', 'podid'].includes(item)) {
+        if (['zoneid', 'domainid', 'state', 'level', 'clusterid', 'podid', 'entitytype'].includes(item)) {
           type = 'list'
         } else if (item === 'tags') {
           type = 'tag'
@@ -272,6 +274,13 @@ export default {
         promises.push(await this.fetchClusters())
       }
 
+      if (arrayField.includes('entitytype')) {
+        const entityTypeIndex = this.fields.findIndex(item => item.name === 'entitytype')
+        this.fields[entityTypeIndex].loading = true
+        this.fields[entityTypeIndex].opts = this.fetchEntityType()
+        this.fields[entityTypeIndex].loading = false
+      }
+
       Promise.all(promises).then(response => {
         if (zoneIndex > -1) {
           const zones = response.filter(item => item.type === 'zoneid')
@@ -403,6 +412,45 @@ export default {
       }
       return state
     },
+    fetchEntityType () {
+      const entityType = []
+      if (this.apiName.indexOf('listAnnotations') > -1) {
+        const allowedTypes = {
+          VM: 'Virtual Machine',
+          HOST: 'Host',
+          VOLUME: 'Volume',
+          SNAPSHOT: 'Snapshot',
+          VM_SNAPSHOT: 'VM Snapshot',
+          INSTANCE_GROUP: 'Instance Group',
+          NETWORK: 'Network',
+          VPC: 'VPC',
+          PUBLIC_IP_ADDRESS: 'Public IP Address',
+          VPN_CUSTOMER_GATEWAY: 'VPC Customer Gateway',
+          TEMPLATE: 'Template',
+          ISO: 'ISO',
+          SSH_KEYPAIR: 'SSH Key Pair',
+          DOMAIN: 'Domain',
+          SERVICE_OFFERING: 'Service Offfering',
+          DISK_OFFERING: 'Disk Offering',
+          NETWORK_OFFERING: 'Network Offering',
+          POD: 'Pod',
+          ZONE: 'Zone',
+          CLUSTER: 'Cluster',
+          PRIMARY_STORAGE: 'Primary Storage',
+          SECONDARY_STORAGE: 'Secondary Storage',
+          VR: 'Virtual Router',
+          SYSTEM_VM: 'System VM',
+          KUBERNETES_CLUSTER: 'Kubernetes Cluster'
+        }
+        for (var key in allowedTypes) {
+          entityType.push({
+            id: key,
+            name: allowedTypes[key]
+          })
+        }
+      }
+      return entityType
+    },
     fetchLevel () {
       const levels = []
       levels.push({
diff --git a/ui/src/config/router.js b/ui/src/config/router.js
index 586d5a2..d6daa01 100644
--- a/ui/src/config/router.js
+++ b/ui/src/config/router.js
@@ -172,6 +172,10 @@ function generateRouterMap (section) {
     map.meta.actions = section.actions
   }
 
+  if (section.params) {
+    map.meta.params = section.params
+  }
+
   return map
 }
 
diff --git a/ui/src/config/section/compute.js b/ui/src/config/section/compute.js
index f2a0c61..131cd55 100644
--- a/ui/src/config/section/compute.js
+++ b/ui/src/config/section/compute.js
@@ -38,7 +38,7 @@ export default {
         return filters
       },
       columns: () => {
-        const fields = ['displayname', 'name', 'state', 'ipaddress']
+        const fields = ['name', 'displayname', 'state', 'ipaddress']
         const metricsFields = ['cpunumber', 'cpuused', 'cputotal',
           {
             memoryused: (record) => {
@@ -442,6 +442,7 @@ export default {
         name: 'k8s',
         component: () => import('@/views/compute/KubernetesServiceTab.vue')
       }],
+      resourceType: 'KubernetesCluster',
       actions: [
         {
           api: 'createKubernetesCluster',
@@ -517,6 +518,7 @@ export default {
       title: 'label.instance.groups',
       icon: 'gold',
       docHelp: 'adminguide/virtual_machines.html#changing-the-vm-name-os-or-group',
+      resourceType: 'VMInstanceGroup',
       permission: ['listInstanceGroups'],
       columns: ['name', 'account'],
       details: ['name', 'id', 'account', 'domain', 'created'],
@@ -525,6 +527,16 @@ export default {
         title: 'label.instances',
         param: 'groupid'
       }],
+      tabs: [
+        {
+          name: 'details',
+          component: () => import('@/components/view/DetailsTab.vue')
+        },
+        {
+          name: 'comments',
+          component: () => import('@/components/view/AnnotationsTab.vue')
+        }
+      ],
       actions: [
         {
           api: 'createInstanceGroup',
@@ -565,12 +577,23 @@ export default {
         }
         return fields
       },
-      details: ['name', 'fingerprint', 'account', 'domain'],
+      resourceType: 'SSHKeyPair',
+      details: ['id', 'name', 'fingerprint', 'account', 'domain'],
       related: [{
         name: 'vm',
         title: 'label.instances',
         param: 'keypair'
       }],
+      tabs: [
+        {
+          name: 'details',
+          component: () => import('@/components/view/DetailsTab.vue')
+        },
+        {
+          name: 'comments',
+          component: () => import('@/components/view/AnnotationsTab.vue')
+        }
+      ],
       actions: [
         {
           api: 'createSSHKeyPair',
diff --git a/ui/src/config/section/domain.js b/ui/src/config/section/domain.js
index e206255..7575202 100644
--- a/ui/src/config/section/domain.js
+++ b/ui/src/config/section/domain.js
@@ -54,6 +54,9 @@ export default {
       name: 'settings',
       component: () => import('@/components/view/SettingsTab.vue'),
       show: (record, route, user) => { return ['Admin'].includes(user.roletype) }
+    }, {
+      name: 'comments',
+      component: () => import('@/components/view/AnnotationsTab.vue')
     }
   ],
   treeView: true,
diff --git a/ui/src/config/section/image.js b/ui/src/config/section/image.js
index ce45ccc..7734537 100644
--- a/ui/src/config/section/image.js
+++ b/ui/src/config/section/image.js
@@ -67,6 +67,10 @@ export default {
       }, {
         name: 'settings',
         component: () => import('@/components/view/DetailSettings')
+      },
+      {
+        name: 'comments',
+        component: () => import('@/components/view/AnnotationsTab.vue')
       }],
       actions: [
         {
@@ -205,6 +209,10 @@ export default {
       }, {
         name: 'zones',
         component: () => import('@/views/image/IsoZones.vue')
+      },
+      {
+        name: 'comments',
+        component: () => import('@/components/view/AnnotationsTab.vue')
       }],
       actions: [
         {
diff --git a/ui/src/config/section/infra/clusters.js b/ui/src/config/section/infra/clusters.js
index 96c3c0a..b1d9997 100644
--- a/ui/src/config/section/infra/clusters.js
+++ b/ui/src/config/section/infra/clusters.js
@@ -38,6 +38,7 @@ export default {
     title: 'label.hosts',
     param: 'clusterid'
   }],
+  resourceType: 'Cluster',
   tabs: [{
     name: 'details',
     component: () => import('@/components/view/DetailsTab.vue')
@@ -47,6 +48,9 @@ export default {
   }, {
     name: 'settings',
     component: () => import('@/components/view/SettingsTab.vue')
+  }, {
+    name: 'comments',
+    component: () => import('@/components/view/AnnotationsTab.vue')
   }],
   actions: [
     {
diff --git a/ui/src/config/section/infra/hosts.js b/ui/src/config/section/infra/hosts.js
index 824cff6..dbf8e6e 100644
--- a/ui/src/config/section/infra/hosts.js
+++ b/ui/src/config/section/infra/hosts.js
@@ -38,6 +38,9 @@ export default {
   tabs: [{
     name: 'details',
     component: () => import('@/components/view/DetailsTab.vue')
+  }, {
+    name: 'comments',
+    component: () => import('@/components/view/AnnotationsTab.vue')
   }],
   related: [{
     name: 'vm',
diff --git a/ui/src/config/section/infra/pods.js b/ui/src/config/section/infra/pods.js
index dbc9791..89c60a2 100644
--- a/ui/src/config/section/infra/pods.js
+++ b/ui/src/config/section/infra/pods.js
@@ -31,12 +31,16 @@ export default {
     title: 'label.hosts',
     param: 'podid'
   }],
+  resourceType: 'Pod',
   tabs: [{
     name: 'details',
     component: () => import('@/components/view/DetailsTab.vue')
   }, {
     name: 'resources',
     component: () => import('@/views/infra/Resources.vue')
+  }, {
+    name: 'comments',
+    component: () => import('@/components/view/AnnotationsTab.vue')
   }],
   actions: [
     {
diff --git a/ui/src/config/section/infra/primaryStorages.js b/ui/src/config/section/infra/primaryStorages.js
index dc18800..ab9241f 100644
--- a/ui/src/config/section/infra/primaryStorages.js
+++ b/ui/src/config/section/infra/primaryStorages.js
@@ -39,12 +39,16 @@ export default {
     title: 'label.volumes',
     param: 'storageid'
   }],
+  resourceType: 'PrimaryStorage',
   tabs: [{
     name: 'details',
     component: () => import('@/components/view/DetailsTab.vue')
   }, {
     name: 'settings',
     component: () => import('@/components/view/SettingsTab.vue')
+  }, {
+    name: 'comments',
+    component: () => import('@/components/view/AnnotationsTab.vue')
   }],
   actions: [
     {
diff --git a/ui/src/config/section/infra/routers.js b/ui/src/config/section/infra/routers.js
index b34389d..00b2a90 100644
--- a/ui/src/config/section/infra/routers.js
+++ b/ui/src/config/section/infra/routers.js
@@ -25,6 +25,7 @@ export default {
   columns: ['name', 'state', 'publicip', 'guestnetworkname', 'vpcname', 'redundantstate', 'version', 'hostname', 'account', 'zonename', 'requiresupgrade'],
   searchFilters: ['name', 'zoneid', 'podid', 'clusterid'],
   details: ['name', 'id', 'version', 'requiresupgrade', 'guestnetworkname', 'vpcname', 'publicip', 'guestipaddress', 'linklocalip', 'serviceofferingname', 'networkdomain', 'isredundantrouter', 'redundantstate', 'hostname', 'account', 'zonename', 'created'],
+  resourceType: 'VirtualRouter',
   tabs: [{
     name: 'details',
     component: () => import('@/components/view/DetailsTab.vue')
@@ -35,6 +36,9 @@ export default {
     name: 'router.health.checks',
     show: (record, route, user) => { return ['Running'].includes(record.state) && ['Admin'].includes(user.roletype) },
     component: () => import('@views/infra/routers/RouterHealthCheck.vue')
+  }, {
+    name: 'comments',
+    component: () => import('@/components/view/AnnotationsTab.vue')
   }],
   related: [{
     name: 'vm',
diff --git a/ui/src/config/section/infra/secondaryStorages.js b/ui/src/config/section/infra/secondaryStorages.js
index 059dde8..b0d20e0 100644
--- a/ui/src/config/section/infra/secondaryStorages.js
+++ b/ui/src/config/section/infra/secondaryStorages.js
@@ -39,12 +39,16 @@ export default {
     }
     return fields
   },
+  resourceType: 'SecondaryStorage',
   tabs: [{
     name: 'details',
     component: () => import('@/components/view/DetailsTab.vue')
   }, {
     name: 'settings',
     component: () => import('@/components/view/SettingsTab.vue')
+  }, {
+    name: 'comments',
+    component: () => import('@/components/view/AnnotationsTab.vue')
   }],
   actions: [
     {
diff --git a/ui/src/config/section/infra/systemVms.js b/ui/src/config/section/infra/systemVms.js
index 27a2e47..2c0bd1d 100644
--- a/ui/src/config/section/infra/systemVms.js
+++ b/ui/src/config/section/infra/systemVms.js
@@ -23,6 +23,17 @@ export default {
   permission: ['listSystemVms'],
   columns: ['name', 'state', 'agentstate', 'systemvmtype', 'publicip', 'privateip', 'linklocalip', 'hostname', 'zonename'],
   details: ['name', 'id', 'agentstate', 'systemvmtype', 'publicip', 'privateip', 'linklocalip', 'gateway', 'hostname', 'zonename', 'created', 'activeviewersessions', 'isdynamicallyscalable'],
+  resourceType: 'SystemVm',
+  tabs: [
+    {
+      name: 'details',
+      component: () => import('@/components/view/DetailsTab.vue')
+    },
+    {
+      name: 'comments',
+      component: () => import('@/components/view/AnnotationsTab.vue')
+    }
+  ],
   actions: [
     {
       api: 'startSystemVm',
diff --git a/ui/src/config/section/infra/zones.js b/ui/src/config/section/infra/zones.js
index 7254155..4df57a5 100644
--- a/ui/src/config/section/infra/zones.js
+++ b/ui/src/config/section/infra/zones.js
@@ -53,6 +53,7 @@ export default {
     title: 'label.secondary.storage',
     param: 'zoneid'
   }],
+  resourceType: 'Zone',
   tabs: [{
     name: 'details',
     component: () => import('@/components/view/DetailsTab.vue')
@@ -68,6 +69,9 @@ export default {
   }, {
     name: 'settings',
     component: () => import('@/components/view/SettingsTab.vue')
+  }, {
+    name: 'comments',
+    component: () => import('@/components/view/AnnotationsTab.vue')
   }],
   actions: [
     {
diff --git a/ui/src/config/section/network.js b/ui/src/config/section/network.js
index 937d58a..9c4b38b 100644
--- a/ui/src/config/section/network.js
+++ b/ui/src/config/section/network.js
@@ -57,6 +57,10 @@ export default {
         name: 'guest.ip.range',
         component: () => import('@/views/network/GuestIpRanges.vue'),
         show: (record) => { return 'listVlanIpRanges' in store.getters.apis && (record.type === 'Shared' || (record.service && record.service.filter(x => x.name === 'SourceNat').count === 0)) }
+      },
+      {
+        name: 'comments',
+        component: () => import('@/components/view/AnnotationsTab.vue')
       }],
       actions: [
         {
@@ -286,6 +290,10 @@ export default {
         name: 'vpn',
         component: () => import('@/views/network/VpnDetails.vue'),
         show: (record) => { return record.issourcenat }
+      },
+      {
+        name: 'comments',
+        component: () => import('@/components/view/AnnotationsTab.vue')
       }],
       actions: [
         {
@@ -623,6 +631,17 @@ export default {
       columns: ['name', 'gateway', 'cidrlist', 'ipsecpsk', 'account'],
       details: ['name', 'id', 'gateway', 'cidrlist', 'ipsecpsk', 'ikepolicy', 'ikelifetime', 'ikeversion', 'esppolicy', 'esplifetime', 'dpd', 'splitconnections', 'forceencap', 'account', 'domain'],
       searchFilters: ['keyword', 'domainid', 'account'],
+      resourceType: 'VPNCustomerGateway',
+      tabs: [
+        {
+          name: 'details',
+          component: () => import('@/components/view/DetailsTab.vue')
+        },
+        {
+          name: 'comments',
+          component: () => import('@/components/view/AnnotationsTab.vue')
+        }
+      ],
       actions: [
         {
           api: 'createVpnCustomerGateway',
diff --git a/ui/src/config/section/offering.js b/ui/src/config/section/offering.js
index 9e22d91..80f8df2 100644
--- a/ui/src/config/section/offering.js
+++ b/ui/src/config/section/offering.js
@@ -42,6 +42,17 @@ export default {
         }
         return fields
       },
+      resourceType: 'ServiceOffering',
+      tabs: [
+        {
+          name: 'details',
+          component: () => import('@/components/view/DetailsTab.vue')
+        },
+        {
+          name: 'comments',
+          component: () => import('@/components/view/AnnotationsTab.vue')
+        }
+      ],
       related: [{
         name: 'vm',
         title: 'label.instances',
@@ -137,6 +148,17 @@ export default {
         }
         return fields
       },
+      resourceType: 'DiskOffering',
+      tabs: [
+        {
+          name: 'details',
+          component: () => import('@/components/view/DetailsTab.vue')
+        },
+        {
+          name: 'comments',
+          component: () => import('@/components/view/AnnotationsTab.vue')
+        }
+      ],
       related: [{
         name: 'volume',
         title: 'label.volumes',
@@ -214,6 +236,17 @@ export default {
       params: { isrecursive: 'true' },
       columns: ['name', 'state', 'guestiptype', 'traffictype', 'networkrate', 'domain', 'zone', 'order'],
       details: ['name', 'id', 'displaytext', 'guestiptype', 'traffictype', 'networkrate', 'ispersistent', 'egressdefaultpolicy', 'availability', 'conservemode', 'specifyvlan', 'specifyipranges', 'supportspublicaccess', 'supportsstrechedl2subnet', 'service', 'tags', 'domain', 'zone'],
+      resourceType: 'NetworkOffering',
+      tabs: [
+        {
+          name: 'details',
+          component: () => import('@/components/view/DetailsTab.vue')
+        },
+        {
+          name: 'comments',
+          component: () => import('@/components/view/AnnotationsTab.vue')
+        }
+      ],
       actions: [{
         api: 'createNetworkOffering',
         icon: 'plus',
diff --git a/ui/src/config/section/storage.js b/ui/src/config/section/storage.js
index 3eb50b6..48f10d0 100644
--- a/ui/src/config/section/storage.js
+++ b/ui/src/config/section/storage.js
@@ -62,6 +62,16 @@ export default {
         title: 'label.snapshots',
         param: 'volumeid'
       }],
+      tabs: [
+        {
+          name: 'details',
+          component: () => import('@/components/view/DetailsTab.vue')
+        },
+        {
+          name: 'comments',
+          component: () => import('@/components/view/AnnotationsTab.vue')
+        }
+      ],
       searchFilters: ['name', 'zoneid', 'domainid', 'account', 'state', 'tags'],
       actions: [
         {
@@ -280,6 +290,16 @@ export default {
         return fields
       },
       details: ['name', 'id', 'volumename', 'intervaltype', 'account', 'domain', 'created'],
+      tabs: [
+        {
+          name: 'details',
+          component: () => import('@/components/view/DetailsTab.vue')
+        },
+        {
+          name: 'comments',
+          component: () => import('@/components/view/AnnotationsTab.vue')
+        }
+      ],
       searchFilters: ['name', 'domainid', 'account', 'tags'],
       actions: [
         {
@@ -352,6 +372,16 @@ export default {
       },
       details: ['name', 'id', 'displayname', 'description', 'type', 'current', 'parentName', 'virtualmachineid', 'account', 'domain', 'created'],
       searchFilters: ['name', 'domainid', 'account', 'tags'],
+      tabs: [
+        {
+          name: 'details',
+          component: () => import('@/components/view/DetailsTab.vue')
+        },
+        {
+          name: 'comments',
+          component: () => import('@/components/view/AnnotationsTab.vue')
+        }
+      ],
       actions: [
         {
           api: 'createSnapshotFromVMSnapshot',
diff --git a/ui/src/config/section/tools.js b/ui/src/config/section/tools.js
index 1406e2c..2a6a9d8 100644
--- a/ui/src/config/section/tools.js
+++ b/ui/src/config/section/tools.js
@@ -14,20 +14,59 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
+import store from '@/store'
 
 export default {
   name: 'tools',
   title: 'label.tools',
   icon: 'tool',
-  permission: ['listInfrastructure'],
   children: [
     {
+      name: 'comment',
+      title: 'label.comments',
+      icon: 'message',
+      docHelp: 'adminguide/events.html',
+      permission: ['listAnnotations'],
+      columns: () => {
+        const cols = ['entityid', 'entitytype', 'annotation', 'created', 'username']
+        if (['Admin'].includes(store.getters.userInfo.roletype)) {
+          cols.push('adminsonly')
+        }
+        return cols
+      },
+      searchFilters: ['entitytype', 'keyword'],
+      params: () => { return { annotationfilter: 'self' } },
+      filters: () => {
+        const filters = ['self', 'all']
+        return filters
+      },
+      actions: [
+        {
+          api: 'removeAnnotation',
+          icon: 'delete',
+          label: 'label.remove.annotation',
+          message: 'message.remove.annotation',
+          dataView: false,
+          groupAction: true,
+          popup: true,
+          groupShow: (selectedItems, storegetters) => {
+            if (['Admin'].includes(store.getters.userInfo.roletype)) {
+              return true
+            }
+            // Display only if the selected items are comments created by the user
+            return selectedItems.filter(x => { return x.username !== store.getters.userInfo.username }).length === 0
+          },
+          groupMap: (selection) => { return selection.map(x => { return { id: x } }) }
+        }
+      ]
+    },
+    {
       name: 'manageinstances',
       title: 'label.action.import.export.instances',
       icon: 'interaction',
       docHelp: 'adminguide/virtual_machines.html#importing-and-unmanaging-virtual-machine',
       resourceType: 'UserVm',
-      permission: ['listUnmanagedInstances'],
+      permission: ['listInfrastructure', 'listUnmanagedInstances'],
       component: () => import('@/views/tools/ManageInstances.vue')
     }
   ]
diff --git a/ui/src/views/AutogenView.vue b/ui/src/views/AutogenView.vue
index c821638..d8ccd0c 100644
--- a/ui/src/views/AutogenView.vue
+++ b/ui/src/views/AutogenView.vue
@@ -56,7 +56,8 @@
                       {{ $t('label.all') }}
                     </a-select-option>
                     <a-select-option v-for="filter in filters" :key="filter">
-                      {{ $t('label.' + filter) }}
+                      {{ $t('label.' + (['comment'].includes($route.name) ? 'filter.annotations.' : '') + filter) }}
+                      <a-icon type="clock-circle" v-if="['comment'].includes($route.name) && !['Admin'].includes($store.getters.userInfo.roletype) && filter === 'all'" />
                     </a-select-option>
                   </a-select>
                 </a-tooltip>
@@ -73,6 +74,7 @@
               :loading="loading"
               :actions="actions"
               :selectedRowKeys="selectedRowKeys"
+              :selectedItems="selectedItems"
               :dataView="dataView"
               :resource="resource"
               @exec-action="(action) => execAction(action, action.groupAction && !dataView)"/>
@@ -638,7 +640,12 @@ export default {
 
       params.listall = true
       if (this.$route.meta.params) {
-        Object.assign(params, this.$route.meta.params)
+        const metaParams = this.$route.meta.params
+        if (typeof metaParams === 'function') {
+          Object.assign(params, metaParams())
+        } else {
+          Object.assign(params, metaParams)
+        }
       }
       if (['Admin', 'DomainAdmin'].includes(this.$store.getters.userInfo.roletype) &&
         'templatefilter' in params && this.routeName === 'template') {
@@ -752,9 +759,7 @@ export default {
       this.loading = true
       if (this.$route.params && this.$route.params.id) {
         params.id = this.$route.params.id
-        if (this.$route.path.startsWith('/ssh/')) {
-          params.name = this.$route.params.id
-        } else if (this.$route.path.startsWith('/vmsnapshot/')) {
+        if (this.$route.path.startsWith('/vmsnapshot/')) {
           params.vmsnapshotid = this.$route.params.id
         } else if (this.$route.path.startsWith('/ldapsetting/')) {
           params.hostname = this.$route.params.id
@@ -799,6 +804,18 @@ export default {
           })
         }
 
+        if (this.apiName === 'listAnnotations') {
+          this.columns.map(col => {
+            if (col.title === 'label.entityid') {
+              col.title = this.$t('label.annotation.entity')
+            } else if (col.title === 'label.entitytype') {
+              col.title = this.$t('label.annotation.entity.type')
+            } else if (col.title === 'label.adminsonly') {
+              col.title = this.$t('label.annotation.admins.only')
+            }
+          })
+        }
+
         for (let idx = 0; idx < this.items.length; idx++) {
           this.items[idx].key = idx
           for (const key in customRender) {
@@ -807,9 +824,7 @@ export default {
               this.items[idx][key] = func(this.items[idx])
             }
           }
-          if (this.$route.path.startsWith('/ssh')) {
-            this.items[idx].id = this.items[idx].name
-          } else if (this.$route.path.startsWith('/ldapsetting')) {
+          if (this.$route.path.startsWith('/ldapsetting')) {
             this.items[idx].id = this.items[idx].hostname
           }
         }
@@ -1305,6 +1320,7 @@ export default {
       delete query.account
       delete query.domainid
       delete query.state
+      delete query.annotationfilter
       if (this.$route.name === 'template') {
         query.templatefilter = filter
       } else if (this.$route.name === 'iso') {
@@ -1322,6 +1338,8 @@ export default {
         } else if (['running', 'stopped'].includes(filter)) {
           query.state = filter
         }
+      } else if (this.$route.name === 'comment') {
+        query.annotationfilter = filter
       }
       query.filter = filter
       query.page = 1
diff --git a/ui/src/views/compute/InstanceTab.vue b/ui/src/views/compute/InstanceTab.vue
index fc8bc6f..703f15c 100644
--- a/ui/src/views/compute/InstanceTab.vue
+++ b/ui/src/views/compute/InstanceTab.vue
@@ -138,6 +138,12 @@
       <a-tab-pane :tab="$t('label.settings')" key="settings">
         <DetailSettings :resource="resource" :loading="loading" />
       </a-tab-pane>
+      <a-tab-pane :tab="$t('label.annotations')" key="comments" v-if="'listAnnotations' in $store.getters.apis">
+        <AnnotationsTab
+          :resource="vm"
+          :items="annotations">
+        </AnnotationsTab>
+      </a-tab-pane>
     </a-tabs>
 
     <a-modal
@@ -283,6 +289,7 @@ import DetailSettings from '@/components/view/DetailSettings'
 import NicsTable from '@/views/network/NicsTable'
 import ListResourceTable from '@/components/view/ListResourceTable'
 import TooltipButton from '@/components/widgets/TooltipButton'
+import AnnotationsTab from '@/components/view/AnnotationsTab'
 
 export default {
   name: 'InstanceTab',
@@ -293,7 +300,8 @@ export default {
     NicsTable,
     Status,
     ListResourceTable,
-    TooltipButton
+    TooltipButton,
+    AnnotationsTab
   },
   mixins: [mixinDevice],
   props: {
@@ -353,7 +361,8 @@ export default {
       listIps: {
         loading: false,
         opts: []
-      }
+      },
+      annotations: []
     }
   },
   created () {
@@ -392,6 +401,7 @@ export default {
     },
     fetchData () {
       this.volumes = []
+      this.annotations = []
       if (!this.vm || !this.vm.id) {
         return
       }
@@ -402,6 +412,11 @@ export default {
         }
         this.$set(this.resource, 'volumes', this.volumes)
       })
+      api('listAnnotations', { entityid: this.resource.id, entitytype: 'VM', annotationfilter: 'all' }).then(json => {
+        if (json.listannotationsresponse && json.listannotationsresponse.annotation) {
+          this.annotations = json.listannotationsresponse.annotation
+        }
+      })
     },
     listNetworks () {
       api('listNetworks', {
diff --git a/ui/src/views/compute/KubernetesServiceTab.vue b/ui/src/views/compute/KubernetesServiceTab.vue
index 3e7ced4..e725e38 100644
--- a/ui/src/views/compute/KubernetesServiceTab.vue
+++ b/ui/src/views/compute/KubernetesServiceTab.vue
@@ -118,6 +118,12 @@
       <a-tab-pane :tab="$t('label.loadbalancing')" key="loadbalancing" v-if="publicIpAddress">
         <LoadBalancing :resource="this.publicIpAddress" :loading="this.networkLoading" />
       </a-tab-pane>
+      <a-tab-pane :tab="$t('label.annotations')" key="comments" v-if="'listAnnotations' in $store.getters.apis">
+        <AnnotationsTab
+          :resource="resource"
+          :items="annotations">
+        </AnnotationsTab>
+      </a-tab-pane>
     </a-tabs>
   </a-spin>
 </template>
@@ -130,6 +136,7 @@ import FirewallRules from '@/views/network/FirewallRules'
 import PortForwarding from '@/views/network/PortForwarding'
 import LoadBalancing from '@/views/network/LoadBalancing'
 import Status from '@/components/widgets/Status'
+import AnnotationsTab from '@/components/view/AnnotationsTab'
 
 export default {
   name: 'KubernetesServiceTab',
@@ -138,7 +145,8 @@ export default {
     FirewallRules,
     PortForwarding,
     LoadBalancing,
-    Status
+    Status,
+    AnnotationsTab
   },
   mixins: [mixinDevice],
   props: {
@@ -167,7 +175,8 @@ export default {
       network: {},
       publicIpAddress: {},
       currentTab: 'details',
-      cksSshStartingPort: 2222
+      cksSshStartingPort: 2222,
+      annotations: []
     }
   },
   created () {
@@ -261,6 +270,19 @@ export default {
       this.fetchKubernetesVersion()
       this.fetchInstances()
       this.fetchPublicIpAddress()
+      this.fetchComments()
+    },
+    fetchComments () {
+      this.clusterConfigLoading = true
+      api('listAnnotations', { entityid: this.resource.id, entitytype: 'KUBERNETES_CLUSTER', annotationfilter: 'all' }).then(json => {
+        if (json.listannotationsresponse && json.listannotationsresponse.annotation) {
+          this.annotations = json.listannotationsresponse.annotation
+        }
+      }).catch(error => {
+        this.$notifyError(error)
+      }).finally(() => {
+        this.clusterConfigLoading = false
+      })
     },
     fetchKubernetesClusterConfig () {
       this.clusterConfigLoading = true
diff --git a/ui/src/views/network/VpcTab.vue b/ui/src/views/network/VpcTab.vue
index a5e7ff5..0b1ffa3 100644
--- a/ui/src/views/network/VpcTab.vue
+++ b/ui/src/views/network/VpcTab.vue
@@ -297,6 +297,12 @@
       <a-tab-pane :tab="$t('label.virtual.routers')" key="vr" v-if="$store.getters.userInfo.roletype === 'Admin'">
         <RoutersTab :resource="resource" :loading="loading" />
       </a-tab-pane>
+      <a-tab-pane :tab="$t('label.annotations')" key="comments" v-if="'listAnnotations' in $store.getters.apis">
+        <AnnotationsTab
+          :resource="resource"
+          :items="annotations">
+        </AnnotationsTab>
+      </a-tab-pane>
     </a-tabs>
   </a-spin>
 </template>
@@ -309,6 +315,7 @@ import Status from '@/components/widgets/Status'
 import IpAddressesTab from './IpAddressesTab'
 import RoutersTab from './RoutersTab'
 import VpcTiersTab from './VpcTiersTab'
+import AnnotationsTab from '@/components/view/AnnotationsTab'
 
 export default {
   name: 'VpcTab',
@@ -317,7 +324,8 @@ export default {
     Status,
     IpAddressesTab,
     RoutersTab,
-    VpcTiersTab
+    VpcTiersTab,
+    AnnotationsTab
   },
   mixins: [mixinDevice],
   props: {
@@ -414,7 +422,8 @@ export default {
       },
       page: 1,
       pageSize: 10,
-      currentTab: 'details'
+      currentTab: 'details',
+      annotations: []
     }
   },
   beforeCreate () {
@@ -471,8 +480,23 @@ export default {
         case 'acl':
           this.fetchAclList()
           break
+        case 'comments':
+          this.fetchComments()
+          break
       }
     },
+    fetchComments () {
+      this.fetchLoading = true
+      api('listAnnotations', { entityid: this.resource.id, entitytype: 'VPC', annotationfilter: 'all' }).then(json => {
+        if (json.listannotationsresponse && json.listannotationsresponse.annotation) {
+          this.annotations = json.listannotationsresponse.annotation
+        }
+      }).catch(error => {
+        this.$notifyError(error)
+      }).finally(() => {
+        this.fetchLoading = false
+      })
+    },
     fetchPrivateGateways () {
       this.fetchLoading = true
       api('listPrivateGateways', {