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', {