/*
* Copyright (c) 2016 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.api.service.impl.resource;
import static com.emc.storageos.api.mapper.DbObjectMapper.map;
import java.net.URI;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import com.emc.storageos.api.mapper.functions.MapSchedulePolicy;
import com.emc.storageos.api.service.impl.response.BulkList;
import com.emc.storageos.db.client.model.NamedURI;
import com.emc.storageos.db.client.model.SchedulePolicy;
import com.emc.storageos.db.client.model.SchedulePolicy.ScheduleFrequency;
import com.emc.storageos.db.client.model.SchedulePolicy.SchedulePolicyType;
import com.emc.storageos.db.client.model.SchedulePolicy.SnapshotExpireType;
import com.emc.storageos.db.client.model.StringSet;
import com.emc.storageos.db.client.util.NullColumnValueGetter;
import com.emc.storageos.model.ResourceTypeEnum;
import com.emc.storageos.model.file.ScheduleSnapshotExpireParam;
import com.emc.storageos.model.schedulepolicy.PolicyParam;
import com.emc.storageos.model.schedulepolicy.SchedulePolicyBulkRep;
import com.emc.storageos.model.schedulepolicy.SchedulePolicyParam;
import com.emc.storageos.model.schedulepolicy.SchedulePolicyRestRep;
import com.emc.storageos.security.authorization.CheckPermission;
import com.emc.storageos.security.authorization.DefaultPermissions;
import com.emc.storageos.security.authorization.Role;
import com.emc.storageos.svcs.errorhandling.resources.APIException;
import com.emc.storageos.volumecontroller.impl.monitoring.RecordableEventManager;
/**
* SchedulePolicyService resource implementation
*
* @author prasaa9
*
*/
@Path("/schedule-policies")
@DefaultPermissions(readRoles = { Role.TENANT_ADMIN, Role.SYSTEM_MONITOR },
writeRoles = { Role.TENANT_ADMIN })
public class SchedulePolicyService extends TaggedResource {
private static final Logger _log = LoggerFactory.getLogger(SchedulePolicyService.class);
protected static final String EVENT_SERVICE_SOURCE = "SchedulePolicyService";
private static final String EVENT_SERVICE_TYPE = "schedulePolicy";
@Autowired
private RecordableEventManager _evtMgr;
@Autowired
private NetworkService networkSvc;
@Override
public String getServiceType() {
return EVENT_SERVICE_TYPE;
}
@Override
protected ResourceTypeEnum getResourceType() {
return ResourceTypeEnum.SCHEDULE_POLICY;
}
@SuppressWarnings("unchecked")
@Override
public Class<SchedulePolicy> getResourceClass() {
return SchedulePolicy.class;
}
/**
* Get schedule policy object from id
*
* @param id the URN of a CoprHD Schedule Policy
* @return
*/
private SchedulePolicy getPolicyById(URI id, boolean checkInactive) {
if (id == null) {
return null;
}
SchedulePolicy schedulePolicy = _permissionsHelper.getObjectById(id, SchedulePolicy.class);
ArgValidator.checkEntity(schedulePolicy, id, isIdEmbeddedInURL(id));
return schedulePolicy;
}
@Override
protected SchedulePolicy queryResource(URI id) {
ArgValidator.checkUri(id);
return getPolicyById(id, false);
}
@Override
protected URI getTenantOwner(URI id) {
SchedulePolicy schedulePolicy = queryResource(id);
return schedulePolicy.getTenantOrg().getURI();
}
/**
* Get the details of a schedule policy.
*
* @param policyId the URN of a schedule policy.
*
* @brief Show schedule policy
* @return A SchedulePolicyRestRep reference specifying the data for the
* schedule policy with the passed policyId.
*/
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}")
@CheckPermission(roles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN })
public SchedulePolicyRestRep getSchedulePolicy(@PathParam("id") URI policyId) {
ArgValidator.checkFieldUriType(policyId, SchedulePolicy.class, "policyId");
SchedulePolicy schedulePolicy = queryResource(policyId);
return map(schedulePolicy);
}
/**
* Update info for schedule policy including name, schedule, snapshot expire etc.
*
* @param policyId the URN of a schedule policy
* @param param schedule policy update parameters
* @brief Update schedule policy
* @return No data returned in response body
* @throws BadRequestException
*/
@PUT
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}")
@CheckPermission(roles = { Role.TENANT_ADMIN })
public Response updateSchedulePolicy(@PathParam("id") URI policyId, PolicyParam param) {
// check policy
ArgValidator.checkFieldUriType(policyId, SchedulePolicy.class, "policyId");
// Check policy is associated with FS before update
SchedulePolicy schedulePolicy = getPolicyById(policyId, true);
StringSet resources = schedulePolicy.getAssignedResources();
if (resources != null && !resources.isEmpty()) {
_log.error("Unable to update schedule policy {} as it is already associated with resources",
schedulePolicy.getPolicyName());
throw APIException.badRequests.unableToUpdateSchedulePolicy(schedulePolicy.getPolicyName());
}
// check schedule policy type is valid or not
if (!ArgValidator.isValidEnum(param.getPolicyType(), SchedulePolicyType.class)) {
throw APIException.badRequests.invalidSchedulePolicyType(param.getPolicyType());
}
_log.info("Schedule policy {} update started", schedulePolicy.getPolicyName());
if (null != param.getPolicyName() && !param.getPolicyName().isEmpty() &&
!schedulePolicy.getLabel().equalsIgnoreCase(param.getPolicyName())) {
checkForDuplicateName(param.getPolicyName(), SchedulePolicy.class, schedulePolicy.getTenantOrg()
.getURI(), "tenantOrg", _dbClient);
NamedURI tenant = schedulePolicy.getTenantOrg();
if (tenant != null) {
tenant.setName(param.getPolicyName());
schedulePolicy.setTenantOrg(tenant);
}
}
// Validate Schedule policy parameters
StringBuilder errorMsg = new StringBuilder();
boolean isValidSchedule = validateSchedulePolicyParam(param.getPolicySchedule(), schedulePolicy, errorMsg);
if (!isValidSchedule && errorMsg != null && errorMsg.length() > 0) {
_log.error("Failed to update schedule policy due to {} ", errorMsg.toString());
throw APIException.badRequests.invalidSchedulePolicyParam(param.getPolicyName(), errorMsg.toString());
}
// Validate snapshot expire parameters
boolean isValidSnapshotExpire = false;
if (param.getSnapshotExpire() != null) {
String expireType = param.getSnapshotExpire().getExpireType();
if (!ArgValidator.isValidEnum(expireType, SnapshotExpireType.class)) {
_log.error(
"Invalid schedule snapshot expire type {}. Valid Snapshot expire types are hours, days, weeks, months and never",
expireType);
throw APIException.badRequests.invalidScheduleSnapshotExpireType(expireType);
}
isValidSnapshotExpire = validateSnapshotExpireParam(param.getSnapshotExpire());
if (!isValidSnapshotExpire) {
int expireTime = param.getSnapshotExpire().getExpireValue();
int minExpireTime = 2;
int maxExpireTime = 10;
_log.error("Invalid schedule snapshot expire time {}. Try an expire time between {} hours to {} years",
expireTime, minExpireTime, maxExpireTime);
throw APIException.badRequests.invalidScheduleSnapshotExpireValue(expireTime, minExpireTime, maxExpireTime);
}
} else {
if (param.getPolicyType().equalsIgnoreCase(SchedulePolicyType.file_snapshot.toString())) {
errorMsg.append("Required parameter snapshot_expire was missing or empty");
_log.error("Failed to update schedule policy due to {} ", errorMsg.toString());
throw APIException.badRequests.invalidSchedulePolicyParam(param.getPolicyName(), errorMsg.toString());
}
}
if (isValidSchedule) {
schedulePolicy.setPolicyType(param.getPolicyType());
schedulePolicy.setLabel(param.getPolicyName());
schedulePolicy.setPolicyName(param.getPolicyName());
schedulePolicy.setScheduleFrequency(param.getPolicySchedule().getScheduleFrequency().toLowerCase());
if (isValidSnapshotExpire) {
schedulePolicy.setSnapshotExpireType(param.getSnapshotExpire().getExpireType().toLowerCase());
if (!param.getSnapshotExpire().getExpireType().equalsIgnoreCase(SnapshotExpireType.NEVER.toString())) {
schedulePolicy.setSnapshotExpireTime((long) param.getSnapshotExpire().getExpireValue());
} else {
schedulePolicy.setSnapshotExpireTime(0L);
}
}
_dbClient.updateObject(schedulePolicy);
_log.info("Schedule policy {} updated successfully", schedulePolicy.getPolicyName());
}
return Response.ok().build();
}
/**
* Delete schedule policy from CoprHD DB.
*
* @param policyId the URN of a schedule policy
* @brief Delete schedule policy from CoprHD DB
* @return No data returned in response body
* @throws BadRequestException
*/
@DELETE
@Path("/{id}")
@CheckPermission(roles = { Role.TENANT_ADMIN })
public Response deleteSchedulePolicy(@PathParam("id") URI policyId) {
ArgValidator.checkFieldUriType(policyId, SchedulePolicy.class, "policyId");
SchedulePolicy schedulePolicy = getPolicyById(policyId, true);
ArgValidator.checkReference(SchedulePolicy.class, policyId, checkForDelete(schedulePolicy));
// Check policy is associated with FS before delete
StringSet resources = schedulePolicy.getAssignedResources();
if (resources != null && !resources.isEmpty()) {
_log.error("Unable to delete schedule policy {} as it is already associated with resources",
schedulePolicy.getPolicyName());
throw APIException.badRequests.unableToDeleteSchedulePolicy(schedulePolicy.getPolicyName());
}
_dbClient.markForDeletion(schedulePolicy);
_log.info("Schedule policy {} deleted successfully", schedulePolicy.getPolicyName());
return Response.ok().build();
}
@Override
public SchedulePolicyBulkRep queryBulkResourceReps(List<URI> ids) {
Iterator<SchedulePolicy> _dbIterator = _dbClient.queryIterativeObjects(
getResourceClass(), ids);
return new SchedulePolicyBulkRep(BulkList.wrapping(_dbIterator,
MapSchedulePolicy.getInstance(_dbClient)));
}
@Override
public SchedulePolicyBulkRep queryFilteredBulkResourceReps(List<URI> ids) {
Iterator<SchedulePolicy> _dbIterator = _dbClient.queryIterativeObjects(
getResourceClass(), ids);
BulkList.ResourceFilter filter = new BulkList.SchedulePolicyFilter(getUserFromContext(), _permissionsHelper);
return new SchedulePolicyBulkRep(BulkList.wrapping(_dbIterator,
MapSchedulePolicy.getInstance(_dbClient), filter));
}
/**
* validates whether the schedule policy parameters are valid or not
*
* @param schedule - schedule policy parameters
* @param schedulePolicy - schedulePolicy object to set schedule values
* @param errorMsg - error message
* @return true/false
*/
public static boolean validateSchedulePolicyParam(SchedulePolicyParam schedule, SchedulePolicy schedulePolicy, StringBuilder errorMsg) {
if (schedule != null) {
// check schedule frequency is valid or not
if (!ArgValidator.isValidEnum(schedule.getScheduleFrequency(), ScheduleFrequency.class)) {
errorMsg.append("Schedule frequency: " + schedule.getScheduleFrequency()
+ " is invalid. Valid schedule frequencies are days, weeks and months");
return false;
}
// validating schedule repeat period
if (schedule.getScheduleRepeat() < 1) {
errorMsg.append("required parameter schedule_repeat is missing or value: " + schedule.getScheduleRepeat()
+ " is invalid");
return false;
}
// validating schedule time
String period = " PM";
int hour, minute;
boolean isValid = true;
if (schedule.getScheduleTime().contains(":")) {
String splitTime[] = schedule.getScheduleTime().split(":");
hour = Integer.parseInt(splitTime[0]);
minute = Integer.parseInt(splitTime[1]);
if (splitTime[0].startsWith("-") || splitTime[1].startsWith("-")) {
isValid = false;
}
} else {
hour = Integer.parseInt(schedule.getScheduleTime());
minute = 0;
}
if (isValid && (hour >= 0 && hour < 24) && (minute >= 0 && minute < 60)) {
if (hour < 12) {
period = " AM";
}
} else {
errorMsg.append("Schedule time: " + schedule.getScheduleTime() + " is invalid");
return false;
}
ScheduleFrequency scheduleFreq = ScheduleFrequency.valueOf(schedule.getScheduleFrequency().toUpperCase());
switch (scheduleFreq) {
case DAYS:
schedulePolicy.setScheduleRepeat((long) schedule.getScheduleRepeat());
schedulePolicy.setScheduleTime(schedule.getScheduleTime() + period);
if (schedulePolicy.getScheduleDayOfWeek() != null && !schedulePolicy.getScheduleDayOfWeek().isEmpty()) {
schedulePolicy.setScheduleDayOfWeek(NullColumnValueGetter.getNullStr());
}
if (schedulePolicy.getScheduleDayOfMonth() != null) {
schedulePolicy.setScheduleDayOfMonth(0L);
}
break;
case WEEKS:
schedulePolicy.setScheduleRepeat((long) schedule.getScheduleRepeat());
if (schedule.getScheduleDayOfWeek() != null && !schedule.getScheduleDayOfWeek().isEmpty()) {
List<String> weeks = Arrays.asList("monday", "tuesday", "wednesday", "thursday", "friday",
"saturday", "sunday");
if (weeks.contains(schedule.getScheduleDayOfWeek().toLowerCase())) {
schedulePolicy.setScheduleDayOfWeek(schedule.getScheduleDayOfWeek().toLowerCase());
} else {
errorMsg.append("Schedule day of week: " + schedule.getScheduleDayOfWeek() + " is invalid");
return false;
}
} else {
errorMsg.append("required parameter schedule_day_of_week was missing or empty");
return false;
}
schedulePolicy.setScheduleTime(schedule.getScheduleTime() + period);
if (schedulePolicy.getScheduleDayOfMonth() != null) {
schedulePolicy.setScheduleDayOfMonth(0L);
}
break;
case MONTHS:
if (schedule.getScheduleDayOfMonth() > 0 && schedule.getScheduleDayOfMonth() <= 31) {
schedulePolicy.setScheduleDayOfMonth((long) schedule.getScheduleDayOfMonth());
schedulePolicy.setScheduleRepeat((long) schedule.getScheduleRepeat());
schedulePolicy.setScheduleTime(schedule.getScheduleTime() + period);
if (schedulePolicy.getScheduleDayOfWeek() != null) {
schedulePolicy.setScheduleDayOfWeek(NullColumnValueGetter.getNullStr());
}
} else {
errorMsg.append("required parameter schedule_day_of_month is missing or value: " + schedule.getScheduleDayOfMonth()
+ " is invalid");
return false;
}
break;
default:
return false;
}
}
return true;
}
/**
* validates whether the snapshot expire parameters are valid or not
*
* @param expireParam - snapshot expire parameters
* @return true/false
*/
public static boolean validateSnapshotExpireParam(ScheduleSnapshotExpireParam expireParam) {
long seconds = 0;
long minPeriod = 7200;
long maxPeriod = 10 * 365 * 24 * 3600;
int expireValue = expireParam.getExpireValue();
SnapshotExpireType expireType = SnapshotExpireType.valueOf(expireParam.getExpireType().toUpperCase());
switch (expireType) {
case HOURS:
seconds = TimeUnit.HOURS.toSeconds(expireValue);
break;
case DAYS:
seconds = TimeUnit.DAYS.toSeconds(expireValue);
break;
case WEEKS:
seconds = TimeUnit.DAYS.toSeconds(expireValue * 7);
break;
case MONTHS:
seconds = TimeUnit.DAYS.toSeconds(expireValue * 30);
break;
case NEVER:
return true;
default:
return false;
}
if (seconds >= minPeriod && seconds <= maxPeriod) {
return true;
}
return false;
}
}