CLOUDSTACK-9957 Annotations (#2181)

* annotations on hosts

* Adding marvin tests

* rebase error

* review comments

* context for owner

* review

* illegal entity test

* entityType check on input

* Annotation events

* rebase issues
This commit is contained in:
dahn 2017-10-13 11:55:26 +02:00 committed by GitHub
parent 189b0e4487
commit a379230e8e
24 changed files with 1215 additions and 67 deletions

View File

@ -19,12 +19,6 @@ package com.cloud.event;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.apache.cloudstack.acl.Role;
import org.apache.cloudstack.acl.RolePermission;
import org.apache.cloudstack.config.Configuration;
import org.apache.cloudstack.ha.HAConfig;
import org.apache.cloudstack.usage.Usage;
import com.cloud.dc.DataCenter; import com.cloud.dc.DataCenter;
import com.cloud.dc.Pod; import com.cloud.dc.Pod;
import com.cloud.dc.StorageNetworkIpRange; import com.cloud.dc.StorageNetworkIpRange;
@ -75,6 +69,12 @@ import com.cloud.user.User;
import com.cloud.vm.Nic; import com.cloud.vm.Nic;
import com.cloud.vm.NicSecondaryIp; import com.cloud.vm.NicSecondaryIp;
import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachine;
import org.apache.cloudstack.acl.Role;
import org.apache.cloudstack.acl.RolePermission;
import org.apache.cloudstack.annotation.Annotation;
import org.apache.cloudstack.config.Configuration;
import org.apache.cloudstack.ha.HAConfig;
import org.apache.cloudstack.usage.Usage;
public class EventTypes { public class EventTypes {
@ -569,6 +569,9 @@ public class EventTypes {
public static final String EVENT_NETSCALER_VM_START = "NETSCALERVM.START"; public static final String EVENT_NETSCALER_VM_START = "NETSCALERVM.START";
public static final String EVENT_NETSCALER_VM_STOP = "NETSCALERVM.STOP"; public static final String EVENT_NETSCALER_VM_STOP = "NETSCALERVM.STOP";
public static final String EVENT_ANNOTATION_CREATE = "ANNOTATION.CREATE";
public static final String EVENT_ANNOTATION_REMOVE = "ANNOTATION.REMOVE";
static { static {
@ -953,6 +956,8 @@ public class EventTypes {
entityEventDetails.put(EVENT_NETSCALER_SERVICEPACKAGE_ADD, "NETSCALER.SERVICEPACKAGE.CREATE"); entityEventDetails.put(EVENT_NETSCALER_SERVICEPACKAGE_ADD, "NETSCALER.SERVICEPACKAGE.CREATE");
entityEventDetails.put(EVENT_NETSCALER_SERVICEPACKAGE_DELETE, "NETSCALER.SERVICEPACKAGE.DELETE"); entityEventDetails.put(EVENT_NETSCALER_SERVICEPACKAGE_DELETE, "NETSCALER.SERVICEPACKAGE.DELETE");
entityEventDetails.put(EVENT_ANNOTATION_CREATE, Annotation.class);
entityEventDetails.put(EVENT_ANNOTATION_REMOVE, Annotation.class);
} }
public static String getEntityForEvent(String eventName) { public static String getEntityForEvent(String eventName) {

View File

@ -0,0 +1,37 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.annotation;
import org.apache.cloudstack.api.Identity;
import org.apache.cloudstack.api.InternalIdentity;
import java.util.Date;
public interface Annotation extends InternalIdentity, Identity {
String getAnnotation();
String getEntityUuid();
AnnotationService.EntityType getEntityType();
String getUserUuid();
Date getCreated();
Date getRemoved();
}

View File

@ -0,0 +1,49 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.annotation;
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.response.AnnotationResponse;
import org.apache.cloudstack.api.response.ListResponse;
public interface AnnotationService {
ListResponse<AnnotationResponse> searchForAnnotations(ListAnnotationsCmd cmd);
AnnotationResponse addAnnotation(AddAnnotationCmd addAnnotationCmd);
AnnotationResponse addAnnotation(String text, EntityType type, String uuid);
AnnotationResponse removeAnnotation(RemoveAnnotationCmd removeAnnotationCmd);
enum EntityType {
HOST("host"), DOMAIN("domain"), VM("vm_instance");
private String tableName;
EntityType(String tableName) {
this.tableName = tableName;
}
static public boolean contains(String representation) {
try {
/* EntityType tiep = */ valueOf(representation);
return true;
} catch (IllegalArgumentException iae) {
return false;
}
}
}
}

View File

@ -25,6 +25,7 @@ public class ApiConstants {
public static final String ADDRESS = "address"; public static final String ADDRESS = "address";
public static final String ALGORITHM = "algorithm"; public static final String ALGORITHM = "algorithm";
public static final String ALLOCATED_ONLY = "allocatedonly"; public static final String ALLOCATED_ONLY = "allocatedonly";
public static final String ANNOTATION = "annotation";
public static final String API_KEY = "apikey"; public static final String API_KEY = "apikey";
public static final String USER_API_KEY = "userapikey"; public static final String USER_API_KEY = "userapikey";
public static final String APPLIED = "applied"; public static final String APPLIED = "applied";
@ -680,6 +681,9 @@ public class ApiConstants {
+ " representing the java supported algorithm, i.e. MD5 or SHA-256. Note that java does not\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."; + " contain an algorithm called SHA256 or one called sha-256, only SHA-256.";
public static final String HAS_ANNOTATION = "hasannotation";
public static final String LAST_ANNOTATED = "lastannotated";
public enum HostDetails { public enum HostDetails {
all, capacity, events, stats, min; all, capacity, events, stats, min;
} }

View File

@ -17,32 +17,6 @@
package org.apache.cloudstack.api; package org.apache.cloudstack.api;
import java.lang.reflect.Field;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import javax.inject.Inject;
import com.cloud.utils.HttpUtils;
import org.apache.cloudstack.acl.RoleService;
import org.apache.log4j.Logger;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.affinity.AffinityGroupService;
import org.apache.cloudstack.alert.AlertService;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.network.element.InternalLoadBalancerElementService;
import org.apache.cloudstack.network.lb.ApplicationLoadBalancerService;
import org.apache.cloudstack.network.lb.InternalLoadBalancerVMService;
import org.apache.cloudstack.query.QueryService;
import org.apache.cloudstack.usage.UsageService;
import com.cloud.configuration.ConfigurationService; import com.cloud.configuration.ConfigurationService;
import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.ConcurrentOperationException;
import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.InsufficientCapacityException;
@ -78,11 +52,35 @@ import com.cloud.user.Account;
import com.cloud.user.AccountService; import com.cloud.user.AccountService;
import com.cloud.user.DomainService; import com.cloud.user.DomainService;
import com.cloud.user.ResourceLimitService; import com.cloud.user.ResourceLimitService;
import com.cloud.utils.HttpUtils;
import com.cloud.utils.ReflectUtil; import com.cloud.utils.ReflectUtil;
import com.cloud.utils.db.EntityManager; import com.cloud.utils.db.EntityManager;
import com.cloud.utils.db.UUIDManager; import com.cloud.utils.db.UUIDManager;
import com.cloud.vm.UserVmService; import com.cloud.vm.UserVmService;
import com.cloud.vm.snapshot.VMSnapshotService; import com.cloud.vm.snapshot.VMSnapshotService;
import org.apache.cloudstack.acl.RoleService;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.affinity.AffinityGroupService;
import org.apache.cloudstack.alert.AlertService;
import org.apache.cloudstack.annotation.AnnotationService;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.network.element.InternalLoadBalancerElementService;
import org.apache.cloudstack.network.lb.ApplicationLoadBalancerService;
import org.apache.cloudstack.network.lb.InternalLoadBalancerVMService;
import org.apache.cloudstack.query.QueryService;
import org.apache.cloudstack.usage.UsageService;
import org.apache.log4j.Logger;
import javax.inject.Inject;
import java.lang.reflect.Field;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
public abstract class BaseCmd { public abstract class BaseCmd {
private static final Logger s_logger = Logger.getLogger(BaseCmd.class.getName()); private static final Logger s_logger = Logger.getLogger(BaseCmd.class.getName());
@ -93,6 +91,7 @@ public abstract class BaseCmd {
public static Pattern newInputDateFormat = Pattern.compile("[\\d]+-[\\d]+-[\\d]+ [\\d]+:[\\d]+:[\\d]+"); public static Pattern newInputDateFormat = Pattern.compile("[\\d]+-[\\d]+-[\\d]+ [\\d]+:[\\d]+:[\\d]+");
private static final DateFormat s_outputFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); private static final DateFormat s_outputFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
protected static final Map<Class<?>, List<Field>> fieldsForCmdClass = new HashMap<Class<?>, List<Field>>(); protected static final Map<Class<?>, List<Field>> fieldsForCmdClass = new HashMap<Class<?>, List<Field>>();
public static enum HTTPMethod { public static enum HTTPMethod {
GET, POST, PUT, DELETE GET, POST, PUT, DELETE
} }
@ -192,6 +191,8 @@ public abstract class BaseCmd {
public AlertService _alertSvc; public AlertService _alertSvc;
@Inject @Inject
public UUIDManager _uuidMgr; public UUIDManager _uuidMgr;
@Inject
public AnnotationService annotationService;
public abstract void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, public abstract void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException,
ResourceAllocationException, NetworkRuleConflictException; ResourceAllocationException, NetworkRuleConflictException;

View File

@ -0,0 +1,80 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.api.command.admin.annotation;
import com.cloud.exception.ConcurrentOperationException;
import com.cloud.exception.InsufficientCapacityException;
import com.cloud.exception.NetworkRuleConflictException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.exception.ResourceUnavailableException;
import 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;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.AnnotationResponse;
import org.apache.cloudstack.context.CallContext;
@APICommand(name = AddAnnotationCmd.APINAME, description = "add an annotation.", responseObject = AnnotationResponse.class,
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.11", authorized = {RoleType.Admin})
public class AddAnnotationCmd extends BaseCmd {
public static final String APINAME = "addAnnotation";
@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;
public String getAnnotation() {
return annotation;
}
public AnnotationService.EntityType getEntityType() {
return AnnotationService.EntityType.valueOf(entityType);
}
public String getEntityUuid() {
return entityUuid;
}
@Override
public void execute()
throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException,
NetworkRuleConflictException {
Preconditions.checkNotNull(entityUuid,"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);
annotationResponse.setResponseName(getCommandName());
this.setResponseObject(annotationResponse);
}
@Override
public String getCommandName() {
return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
}
@Override
public long getEntityOwnerId() {
return CallContext.current().getCallingAccount().getAccountId();
}
}

View File

@ -0,0 +1,81 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.api.command.admin.annotation;
import com.cloud.exception.ConcurrentOperationException;
import com.cloud.exception.InsufficientCapacityException;
import com.cloud.exception.NetworkRuleConflictException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.utils.StringUtils;
import com.google.common.base.Preconditions;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.BaseListCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.AnnotationResponse;
import org.apache.cloudstack.api.response.ListResponse;
@APICommand(name = ListAnnotationsCmd.APINAME, description = "Lists annotations.", responseObject = AnnotationResponse.class,
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.11", authorized = {RoleType.Admin})
public class ListAnnotationsCmd extends BaseListCmd {
public static final String APINAME = "listAnnotations";
@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;
public String getUuid() {
return uuid;
}
public String getEntityType() {
return entityType;
}
public String getEntityUuid() {
return entityUuid;
}
@Override public void execute()
throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException,
NetworkRuleConflictException {
// preconditions to check:
// if entity type is null entity uuid can not have a value
Preconditions.checkArgument(StringUtils.isNotBlank(entityType) ? ! StringUtils.isNotBlank(uuid) : true,
"I can search for an anotation on an entity or for a specific annotation, not both");
// if uuid has a value entity type and entity uuid can not have a value
Preconditions.checkArgument(StringUtils.isNotBlank(uuid) ? entityType == null && entityUuid == null : true,
"I will either search for a specific annotation or for annotations on an entity, not both");
ListResponse<AnnotationResponse> response = annotationService.searchForAnnotations(this);
response.setResponseName(getCommandName());
this.setResponseObject(response);
response.setObjectName("annotations");
}
@Override public String getCommandName() {
return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
}
}

View File

@ -0,0 +1,64 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.api.command.admin.annotation;
import com.cloud.exception.ConcurrentOperationException;
import com.cloud.exception.InsufficientCapacityException;
import com.cloud.exception.NetworkRuleConflictException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.exception.ResourceUnavailableException;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.AnnotationResponse;
import org.apache.cloudstack.context.CallContext;
@APICommand(name = RemoveAnnotationCmd.APINAME, description = "remove an annotation.", responseObject = AnnotationResponse.class,
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.11", authorized = {RoleType.Admin})
public class RemoveAnnotationCmd extends BaseCmd {
public static final String APINAME = "removeAnnotation";
@Parameter(name = ApiConstants.ID, type = CommandType.STRING, required = true, description = "the id of the annotation")
private String uuid;
public String getUuid() {
return uuid;
}
@Override
public void execute()
throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException,
NetworkRuleConflictException {
AnnotationResponse annotationResponse = annotationService.removeAnnotation(this);
annotationResponse.setResponseName(getCommandName());
this.setResponseObject(annotationResponse);
}
@Override
public String getCommandName() {
return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
}
@Override
public long getEntityOwnerId() {
return CallContext.current().getCallingAccount().getAccountId();
}
}

View File

@ -16,10 +16,10 @@
// under the License. // under the License.
package org.apache.cloudstack.api.command.admin.host; package org.apache.cloudstack.api.command.admin.host;
import java.util.List; import com.cloud.host.Host;
import com.cloud.user.Account;
import org.apache.log4j.Logger; import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.annotation.AnnotationService;
import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.ApiErrorCode;
@ -28,9 +28,9 @@ import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.GuestOSCategoryResponse; import org.apache.cloudstack.api.response.GuestOSCategoryResponse;
import org.apache.cloudstack.api.response.HostResponse; import org.apache.cloudstack.api.response.HostResponse;
import org.apache.log4j.Logger;
import com.cloud.host.Host; import java.util.List;
import com.cloud.user.Account;
@APICommand(name = "updateHost", description = "Updates a host.", responseObject = HostResponse.class, @APICommand(name = "updateHost", description = "Updates a host.", responseObject = HostResponse.class,
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
@ -62,6 +62,9 @@ public class UpdateHostCmd extends BaseCmd {
@Parameter(name = ApiConstants.URL, type = CommandType.STRING, description = "the new uri for the secondary storage: nfs://host/path") @Parameter(name = ApiConstants.URL, type = CommandType.STRING, description = "the new uri for the secondary storage: nfs://host/path")
private String url; private String url;
@Parameter(name = ApiConstants.ANNOTATION, type = CommandType.STRING, description = "Add an annotation to this host", since = "4.11", authorized = {RoleType.Admin})
private String annotation;
///////////////////////////////////////////////////// /////////////////////////////////////////////////////
/////////////////// Accessors /////////////////////// /////////////////// Accessors ///////////////////////
///////////////////////////////////////////////////// /////////////////////////////////////////////////////
@ -86,6 +89,10 @@ public class UpdateHostCmd extends BaseCmd {
return url; return url;
} }
public String getAnnotation() {
return annotation;
}
///////////////////////////////////////////////////// /////////////////////////////////////////////////////
/////////////// API Implementation/////////////////// /////////////// API Implementation///////////////////
///////////////////////////////////////////////////// /////////////////////////////////////////////////////
@ -109,6 +116,9 @@ public class UpdateHostCmd extends BaseCmd {
Host result; Host result;
try { try {
result = _resourceService.updateHost(this); result = _resourceService.updateHost(this);
if(getAnnotation() != null) {
annotationService.addAnnotation(getAnnotation(), AnnotationService.EntityType.HOST, result.getUuid());
}
HostResponse hostResponse = _responseGenerator.createHostResponse(result); HostResponse hostResponse = _responseGenerator.createHostResponse(result);
hostResponse.setResponseName(getCommandName()); hostResponse.setResponseName(getCommandName());
this.setResponseObject(hostResponse); this.setResponseObject(hostResponse);

View File

@ -0,0 +1,121 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.api.response;
import com.cloud.serializer.Param;
import com.google.gson.annotations.SerializedName;
import org.apache.cloudstack.annotation.Annotation;
import org.apache.cloudstack.annotation.AnnotationService;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseResponse;
import org.apache.cloudstack.api.EntityReference;
import java.util.Date;
/**
* @since 4.11
*/
@EntityReference(value = Annotation.class)
public class AnnotationResponse extends BaseResponse {
@SerializedName(ApiConstants.ID)
@Param(description = "the (uu)id of the annotation")
private String uuid;
@SerializedName(ApiConstants.ENTITY_TYPE)
@Param(description = "the type of the annotated entity")
private String entityType;
@SerializedName(ApiConstants.ENTITY_ID)
@Param(description = "the (uu)id of the entitiy to which this annotation pertains")
private String entityUuid;
@SerializedName(ApiConstants.ANNOTATION)
@Param(description = "the contents of the annotation")
private String annotation;
@SerializedName(ApiConstants.USER_ID)
@Param(description = "The (uu)id of the user that entered the annotation")
private String userUuid;
@SerializedName(ApiConstants.CREATED)
@Param(description = "the creation timestamp for this annotation")
private Date created;
@SerializedName(ApiConstants.REMOVED)
@Param(description = "the removal timestamp for this annotation")
private Date removed;
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public String getEntityType() {
return entityType;
}
public void setEntityType(String entityType) {
this.entityType = entityType;
}
public void setEntityType(AnnotationService.EntityType entityType) {
this.entityType = entityType.toString();
}
public String getEntityUuid() {
return entityUuid;
}
public void setEntityUuid(String entityUuid) {
this.entityUuid = entityUuid;
}
public String getAnnotation() {
return annotation;
}
public void setAnnotation(String annotation) {
this.annotation = annotation;
}
public String getUserUuid() {
return userUuid;
}
public void setUserUuid(String userUuid) {
this.userUuid = userUuid;
}
public Date getCreated() {
return created;
}
public void setCreated(Date created) {
this.created = created;
}
public Date getRemoved() {
return removed;
}
public void setRemoved(Date removed) {
this.removed = removed;
}
}

View File

@ -231,6 +231,17 @@ public class HostResponse extends BaseResponse {
@Param(description = "Host details in key/value pairs.", since = "4.5") @Param(description = "Host details in key/value pairs.", since = "4.5")
private Map details; private Map details;
@SerializedName(ApiConstants.ANNOTATION)
@Param(description = "the last annotation set on this host by an admin", since = "4.11")
private String annotation;
@SerializedName(ApiConstants.LAST_ANNOTATED)
@Param(description = "the last time this host was annotated", since = "4.11")
private Date lastAnnotated;
@SerializedName(ApiConstants.USERNAME)
@Param(description = "the admin that annotated this host", since = "4.11")
private String username;
// Default visibility to support accessing the details from unit tests // Default visibility to support accessing the details from unit tests
Map getDetails() { Map getDetails() {
@ -458,6 +469,18 @@ public class HostResponse extends BaseResponse {
this.haHost = haHost; this.haHost = haHost;
} }
public void setAnnotation(String annotation) {
this.annotation = annotation;
}
public void setLastAnnotated(Date lastAnnotated) {
this.lastAnnotated = lastAnnotated;
}
public void setUsername(String username) {
this.username = username;
}
public void setDetails(Map details) { public void setDetails(Map details) {
if (details == null) { if (details == null) {

View File

@ -353,4 +353,5 @@
<bean id="LBHealthCheckPolicyDetailsDaoImpl" class="org.apache.cloudstack.resourcedetail.dao.LBHealthCheckPolicyDetailsDaoImpl" /> <bean id="LBHealthCheckPolicyDetailsDaoImpl" class="org.apache.cloudstack.resourcedetail.dao.LBHealthCheckPolicyDetailsDaoImpl" />
<bean id="outOfBandManagementDaoImpl" class="org.apache.cloudstack.outofbandmanagement.dao.OutOfBandManagementDaoImpl" /> <bean id="outOfBandManagementDaoImpl" class="org.apache.cloudstack.outofbandmanagement.dao.OutOfBandManagementDaoImpl" />
<bean id="GuestOsDetailsDaoImpl" class="org.apache.cloudstack.resourcedetail.dao.GuestOsDetailsDaoImpl" /> <bean id="GuestOsDetailsDaoImpl" class="org.apache.cloudstack.resourcedetail.dao.GuestOsDetailsDaoImpl" />
<bean id="annotationDaoImpl" class="org.apache.cloudstack.annotation.dao.AnnotationDaoImpl" />
</beans> </beans>

View File

@ -0,0 +1,154 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.annotation;
import com.cloud.utils.db.GenericDao;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;
import java.util.UUID;
/**
* @since 4.11
*/
@Entity
@Table(name = "annotations")
public class AnnotationVO implements Annotation {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private long id;
@Column(name = "uuid")
private String uuid;
@Column(name = "annotation")
private String annotation;
@Column(name = "entity_uuid")
private String entityUuid;
@Column(name = "entity_type")
private AnnotationService.EntityType entityType;
@Column(name = "user_uuid")
private String userUuid;
@Column(name = GenericDao.CREATED_COLUMN)
private Date created;
@Column(name = GenericDao.REMOVED_COLUMN)
private Date removed;
// construct
public AnnotationVO() {
this.uuid = UUID.randomUUID().toString();
}
public AnnotationVO(String text, AnnotationService.EntityType type, String uuid) {
this();
setAnnotation(text);
setEntityType(type);
setEntityUuid(uuid);
}
public AnnotationVO(String text, String type, String uuid) {
this();
setAnnotation(text);
setEntityType(type);
setEntityUuid(uuid);
}
// access
@Override
public long getId() {
return id;
}
@Override
public String getUuid() {
return uuid;
}
@Override
public String getAnnotation() {
return annotation;
}
@Override
public String getEntityUuid() {
return entityUuid;
}
@Override
public AnnotationService.EntityType getEntityType() {
return entityType;
}
@Override
public String getUserUuid() {
return userUuid;
}
@Override
public Date getCreated() {
return created;
}
@Override
public Date getRemoved() {
return removed;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public void setAnnotation(String annotation) {
this.annotation = annotation;
}
public void setEntityUuid(String entityUuid) {
this.entityUuid = entityUuid;
}
public void setEntityType(String entityType) {
this.entityType = AnnotationService.EntityType.valueOf(entityType);
}
public void setEntityType(AnnotationService.EntityType entityType) {
this.entityType = entityType;
}
public void setUserUuid(String userUuid) {
this.userUuid = userUuid;
}
public void setCreated(Date created) {
this.created = created;
}
public void setRemoved(Date removed) {
this.removed = removed;
}
}

View File

@ -0,0 +1,30 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.annotation.dao;
import org.apache.cloudstack.annotation.AnnotationVO;
import com.cloud.utils.db.GenericDao;
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);
}

View File

@ -0,0 +1,59 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.annotation.dao;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import org.apache.cloudstack.annotation.AnnotationVO;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @since 4.1
*/
@Component
public class AnnotationDaoImpl extends GenericDaoBase<AnnotationVO, Long> implements AnnotationDao {
private final SearchBuilder<AnnotationVO> AnnotationSearchByType;
private final SearchBuilder<AnnotationVO> AnnotationSearchByTypeAndUuid;
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();
}
@Override public List<AnnotationVO> findByEntityType(String entityType) {
SearchCriteria<AnnotationVO> sc = createSearchCriteria();
sc.addAnd("entityType", SearchCriteria.Op.EQ, entityType);
return listBy(sc);
}
@Override public List<AnnotationVO> findByEntity(String entityType, String entityUuid) {
SearchCriteria<AnnotationVO> sc = createSearchCriteria();
sc.addAnd("entityType", SearchCriteria.Op.EQ, entityType);
sc.addAnd("entityUuid", SearchCriteria.Op.EQ, entityUuid);
return listBy(sc, null);
}
}

View File

@ -283,6 +283,7 @@
<property name="gslbServiceProviders" value="#{gslbServiceProvidersRegistry.registered}" /> <property name="gslbServiceProviders" value="#{gslbServiceProvidersRegistry.registered}" />
</bean> </bean>
<bean id="certServiceImpl" class="org.apache.cloudstack.network.ssl.CertServiceImpl" /> <bean id="certServiceImpl" class="org.apache.cloudstack.network.ssl.CertServiceImpl" />
<bean id="imageStoreUploadMonitorImpl" class="com.cloud.storage.ImageStoreUploadMonitorImpl" /> <bean id="imageStoreUploadMonitorImpl" class="com.cloud.storage.ImageStoreUploadMonitorImpl" />
<!-- the new CA manager --> <!-- the new CA manager -->
@ -290,4 +291,6 @@
<property name="caProviders" value="#{caProvidersRegistry.registered}" /> <property name="caProviders" value="#{caProvidersRegistry.registered}" />
</bean> </bean>
<bean id="annotationService" class="org.apache.cloudstack.annotation.AnnotationManagerImpl" />
</beans> </beans>

View File

@ -35,8 +35,6 @@ import org.apache.cloudstack.api.response.HostForMigrationResponse;
import org.apache.cloudstack.api.response.HostResponse; import org.apache.cloudstack.api.response.HostResponse;
import org.apache.cloudstack.api.response.VgpuResponse; import org.apache.cloudstack.api.response.VgpuResponse;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.ha.HAResource;
import org.apache.cloudstack.ha.dao.HAConfigDao;
import org.apache.cloudstack.outofbandmanagement.dao.OutOfBandManagementDao; import org.apache.cloudstack.outofbandmanagement.dao.OutOfBandManagementDao;
import com.cloud.api.ApiDBUtils; import com.cloud.api.ApiDBUtils;
@ -52,6 +50,9 @@ import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.SearchCriteria;
import org.apache.cloudstack.ha.HAResource;
import org.apache.cloudstack.ha.dao.HAConfigDao;
@Component @Component
public class HostJoinDaoImpl extends GenericDaoBase<HostJoinVO, Long> implements HostJoinDao { public class HostJoinDaoImpl extends GenericDaoBase<HostJoinVO, Long> implements HostJoinDao {
public static final Logger s_logger = Logger.getLogger(HostJoinDaoImpl.class); public static final Logger s_logger = Logger.getLogger(HostJoinDaoImpl.class);
@ -244,6 +245,9 @@ public class HostJoinDaoImpl extends GenericDaoBase<HostJoinVO, Long> implements
hostResponse.setJobId(host.getJobUuid()); hostResponse.setJobId(host.getJobUuid());
hostResponse.setJobStatus(host.getJobStatus()); hostResponse.setJobStatus(host.getJobStatus());
} }
hostResponse.setAnnotation(host.getAnnotation());
hostResponse.setLastAnnotated(host.getLastAnnotated ());
hostResponse.setUsername(host.getUsername());
hostResponse.setObjectName("host"); hostResponse.setObjectName("host");

View File

@ -27,15 +27,15 @@ import javax.persistence.Table;
import javax.persistence.Temporal; import javax.persistence.Temporal;
import javax.persistence.TemporalType; import javax.persistence.TemporalType;
import org.apache.cloudstack.api.Identity;
import org.apache.cloudstack.api.InternalIdentity;
import com.cloud.host.Host.Type; import com.cloud.host.Host.Type;
import com.cloud.host.Status; import com.cloud.host.Status;
import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.org.Cluster; import com.cloud.org.Cluster;
import com.cloud.resource.ResourceState; import com.cloud.resource.ResourceState;
import com.cloud.utils.StringUtils;
import com.cloud.utils.db.GenericDao; import com.cloud.utils.db.GenericDao;
import org.apache.cloudstack.api.Identity;
import org.apache.cloudstack.api.InternalIdentity;
import org.apache.cloudstack.ha.HAConfig; import org.apache.cloudstack.ha.HAConfig;
import org.apache.cloudstack.outofbandmanagement.OutOfBandManagement; import org.apache.cloudstack.outofbandmanagement.OutOfBandManagement;
@ -192,6 +192,15 @@ public class HostJoinVO extends BaseViewVO implements InternalIdentity, Identity
@Column(name = "job_status") @Column(name = "job_status")
private int jobStatus; private int jobStatus;
@Column(name = "annotation")
private String annotation;
@Column(name = "last_annotated")
private Date lastAnnotated;
@Column(name = "username")
private String username;
@Override @Override
public long getId() { public long getId() {
return this.id; return this.id;
@ -377,4 +386,20 @@ public class HostJoinVO extends BaseViewVO implements InternalIdentity, Identity
public String getTag() { public String getTag() {
return tag; return tag;
} }
public String getAnnotation() {
return annotation;
}
public Date getLastAnnotated() {
return lastAnnotated;
}
public String getUsername() {
return username;
}
public boolean isAnnotated() {
return StringUtils.isNotBlank(annotation);
}
} }

View File

@ -0,0 +1,149 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.annotation;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import com.cloud.event.ActionEvent;
import com.cloud.event.EventTypes;
import com.cloud.utils.component.ManagerBase;
import com.cloud.utils.component.PluggableService;
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.response.AnnotationResponse;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.log4j.Logger;
/**
* @since 4.11
*/
public final class AnnotationManagerImpl extends ManagerBase implements AnnotationService, PluggableService {
public static final Logger LOGGER = Logger.getLogger(AnnotationManagerImpl.class);
@Inject
private AnnotationDao annotationDao;
@Override
public ListResponse<AnnotationResponse> searchForAnnotations(ListAnnotationsCmd cmd) {
List<AnnotationVO> annotations = getAnnotationsForApiCmd(cmd);
List<AnnotationResponse> annotationResponses = convertAnnotationsToResponses(annotations);
return createAnnotationsResponseList(annotationResponses);
}
@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());
}
public AnnotationResponse addAnnotation(String text, EntityType type, String uuid) {
CallContext ctx = CallContext.current();
String userUuid = ctx.getCallingUserUuid();
AnnotationVO annotation = new AnnotationVO(text, type, uuid);
annotation.setUserUuid(userUuid);
annotation = annotationDao.persist(annotation);
return createAnnotationResponse(annotation);
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_ANNOTATION_REMOVE, eventDescription = "removing an annotation on an entity")
public AnnotationResponse removeAnnotation(RemoveAnnotationCmd removeAnnotationCmd) {
String uuid = removeAnnotationCmd.getUuid();
if(LOGGER.isDebugEnabled()) {
LOGGER.debug("marking annotation removed: " + uuid);
}
AnnotationVO annotation = annotationDao.findByUuid(uuid);
annotationDao.remove(annotation.getId());
return createAnnotationResponse(annotation);
}
private List<AnnotationVO> 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);
}
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);
}
} else {
if(LOGGER.isDebugEnabled()) {
LOGGER.debug("getting all annotations");
}
annotations = annotationDao.listAll();
}
return annotations;
}
private List<AnnotationResponse> convertAnnotationsToResponses(List<AnnotationVO> annotations) {
List<AnnotationResponse> annotationResponses = new ArrayList<>();
for (AnnotationVO annotation : annotations) {
annotationResponses.add(createAnnotationResponse(annotation));
}
return annotationResponses;
}
private ListResponse<AnnotationResponse> createAnnotationsResponseList(List<AnnotationResponse> annotationResponses) {
ListResponse<AnnotationResponse> listResponse = new ListResponse<>();
listResponse.setResponses(annotationResponses);
return listResponse;
}
public static AnnotationResponse createAnnotationResponse(AnnotationVO annotation) {
AnnotationResponse response = new AnnotationResponse();
response.setUuid(annotation.getUuid());
response.setEntityType(annotation.getEntityType());
response.setEntityUuid(annotation.getEntityUuid());
response.setAnnotation(annotation.getAnnotation());
response.setUserUuid(annotation.getUserUuid());
response.setCreated(annotation.getCreated());
response.setRemoved(annotation.getRemoved());
response.setObjectName("annotation");
return response;
}
@Override public List<Class<?>> getCommands() {
final List<Class<?>> cmdList = new ArrayList<>();
cmdList.add(AddAnnotationCmd.class);
cmdList.add(ListAnnotationsCmd.class);
cmdList.add(RemoveAnnotationCmd.class);
return cmdList;
}
}

View File

@ -159,10 +159,46 @@ CREATE TABLE IF NOT EXISTS `cloud`.`ha_config` (
DELETE from `cloud`.`configuration` where name='outofbandmanagement.sync.interval'; DELETE from `cloud`.`configuration` where name='outofbandmanagement.sync.interval';
-- Annotations specifc changes following
CREATE TABLE IF NOT EXISTS `cloud`.`annotations` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`uuid` varchar(40) UNIQUE,
`annotation` text,
`entity_uuid` varchar(40),
`entity_type` varchar(32),
`user_uuid` varchar(40),
`created` datetime COMMENT 'date of creation',
`removed` datetime COMMENT 'date of removal',
PRIMARY KEY (`id`),
KEY (`uuid`),
KEY `i_entity` (`entity_uuid`, `entity_type`, `created`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP VIEW IF EXISTS `cloud`.`last_annotation_view`;
CREATE VIEW `last_annotation_view` AS
SELECT
`annotations`.`uuid` AS `uuid`,
`annotations`.`annotation` AS `annotation`,
`annotations`.`entity_uuid` AS `entity_uuid`,
`annotations`.`entity_type` AS `entity_type`,
`annotations`.`user_uuid` AS `user_uuid`,
`annotations`.`created` AS `created`,
`annotations`.`removed` AS `removed`
FROM
`annotations`
WHERE
`annotations`.`created` IN (SELECT
MAX(`annotations`.`created`)
FROM
`annotations`
WHERE
`annotations`.`removed` IS NULL
GROUP BY `annotations`.`entity_uuid`);
-- Host HA changes: -- Host HA changes:
DROP VIEW IF EXISTS `cloud`.`host_view`; DROP VIEW IF EXISTS `cloud`.`host_view`;
CREATE VIEW `cloud`.`host_view` AS CREATE VIEW `cloud`.`host_view` AS
select SELECT
host.id, host.id,
host.uuid, host.uuid,
host.name, host.name,
@ -210,37 +246,46 @@ CREATE VIEW `cloud`.`host_view` AS
oobm.power_state AS `oobm_power_state`, oobm.power_state AS `oobm_power_state`,
ha_config.enabled AS `ha_enabled`, ha_config.enabled AS `ha_enabled`,
ha_config.ha_state AS `ha_state`, ha_config.ha_state AS `ha_state`,
ha_config.provider AS `ha_provider` ha_config.provider AS `ha_provider`,
from `last_annotation_view`.`annotation` AS `annotation`,
`last_annotation_view`.`created` AS `last_annotated`,
`user`.`username` AS `username`
FROM
`cloud`.`host` `cloud`.`host`
left join LEFT JOIN
`cloud`.`cluster` ON host.cluster_id = cluster.id `cloud`.`cluster` ON host.cluster_id = cluster.id
left join LEFT JOIN
`cloud`.`data_center` ON host.data_center_id = data_center.id `cloud`.`data_center` ON host.data_center_id = data_center.id
left join LEFT JOIN
`cloud`.`host_pod_ref` ON host.pod_id = host_pod_ref.id `cloud`.`host_pod_ref` ON host.pod_id = host_pod_ref.id
left join LEFT JOIN
`cloud`.`host_details` ON host.id = host_details.host_id `cloud`.`host_details` ON host.id = host_details.host_id
and host_details.name = 'guest.os.category.id' AND host_details.name = 'guest.os.category.id'
left join LEFT JOIN
`cloud`.`guest_os_category` ON guest_os_category.id = CONVERT( host_details.value , UNSIGNED) `cloud`.`guest_os_category` ON guest_os_category.id = CONVERT ( host_details.value, UNSIGNED )
left join LEFT JOIN
`cloud`.`host_tags` ON host_tags.host_id = host.id `cloud`.`host_tags` ON host_tags.host_id = host.id
left join LEFT JOIN
`cloud`.`op_host_capacity` mem_caps ON host.id = mem_caps.host_id `cloud`.`op_host_capacity` mem_caps ON host.id = mem_caps.host_id
and mem_caps.capacity_type = 0 AND mem_caps.capacity_type = 0
left join LEFT JOIN
`cloud`.`op_host_capacity` cpu_caps ON host.id = cpu_caps.host_id `cloud`.`op_host_capacity` cpu_caps ON host.id = cpu_caps.host_id
and cpu_caps.capacity_type = 1 AND cpu_caps.capacity_type = 1
left join LEFT JOIN
`cloud`.`async_job` ON async_job.instance_id = host.id `cloud`.`async_job` ON async_job.instance_id = host.id
and async_job.instance_type = 'Host' AND async_job.instance_type = 'Host'
and async_job.job_status = 0 AND async_job.job_status = 0
left join LEFT JOIN
`cloud`.`oobm` ON oobm.host_id = host.id `cloud`.`oobm` ON oobm.host_id = host.id
left join left join
`cloud`.`ha_config` ON ha_config.resource_id=host.id `cloud`.`ha_config` ON ha_config.resource_id=host.id
and ha_config.resource_type='Host'; and ha_config.resource_type='Host'
LEFT JOIN
`cloud`.`last_annotation_view` ON `last_annotation_view`.`entity_uuid` = `host`.`uuid`
LEFT JOIN
`cloud`.`user` ON `user`.`uuid` = `last_annotation_view`.`user_uuid`;
-- End Of Annotations specific changes
-- Out-of-band management driver for nested-cloudstack -- Out-of-band management driver for nested-cloudstack
ALTER TABLE `cloud`.`oobm` MODIFY COLUMN port VARCHAR(255); ALTER TABLE `cloud`.`oobm` MODIFY COLUMN port VARCHAR(255);

View File

@ -0,0 +1,178 @@
# 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.
""" BVT tests for Hosts and Clusters
"""
#Import Local Modules
import marvin
from marvin.cloudstackTestCase import *
from marvin.cloudstackAPI import *
from marvin.lib.utils import *
from marvin.lib.base import *
from marvin.lib.common import *
from marvin.lib.utils import (random_gen)
from nose.plugins.attrib import attr
#Import System modules
import time
_multiprocess_shared_ = True
class TestHostAnnotations(cloudstackTestCase):
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
def cleanAnnotations(self):
"""Remove annotations"""
for annotation in self.added_annotations:
self.removeAnnotation(annotation.annotation.id)
def addAnnotation(self, annotation):
cmd = addAnnotation.addAnnotationCmd()
cmd.entityid = self.host.id
cmd.entitytype = "HOST"
cmd.annotation = annotation
self.added_annotations.append(self.apiclient.addAnnotation(cmd))
return self.added_annotations[-1]
def removeAnnotation(self, id):
cmd = removeAnnotation.removeAnnotationCmd()
cmd.id = id
return self.apiclient.removeAnnotation(cmd)
def getHostAnnotation(self, hostId):
host = list_hosts(self.apiclient,
zoneid=self.zone.id,
type='Routing')[0]
return host.annotation
@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.assertEqual(self.added_annotations[-1].annotation.annotation, "annotation1")
@attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false")
def test_02_add_multiple_annotations(self):
"""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.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.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.assertEqual(self.added_annotations[-1].annotation.annotation, "annotation3")
#Check that the last one is visible in host details
self.assertEqual(self.getHostAnnotation(self.host.id), "annotation3")
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"""
self.addAnnotation("annotation1")
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))
except Exception:
pass
else:
self.fail("AddAnnotation is allowed for User")
cmd = listAnnotations.listAnnotationsCmd()
try:
userApiClient.listAnnotations(cmd)
except Exception:
pass
else:
self.fail("ListAnnotations is allowed for User")
cmd = removeAnnotation.removeAnnotationCmd()
cmd.id = self.added_annotations[-1].annotation.id
try:
userApiClient.removeAnnotation(cmd)
except Exception:
pass
else:
self.fail("RemoveAnnotation is allowed for User")
@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.removeAnnotation(self.added_annotations[-1].annotation.id)
del self.added_annotations[-1]
@attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false")
def test_05_add_annotation_for_invvalid_entityType(self):
cmd = addAnnotation.addAnnotationCmd()
cmd.entityid = self.host.id
cmd.entitytype = "BLA"
cmd.annotation = annotation
try:
self.apiclient.addAnnotation(cmd)
except CloudstackAPIException as f:
log.debug("error message %s" % f)
else:
self.fail("AddAnnotation is allowed for on an unknown entityType")
return self.added_annotations[-1]

View File

@ -181,6 +181,9 @@ known_categories = {
'deleteServicePackageOffering' : 'Load Balancer', 'deleteServicePackageOffering' : 'Load Balancer',
'destroyNsVpx' : 'Load Balancer', 'destroyNsVpx' : 'Load Balancer',
'startNsVpx' : 'Load Balancer', 'startNsVpx' : 'Load Balancer',
'listAnnotations' : 'Annotations',
'addAnnotation' : 'Annotations',
'removeAnnotation' : 'Annotations',
'CA': 'Certificate' 'CA': 'Certificate'
} }

View File

@ -427,6 +427,8 @@ var dictionary = {"ICMP.code":"ICMP Code",
"label.allocated":"Allocated", "label.allocated":"Allocated",
"label.allocation.state":"Allocation State", "label.allocation.state":"Allocation State",
"label.allow":"Allow", "label.allow":"Allow",
"label.annotated.by":"Annotator",
"label.annotation":"Annotation",
"label.anti.affinity":"Anti-affinity", "label.anti.affinity":"Anti-affinity",
"label.anti.affinity.group":"Anti-affinity Group", "label.anti.affinity.group":"Anti-affinity Group",
"label.anti.affinity.groups":"Anti-affinity Groups", "label.anti.affinity.groups":"Anti-affinity Groups",
@ -944,6 +946,7 @@ var dictionary = {"ICMP.code":"ICMP Code",
"label.lang.polish":"Polish", "label.lang.polish":"Polish",
"label.lang.russian":"Russian", "label.lang.russian":"Russian",
"label.lang.spanish":"Spanish", "label.lang.spanish":"Spanish",
"label.last.annotated":"Last annotation date",
"label.last.disconnected":"Last Disconnected", "label.last.disconnected":"Last Disconnected",
"label.last.name":"Last Name", "label.last.name":"Last Name",
"label.lastname.lower":"lastname", "label.lastname.lower":"lastname",

View File

@ -16062,7 +16062,10 @@
array1.push("&hosttags=" + todb(args.data.hosttags)); array1.push("&hosttags=" + todb(args.data.hosttags));
if (args.data.oscategoryid != null && args.data.oscategoryid.length > 0) if (args.data.oscategoryid != null && args.data.oscategoryid.length > 0)
array1.push("&osCategoryId=" + args.data.oscategoryid); array1.push("&osCategoryId=" + args.data.oscategoryid);
if (args.data.annotation != null && args.data.annotation.length > 0)
array1.push("&annotation=" + args.data.annotation);
$.ajax({ $.ajax({
url: createURL("updateHost&id=" + args.context.hosts[0].id + array1.join("")), url: createURL("updateHost&id=" + args.context.hosts[0].id + array1.join("")),
@ -17073,11 +17076,22 @@
ipaddress: { ipaddress: {
label: 'label.ip.address' label: 'label.ip.address'
}, },
annotation: {
label: 'label.annotation',
isEditable: true
},
lastannotated: {
label: 'label.last.annotated',
converter: cloudStack.converters.toLocalDate
},
username: {
label: 'label.annotated.by'
},
disconnected: { disconnected: {
label: 'label.last.disconnected' label: 'label.last.disconnected'
}, },
cpusockets: { cpusockets: {
label: 'label.number.of.cpu.sockets' label: 'label.number.of.cpu.sockets'
} }
}, { }, {
@ -17099,12 +17113,17 @@
if (item && item.outofbandmanagement) { if (item && item.outofbandmanagement) {
item.powerstate = item.outofbandmanagement.powerstate; item.powerstate = item.outofbandmanagement.powerstate;
} }
if (item && item.hostha) { if (item && item.hostha) {
item.hastate = item.hostha.hastate; item.hastate = item.hostha.hastate;
item.haprovider = item.hostha.haprovider; item.haprovider = item.hostha.haprovider;
item.haenabled = item.hostha.haenable; item.haenabled = item.hostha.haenable;
} }
item.annotation = item.annotation;
item.lastannotated = item.lastannotated;
item.username = item.username;
$.ajax({ $.ajax({
url: createURL("listDedicatedHosts&hostid=" + args.context.hosts[0].id), url: createURL("listDedicatedHosts&hostid=" + args.context.hosts[0].id),
dataType: "json", dataType: "json",