mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
Merge branch 'master' into api_limit
Conflicts: api/src/org/apache/cloudstack/api/BaseCmd.java server/src/com/cloud/api/ApiServer.java server/src/com/cloud/api/ApiServlet.java Signed-off-by: Min Chen <min.chen@citrix.com>
This commit is contained in:
commit
c1a540c6bb
40
api/src/com/cloud/agent/api/storage/ResizeVolumeAnswer.java
Normal file
40
api/src/com/cloud/agent/api/storage/ResizeVolumeAnswer.java
Normal file
@ -0,0 +1,40 @@
|
||||
// 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.agent.api.storage;
|
||||
|
||||
import com.cloud.agent.api.Answer;
|
||||
|
||||
public class ResizeVolumeAnswer extends Answer {
|
||||
private long newSize;
|
||||
|
||||
protected ResizeVolumeAnswer() {
|
||||
|
||||
}
|
||||
|
||||
public ResizeVolumeAnswer(ResizeVolumeCommand cmd, boolean result, String details, long newSize) {
|
||||
super(cmd, result, details);
|
||||
this.newSize = newSize;
|
||||
}
|
||||
|
||||
public ResizeVolumeAnswer(ResizeVolumeCommand cmd, boolean result, String details) {
|
||||
super(cmd, result, details);
|
||||
}
|
||||
|
||||
public long getNewSize() {
|
||||
return newSize;
|
||||
}
|
||||
}
|
||||
86
api/src/com/cloud/agent/api/storage/ResizeVolumeCommand.java
Normal file
86
api/src/com/cloud/agent/api/storage/ResizeVolumeCommand.java
Normal file
@ -0,0 +1,86 @@
|
||||
// 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.agent.api.storage;
|
||||
|
||||
import com.cloud.agent.api.Command;
|
||||
import com.cloud.agent.api.to.StorageFilerTO;
|
||||
import com.cloud.storage.StoragePool;
|
||||
|
||||
public class ResizeVolumeCommand extends Command {
|
||||
private String path;
|
||||
private StorageFilerTO pool;
|
||||
private String vmInstance;
|
||||
private Long newSize;
|
||||
private Long currentSize;
|
||||
private boolean shrinkOk;
|
||||
|
||||
protected ResizeVolumeCommand() {
|
||||
|
||||
}
|
||||
|
||||
public ResizeVolumeCommand(String path,
|
||||
StorageFilerTO pool,
|
||||
Long currentSize,
|
||||
Long newSize,
|
||||
boolean shrinkOk,
|
||||
String vmInstance)
|
||||
{
|
||||
this.path = path;
|
||||
this.pool = pool;
|
||||
this.vmInstance = vmInstance;
|
||||
this.currentSize = currentSize;
|
||||
this.newSize = newSize;
|
||||
this.shrinkOk = shrinkOk;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public String getPoolUuid() {
|
||||
return pool.getUuid();
|
||||
}
|
||||
|
||||
public StorageFilerTO getPool() {
|
||||
return pool;
|
||||
}
|
||||
|
||||
public long getNewSize() {
|
||||
return newSize;
|
||||
}
|
||||
|
||||
public long getCurrentSize() {
|
||||
return currentSize;
|
||||
}
|
||||
|
||||
public boolean getShrinkOk() {
|
||||
return shrinkOk;
|
||||
}
|
||||
|
||||
public String getInstanceName() {
|
||||
return vmInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean executeInSequence() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@ -108,6 +108,7 @@ public class EventTypes {
|
||||
public static final String EVENT_VOLUME_EXTRACT = "VOLUME.EXTRACT";
|
||||
public static final String EVENT_VOLUME_UPLOAD = "VOLUME.UPLOAD";
|
||||
public static final String EVENT_VOLUME_MIGRATE = "VOLUME.MIGRATE";
|
||||
public static final String EVENT_VOLUME_RESIZE = "VOLUME.RESIZE";
|
||||
|
||||
// Domains
|
||||
public static final String EVENT_DOMAIN_CREATE = "DOMAIN.CREATE";
|
||||
|
||||
@ -54,7 +54,6 @@ public class CloudException extends Exception {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
public ArrayList<String> getIdProxyList() {
|
||||
return idList;
|
||||
}
|
||||
|
||||
@ -23,6 +23,7 @@ import org.apache.cloudstack.api.command.admin.storage.CancelPrimaryStorageMaint
|
||||
import org.apache.cloudstack.api.command.admin.storage.UpdateStoragePoolCmd;
|
||||
import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd;
|
||||
import org.apache.cloudstack.api.command.user.volume.UploadVolumeCmd;
|
||||
import org.apache.cloudstack.api.command.user.volume.ResizeVolumeCmd;
|
||||
import com.cloud.exception.ConcurrentOperationException;
|
||||
import com.cloud.exception.InsufficientCapacityException;
|
||||
import com.cloud.exception.PermissionDeniedException;
|
||||
@ -70,6 +71,15 @@ public interface StorageService{
|
||||
Volume createVolume(CreateVolumeCmd cmd);
|
||||
|
||||
|
||||
/**
|
||||
* Resizes the volume based on the given criteria
|
||||
*
|
||||
* @param cmd
|
||||
* the API command wrapping the criteria
|
||||
* @return the volume object
|
||||
*/
|
||||
Volume resizeVolume(ResizeVolumeCmd cmd);
|
||||
|
||||
/**
|
||||
* Delete the storage pool
|
||||
*
|
||||
|
||||
@ -36,6 +36,7 @@ public interface Volume extends ControlledEntity, Identity, InternalIdentity, Ba
|
||||
Ready("The volume is ready to be used."),
|
||||
Migrating("The volume is migrating to other storage pool"),
|
||||
Snapshotting("There is a snapshot created on this volume, not backed up to secondary storage yet"),
|
||||
Resizing("The volume is being resized"),
|
||||
Expunging("The volume is being expunging"),
|
||||
Destroy("The volume is destroyed, and can't be recovered."),
|
||||
UploadOp ("The volume upload operation is in progress or in short the volume is on secondary storage");
|
||||
@ -62,7 +63,10 @@ public interface Volume extends ControlledEntity, Identity, InternalIdentity, Ba
|
||||
s_fsm.addTransition(Creating, Event.OperationFailed, Allocated);
|
||||
s_fsm.addTransition(Creating, Event.OperationSucceeded, Ready);
|
||||
s_fsm.addTransition(Creating, Event.DestroyRequested, Destroy);
|
||||
s_fsm.addTransition(Creating, Event.CreateRequested, Creating);
|
||||
s_fsm.addTransition(Creating, Event.CreateRequested, Creating);
|
||||
s_fsm.addTransition(Ready, Event.ResizeRequested, Resizing);
|
||||
s_fsm.addTransition(Resizing, Event.OperationSucceeded, Ready);
|
||||
s_fsm.addTransition(Resizing, Event.OperationFailed, Ready);
|
||||
s_fsm.addTransition(Allocated, Event.UploadRequested, UploadOp);
|
||||
s_fsm.addTransition(UploadOp, Event.CopyRequested, Creating);// CopyRequested for volume from sec to primary storage
|
||||
s_fsm.addTransition(Creating, Event.CopySucceeded, Ready);
|
||||
@ -92,7 +96,8 @@ public interface Volume extends ControlledEntity, Identity, InternalIdentity, Ba
|
||||
MigrationRequested,
|
||||
SnapshotRequested,
|
||||
DestroyRequested,
|
||||
ExpungingRequested;
|
||||
ExpungingRequested,
|
||||
ResizeRequested;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -387,6 +387,7 @@ public class ApiConstants {
|
||||
public static final String ESP_LIFETIME = "esplifetime";
|
||||
public static final String DPD = "dpd";
|
||||
public static final String FOR_VPC = "forvpc";
|
||||
public static final String SHRINK_OK = "shrinkok";
|
||||
public static final String NICIRA_NVP_DEVICE_ID = "nvpdeviceid";
|
||||
public static final String NICIRA_NVP_TRANSPORT_ZONE_UUID = "transportzoneuuid";
|
||||
public static final String NICIRA_NVP_DEVICE_NAME = "niciradevicename";
|
||||
|
||||
@ -81,7 +81,6 @@ public abstract class BaseCmd {
|
||||
BOOLEAN, DATE, FLOAT, INTEGER, SHORT, LIST, LONG, OBJECT, MAP, STRING, TZDATE, UUID
|
||||
}
|
||||
|
||||
|
||||
public static final DateFormat INPUT_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
|
||||
public static final DateFormat NEW_INPUT_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
public static Pattern newInputDateFormat = Pattern.compile("[\\d]+-[\\d]+-[\\d]+ [\\d]+:[\\d]+:[\\d]+");
|
||||
|
||||
0
api/src/org/apache/cloudstack/api/BaseUpdateTemplateOrIsoCmd.java
Executable file → Normal file
0
api/src/org/apache/cloudstack/api/BaseUpdateTemplateOrIsoCmd.java
Executable file → Normal file
0
api/src/org/apache/cloudstack/api/BaseUpdateTemplateOrIsoPermissionsCmd.java
Executable file → Normal file
0
api/src/org/apache/cloudstack/api/BaseUpdateTemplateOrIsoPermissionsCmd.java
Executable file → Normal 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.api.command.user.volume;
|
||||
|
||||
import org.apache.cloudstack.api.response.*;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.ApiErrorCode;
|
||||
import org.apache.cloudstack.api.BaseAsyncCmd;
|
||||
import org.apache.cloudstack.api.BaseCmd;
|
||||
import org.apache.cloudstack.api.BaseListTaggedResourcesCmd;
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import com.cloud.exception.InvalidParameterValueException;
|
||||
import com.cloud.exception.PermissionDeniedException;
|
||||
import com.cloud.exception.ResourceAllocationException;
|
||||
import com.cloud.async.AsyncJob;
|
||||
import com.cloud.event.EventTypes;
|
||||
import com.cloud.projects.Project;
|
||||
import com.cloud.storage.Volume;
|
||||
import com.cloud.user.Account;
|
||||
import com.cloud.user.UserContext;
|
||||
|
||||
|
||||
@APICommand(name="resizeVolume", description="Resizes a volume", responseObject=VolumeResponse.class)
|
||||
public class ResizeVolumeCmd extends BaseAsyncCmd {
|
||||
public static final Logger s_logger = Logger.getLogger(ResizeVolumeCmd.class.getName());
|
||||
|
||||
private static final String s_name = "resizevolumeresponse";
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
//////////////// API parameters /////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
@Parameter(name=ApiConstants.ID, entityType=VolumeResponse.class, type=CommandType.UUID, description="the ID of the disk volume")
|
||||
private Long id;
|
||||
|
||||
@Parameter(name=ApiConstants.SIZE, type=CommandType.LONG, required=false, description="New volume size in G")
|
||||
private Long size;
|
||||
|
||||
@Parameter(name=ApiConstants.SHRINK_OK, type=CommandType.BOOLEAN, required=false, description="Verify OK to Shrink")
|
||||
private boolean shrinkOk;
|
||||
|
||||
@Parameter(name=ApiConstants.DISK_OFFERING_ID, entityType=DiskOfferingResponse.class, type=CommandType.UUID, required=false, description="new disk offering id")
|
||||
private Long newDiskOfferingId;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////////// Accessors ///////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
public Long getEntityId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Long getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public boolean getShrinkOk() {
|
||||
return shrinkOk;
|
||||
}
|
||||
|
||||
public Long getNewDiskOfferingId() {
|
||||
return newDiskOfferingId;
|
||||
}
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////// API Implementation///////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public String getCommandName() {
|
||||
return s_name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncJob.Type getInstanceType() {
|
||||
return AsyncJob.Type.Volume;
|
||||
}
|
||||
|
||||
public static String getResultObjectName() {
|
||||
return "volume";
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEntityOwnerId() {
|
||||
|
||||
Volume volume = _entityMgr.findById(Volume.class, getEntityId());
|
||||
if (volume == null) {
|
||||
throw new InvalidParameterValueException("Unable to find volume by id=" + id);
|
||||
}
|
||||
|
||||
Account account = _accountService.getAccount(volume.getAccountId());
|
||||
//Can resize volumes for enabled projects/accounts only
|
||||
if (account.getType() == Account.ACCOUNT_TYPE_PROJECT) {
|
||||
Project project = _projectService.findByProjectAccountId(volume.getAccountId());
|
||||
if (project.getState() != Project.State.Active) {
|
||||
throw new PermissionDeniedException("Can't add resources to project id=" + project.getId() + " in state=" + project.getState() + " as it's no longer active");
|
||||
}
|
||||
} else if (account.getState() == Account.State.disabled) {
|
||||
throw new PermissionDeniedException("The owner of volume " + id + " is disabled: " + account);
|
||||
}
|
||||
|
||||
return volume.getAccountId();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getEventType() {
|
||||
return EventTypes.EVENT_VOLUME_RESIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventDescription() {
|
||||
return "Volume Id: " + getEntityId() + " to size " + getSize() + "G" ;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(){
|
||||
UserContext.current().setEventDetails("Volume Id: " + getEntityId() + " to size " + getSize() + "G");
|
||||
Volume volume = _storageService.resizeVolume(this);
|
||||
if (volume != null) {
|
||||
VolumeResponse response = _responseGenerator.createVolumeResponse(volume);
|
||||
//FIXME - have to be moved to ApiResponseHelper
|
||||
response.setResponseName(getCommandName());
|
||||
this.setResponseObject(response);
|
||||
} else {
|
||||
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to resize volume");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,199 @@
|
||||
// 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 src.com.cloud.agent.api.test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.cloud.agent.api.storage.ResizeVolumeCommand;
|
||||
import com.cloud.agent.api.to.StorageFilerTO;
|
||||
import com.cloud.storage.StoragePool;
|
||||
import com.cloud.storage.Storage.StoragePoolType;
|
||||
import com.cloud.storage.StoragePoolStatus;
|
||||
|
||||
|
||||
public class ResizeVolumeCommandTest {
|
||||
|
||||
public StoragePool dummypool = new StoragePool() {
|
||||
public long getId() {
|
||||
return 1L;
|
||||
};
|
||||
|
||||
public String getName() {
|
||||
return "name";
|
||||
};
|
||||
|
||||
public String getUuid() {
|
||||
return "bed9f83e-cac3-11e1-ac8a-0050568b007e";
|
||||
};
|
||||
|
||||
public StoragePoolType getPoolType() {
|
||||
return StoragePoolType.Filesystem;
|
||||
};
|
||||
|
||||
public Date getCreated() {
|
||||
Date date = null;
|
||||
try {
|
||||
date = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss")
|
||||
.parse("01/01/1970 12:12:12");
|
||||
} catch (ParseException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return date;
|
||||
}
|
||||
|
||||
public Date getUpdateTime() {
|
||||
return new Date();
|
||||
};
|
||||
|
||||
public long getDataCenterId() {
|
||||
return 0L;
|
||||
};
|
||||
|
||||
public long getCapacityBytes() {
|
||||
return 0L;
|
||||
};
|
||||
|
||||
public long getAvailableBytes() {
|
||||
return 0L;
|
||||
};
|
||||
|
||||
public Long getClusterId() {
|
||||
return 0L;
|
||||
};
|
||||
|
||||
public String getHostAddress() {
|
||||
return "hostAddress";
|
||||
};
|
||||
|
||||
public String getPath() {
|
||||
return "path";
|
||||
};
|
||||
|
||||
public String getUserInfo() {
|
||||
return "userInfo";
|
||||
};
|
||||
|
||||
public boolean isShared() {
|
||||
return false;
|
||||
};
|
||||
|
||||
public boolean isLocal() {
|
||||
return false;
|
||||
};
|
||||
|
||||
public StoragePoolStatus getStatus() {
|
||||
return StoragePoolStatus.Up;
|
||||
};
|
||||
|
||||
public int getPort() {
|
||||
return 25;
|
||||
};
|
||||
|
||||
public Long getPodId() {
|
||||
return 0L;
|
||||
};
|
||||
};
|
||||
|
||||
Long newSize = 4194304L;
|
||||
Long currentSize = 1048576L;
|
||||
|
||||
ResizeVolumeCommand rv = new ResizeVolumeCommand("dummydiskpath",
|
||||
new StorageFilerTO(dummypool), currentSize, newSize, false,
|
||||
"vmName");
|
||||
|
||||
@Test
|
||||
public void testExecuteInSequence() {
|
||||
boolean b = rv.executeInSequence();
|
||||
assertFalse(b);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetPath() {
|
||||
String path = rv.getPath();
|
||||
assertTrue(path.equals("dummydiskpath"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetPoolUuid() {
|
||||
String poolUuid = rv.getPoolUuid();
|
||||
assertTrue(poolUuid.equals("bed9f83e-cac3-11e1-ac8a-0050568b007e"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetPool() {
|
||||
StorageFilerTO pool = rv.getPool();
|
||||
|
||||
Long id = pool.getId();
|
||||
Long expectedL = 1L;
|
||||
assertEquals(expectedL, id);
|
||||
|
||||
String uuid = pool.getUuid();
|
||||
assertTrue(uuid.equals("bed9f83e-cac3-11e1-ac8a-0050568b007e"));
|
||||
|
||||
String host = pool.getHost();
|
||||
assertTrue(host.equals("hostAddress"));
|
||||
|
||||
String path = pool.getPath();
|
||||
assertTrue(path.equals("path"));
|
||||
|
||||
String userInfo = pool.getUserInfo();
|
||||
assertTrue(userInfo.equals("userInfo"));
|
||||
|
||||
Integer port = pool.getPort();
|
||||
Integer expectedI = 25;
|
||||
assertEquals(expectedI, port);
|
||||
|
||||
StoragePoolType type = pool.getType();
|
||||
assertEquals(StoragePoolType.Filesystem, type);
|
||||
|
||||
String str = pool.toString();
|
||||
assertTrue(str.equals("Pool[" + id.toString() + "|" + host + ":"
|
||||
+ port.toString() + "|" + path + "]"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetNewSize() {
|
||||
long newSize = rv.getNewSize();
|
||||
assertTrue(newSize == 4194304L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCurrentSize() {
|
||||
long currentSize = rv.getCurrentSize();
|
||||
assertTrue(currentSize == 1048576L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetShrinkOk() {
|
||||
assertFalse(rv.getShrinkOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetInstanceName() {
|
||||
String vmName = rv.getInstanceName();
|
||||
assertTrue(vmName.equals("vmName"));
|
||||
}
|
||||
|
||||
}
|
||||
@ -255,6 +255,7 @@ deleteVolume=15
|
||||
listVolumes=15
|
||||
extractVolume=15
|
||||
migrateVolume=15
|
||||
resizeVolume=15
|
||||
|
||||
#### registration command: FIXME -- this really should be something in management server that
|
||||
#### generates a new key for the user and they just have to
|
||||
@ -512,4 +513,4 @@ listApis=15
|
||||
#### API Rate Limit service command
|
||||
|
||||
getApiLimit=15
|
||||
resetApiLimit=1
|
||||
resetApiLimit=1
|
||||
|
||||
@ -162,6 +162,8 @@ import com.cloud.agent.api.storage.CreatePrivateTemplateAnswer;
|
||||
import com.cloud.agent.api.storage.DestroyCommand;
|
||||
import com.cloud.agent.api.storage.PrimaryStorageDownloadAnswer;
|
||||
import com.cloud.agent.api.storage.PrimaryStorageDownloadCommand;
|
||||
import com.cloud.agent.api.storage.ResizeVolumeCommand;
|
||||
import com.cloud.agent.api.storage.ResizeVolumeAnswer;
|
||||
import com.cloud.agent.api.to.IpAddressTO;
|
||||
import com.cloud.agent.api.to.NicTO;
|
||||
import com.cloud.agent.api.to.StorageFilerTO;
|
||||
@ -255,6 +257,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements
|
||||
private String _patchdomrPath;
|
||||
private String _createvmPath;
|
||||
private String _manageSnapshotPath;
|
||||
private String _resizeVolumePath;
|
||||
private String _createTmplPath;
|
||||
private String _heartBeatPath;
|
||||
private String _securityGroupPath;
|
||||
@ -534,6 +537,12 @@ public class LibvirtComputingResource extends ServerResourceBase implements
|
||||
"Unable to find the managesnapshot.sh");
|
||||
}
|
||||
|
||||
_resizeVolumePath = Script.findScript(storageScriptsDir, "resizevolume.sh");
|
||||
if (_resizeVolumePath == null) {
|
||||
throw new ConfigurationException(
|
||||
"Unable to find the resizevolume.sh");
|
||||
}
|
||||
|
||||
_createTmplPath = Script
|
||||
.findScript(storageScriptsDir, "createtmplt.sh");
|
||||
if (_createTmplPath == null) {
|
||||
@ -1062,6 +1071,8 @@ public class LibvirtComputingResource extends ServerResourceBase implements
|
||||
return execute((CleanupNetworkRulesCmd) cmd);
|
||||
} else if (cmd instanceof CopyVolumeCommand) {
|
||||
return execute((CopyVolumeCommand) cmd);
|
||||
} else if (cmd instanceof ResizeVolumeCommand) {
|
||||
return execute((ResizeVolumeCommand) cmd);
|
||||
} else if (cmd instanceof CheckNetworkCommand) {
|
||||
return execute((CheckNetworkCommand) cmd);
|
||||
} else {
|
||||
@ -1268,6 +1279,72 @@ public class LibvirtComputingResource extends ServerResourceBase implements
|
||||
}
|
||||
}
|
||||
|
||||
private String getResizeScriptType (KVMStoragePool pool, KVMPhysicalDisk vol) {
|
||||
StoragePoolType poolType = pool.getType();
|
||||
PhysicalDiskFormat volFormat = vol.getFormat();
|
||||
|
||||
if(pool.getType() == StoragePoolType.CLVM && volFormat == KVMPhysicalDisk.PhysicalDiskFormat.RAW) {
|
||||
return "CLVM";
|
||||
} else if ((poolType == StoragePoolType.NetworkFilesystem
|
||||
|| poolType == StoragePoolType.SharedMountPoint
|
||||
|| poolType == StoragePoolType.Filesystem)
|
||||
&& volFormat == KVMPhysicalDisk.PhysicalDiskFormat.QCOW2 ) {
|
||||
return "QCOW2";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/* uses a local script now, eventually support for virStorageVolResize() will maybe work on
|
||||
qcow2 and lvm and we can do this in libvirt calls */
|
||||
public Answer execute(ResizeVolumeCommand cmd) {
|
||||
String volid = cmd.getPath();
|
||||
long newSize = cmd.getNewSize();
|
||||
long currentSize = cmd.getCurrentSize();
|
||||
String vmInstanceName = cmd.getInstanceName();
|
||||
boolean shrinkOk = cmd.getShrinkOk();
|
||||
StorageFilerTO spool = cmd.getPool();
|
||||
|
||||
try {
|
||||
KVMStoragePool pool = _storagePoolMgr.getStoragePool(spool.getType(), spool.getUuid());
|
||||
KVMPhysicalDisk vol = pool.getPhysicalDisk(volid);
|
||||
String path = vol.getPath();
|
||||
String type = getResizeScriptType(pool, vol);
|
||||
|
||||
if (type == null) {
|
||||
return new ResizeVolumeAnswer(cmd, false, "Unsupported volume format: pool type '"
|
||||
+ pool.getType() + "' and volume format '" + vol.getFormat() + "'");
|
||||
}
|
||||
|
||||
s_logger.debug("got to the stage where we execute the volume resize, params:"
|
||||
+ path + "," + currentSize + "," + newSize + "," + type + "," + vmInstanceName + "," + shrinkOk);
|
||||
final Script resizecmd = new Script(_resizeVolumePath,
|
||||
_cmdsTimeout, s_logger);
|
||||
resizecmd.add("-s",String.valueOf(newSize));
|
||||
resizecmd.add("-c",String.valueOf(currentSize));
|
||||
resizecmd.add("-p",path);
|
||||
resizecmd.add("-t",type);
|
||||
resizecmd.add("-r",String.valueOf(shrinkOk));
|
||||
resizecmd.add("-v",vmInstanceName);
|
||||
String result = resizecmd.execute();
|
||||
|
||||
if (result == null) {
|
||||
|
||||
/* fetch new size as seen from libvirt, don't want to assume anything */
|
||||
pool = _storagePoolMgr.getStoragePool(spool.getType(), spool.getUuid());
|
||||
long finalSize = pool.getPhysicalDisk(volid).getVirtualSize();
|
||||
s_logger.debug("after resize, size reports as " + finalSize + ", requested " + newSize);
|
||||
return new ResizeVolumeAnswer(cmd, true, "success", finalSize);
|
||||
}
|
||||
|
||||
return new ResizeVolumeAnswer(cmd, false, result);
|
||||
} catch (CloudRuntimeException e) {
|
||||
String error = "failed to resize volume: " + e;
|
||||
s_logger.debug(error);
|
||||
return new ResizeVolumeAnswer(cmd, false, error);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Answer execute(DestroyCommand cmd) {
|
||||
VolumeTO vol = cmd.getVolume();
|
||||
|
||||
|
||||
@ -182,6 +182,8 @@ import com.cloud.agent.api.storage.CreatePrivateTemplateAnswer;
|
||||
import com.cloud.agent.api.storage.DestroyCommand;
|
||||
import com.cloud.agent.api.storage.PrimaryStorageDownloadAnswer;
|
||||
import com.cloud.agent.api.storage.PrimaryStorageDownloadCommand;
|
||||
import com.cloud.agent.api.storage.ResizeVolumeCommand;
|
||||
import com.cloud.agent.api.storage.ResizeVolumeAnswer;
|
||||
import com.cloud.agent.api.to.IpAddressTO;
|
||||
import com.cloud.agent.api.to.NicTO;
|
||||
import com.cloud.agent.api.to.PortForwardingRuleTO;
|
||||
@ -468,6 +470,8 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe
|
||||
return execute((DeleteStoragePoolCommand) cmd);
|
||||
} else if (clazz == CopyVolumeCommand.class) {
|
||||
return execute((CopyVolumeCommand) cmd);
|
||||
} else if (clazz == ResizeVolumeCommand.class) {
|
||||
return execute((ResizeVolumeCommand) cmd);
|
||||
} else if (clazz == AttachVolumeCommand.class) {
|
||||
return execute((AttachVolumeCommand) cmd);
|
||||
} else if (clazz == AttachIsoCommand.class) {
|
||||
@ -5618,6 +5622,23 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe
|
||||
}
|
||||
}
|
||||
|
||||
public Answer execute(ResizeVolumeCommand cmd) {
|
||||
Connection conn = getConnection();
|
||||
StorageFilerTO pool = cmd.getPool();
|
||||
String volid = cmd.getPath();
|
||||
long newSize = cmd.getNewSize();
|
||||
|
||||
try {
|
||||
VDI vdi = getVDIbyUuid(conn, volid);
|
||||
vdi.resize(conn, newSize);
|
||||
return new ResizeVolumeAnswer(cmd, true, "success", newSize);
|
||||
} catch (Exception e) {
|
||||
s_logger.warn("Unable to resize volume",e);
|
||||
String error = "failed to resize volume:" +e;
|
||||
return new ResizeVolumeAnswer(cmd, false, error );
|
||||
}
|
||||
}
|
||||
|
||||
protected SR getISOSRbyVmName(Connection conn, String vmName) {
|
||||
try {
|
||||
Set<SR> srs = SR.getByNameLabel(conn, vmName + "-ISO");
|
||||
@ -7684,4 +7705,5 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe
|
||||
return new SetStaticRouteAnswer(cmd, false, null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
253
scripts/storage/qcow2/resizevolume.sh
Normal file
253
scripts/storage/qcow2/resizevolume.sh
Normal file
@ -0,0 +1,253 @@
|
||||
#!/usr/bin/env bash
|
||||
# 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.
|
||||
|
||||
|
||||
|
||||
# resizevolume.sh -- resize a volume
|
||||
|
||||
usage() {
|
||||
printf "Usage: %s: -c <current-volume-size> -s <new-volume-size> -p <volume path> -v <vm instance name> -t <storage-type> -r <shrink-bool>\n" $(basename $0) >&2
|
||||
}
|
||||
|
||||
getdevmappername() {
|
||||
local path=$1
|
||||
local devmappername=`readlink -f $path |cut -d/ -f3`
|
||||
if [[ $devmappername =~ "dm-" ]]
|
||||
then
|
||||
dmname=$devmappername
|
||||
return 0
|
||||
else
|
||||
return 1;
|
||||
fi
|
||||
}
|
||||
|
||||
getdevmappersize() {
|
||||
local dm=$1
|
||||
if [ ! -e "/sys/block/${dm}/size" ]
|
||||
then
|
||||
log "unable to find ${dm} in /sys/block" 1
|
||||
exit 1
|
||||
fi
|
||||
actualsize=$((`cat /sys/block/${dm}/size`*512));
|
||||
|
||||
if [[ -z "$actualsize" ]]
|
||||
then
|
||||
log "unable to find actual size of ${dm}" 1
|
||||
exit 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# log "message" 1 <-- prints to stdout as well as log, to pass error up to cloudstack
|
||||
# log "message" prints only to log file
|
||||
# variable shouldwelog controls whether we print to log file
|
||||
log() {
|
||||
local d=`date`
|
||||
local msg=${1}
|
||||
local stdout=${2}
|
||||
|
||||
if [ ! -z "$stdout" ]
|
||||
then
|
||||
echo $1
|
||||
fi
|
||||
|
||||
if [ $shouldwelog -eq 1 ]
|
||||
then
|
||||
echo "$d - $1" >> /var/log/cloud/agent/resizevolume.log
|
||||
fi
|
||||
}
|
||||
|
||||
failshrink() {
|
||||
# if this is a shrink operation, fail if commands will shrink the volume and we haven't signed of on shrinking
|
||||
if [ $actualsize -gt $newsize ]
|
||||
then
|
||||
if [ "$shrink" == "false" ]
|
||||
then
|
||||
log "result would shrink the volume from $actualsize to $newsize, but confirmation to shrink wasn't passed. Shrink='$shrink'" 1
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
notifyqemu() {
|
||||
#move this back into cloudstack libvirt calls once the libvirt java bindings support block resize
|
||||
#we try to inform hypervisor of new size, but don't fail if we can't
|
||||
if `virsh help 2>/dev/null | grep -q blockresize`
|
||||
then
|
||||
if `virsh domstate $vmname >/dev/null 2>&1`
|
||||
then
|
||||
sizeinkb=$(($newsize/1024))
|
||||
virsh blockresize --domain $vmname --path $path --size $sizeinkb >/dev/null 2>&1
|
||||
retval=$?
|
||||
if [ -z $retval ] || [ $retval -ne 0 ]
|
||||
then
|
||||
log "failed to live resize $path to size of $sizeinkb kb" 1
|
||||
else
|
||||
liveresize='true'
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
resizelvm() {
|
||||
local dmname=''
|
||||
local actualsize=''
|
||||
local liveresize='false'
|
||||
|
||||
##### sanity checks #####
|
||||
if ! `lvresize --version > /dev/null 2>&1`
|
||||
then
|
||||
log "unable to resolve executable 'lvresize'" 1
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! `virsh --version > /dev/null 2>&1`
|
||||
then
|
||||
log "unable to resolve executable 'virsh'" 1
|
||||
exit 1
|
||||
fi
|
||||
##### end sanity #####
|
||||
|
||||
if ! getdevmappername $path
|
||||
then
|
||||
log "unable to resolve a device mapper dev from $path" 1
|
||||
exit 1
|
||||
fi
|
||||
|
||||
getdevmappersize $dmname
|
||||
|
||||
if [ $actualsize -ne $currentsize ]
|
||||
then
|
||||
log "disk isn't the size we think it is: cloudstack said $currentsize, disk said $actualsize."
|
||||
fi
|
||||
|
||||
# if this is a shrink operation, fail if commands will shrink the volume and we haven't signed of on shrinking
|
||||
failshrink
|
||||
|
||||
output=`lvresize -f -L ${newsize}B $path 2>&1`
|
||||
retval=$?
|
||||
|
||||
if [ -z $retval ] || [ $retval -ne 0 ]
|
||||
then
|
||||
log "lvresize failed: $output " 1
|
||||
exit 1
|
||||
fi
|
||||
|
||||
#move this back into cloudstack libvirt calls once the libvirt java bindings support block resize
|
||||
#we try to inform hypervisor of new size, but don't fail if we can't
|
||||
notifyqemu
|
||||
|
||||
log "performed successful resize - dm:$dmname currentsize:$currentsize newsize:$newsize path:$path type:$ptype vmname:$vmname live:$liveresize shrink:$shrink"
|
||||
}
|
||||
|
||||
resizeqcow2() {
|
||||
|
||||
##### sanity checks #####
|
||||
if [ ! -e "$path" ]
|
||||
then
|
||||
log "unable to find file $path" 1
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! `qemu-img info /dev/null > /dev/null 2>&1`
|
||||
then
|
||||
log "unable to resolve executable 'qemu-img'" 1
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! `virsh --version > /dev/null 2>&1`
|
||||
then
|
||||
log "unable to resolve executable 'virsh'" 1
|
||||
exit 1
|
||||
fi
|
||||
##### end sanity #####
|
||||
|
||||
$actualsize=`qemu-img info $path | grep "virtual size" | sed -re 's/^.*\(([0-9]+).*$/\1/g'`
|
||||
|
||||
if [ $actualsize -ne $currentsize ]
|
||||
then
|
||||
log "disk isn't the size we think it is: cloudstack said $currentsize, disk said $actualsize."
|
||||
fi
|
||||
|
||||
# if this is a shrink operation, fail if commands will shrink the volume and we haven't signed of on shrinking
|
||||
failshrink
|
||||
|
||||
output=`qemu-img resize $path $newsize 2>&1`
|
||||
retval=$?
|
||||
|
||||
if [ -z $retval ] || [ $retval -ne 0 ]
|
||||
then
|
||||
log "qemu-img resize failed: $output" 1
|
||||
exit 1
|
||||
fi
|
||||
|
||||
#move this back into cloudstack libvirt calls once the libvirt java bindings support block resize
|
||||
#we try to inform hypervisor of new size, but don't fail if we can't
|
||||
notifyqemu
|
||||
|
||||
log "performed successful resize - currentsize:$currentsize newsize:$newsize path:$path type:$ptype vmname:$vmname live:$liveresize shrink:$shrink"
|
||||
}
|
||||
|
||||
sflag=
|
||||
cflag=
|
||||
pflag=
|
||||
vflag=
|
||||
tflag=
|
||||
rflag=
|
||||
|
||||
while getopts 'c:s:v:p:t:r:' OPTION
|
||||
do
|
||||
case $OPTION in
|
||||
s) sflag=1
|
||||
newsize="$OPTARG"
|
||||
;;
|
||||
c) cflag=1
|
||||
currentsize="$OPTARG"
|
||||
;;
|
||||
v) vflag=1
|
||||
vmname="$OPTARG"
|
||||
;;
|
||||
p) dflag=1
|
||||
path="$OPTARG"
|
||||
;;
|
||||
t) tflag=1
|
||||
ptype="$OPTARG"
|
||||
;;
|
||||
r) rflag=1
|
||||
shrink="$OPTARG"
|
||||
;;
|
||||
?) usage
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
shouldwelog=1 #set this to 1 while debugging to get output in /var/log/cloud/agent/resizevolume.log
|
||||
|
||||
if [ "$ptype" == "CLVM" ]
|
||||
then
|
||||
resizelvm
|
||||
elif [ "$ptype" == "QCOW2" ]
|
||||
then
|
||||
resizeqcow2
|
||||
else
|
||||
echo "unsupported type $ptype"
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@ -138,7 +138,7 @@ import com.cloud.utils.component.Inject;
|
||||
import com.cloud.utils.concurrency.NamedThreadFactory;
|
||||
import com.cloud.utils.db.SearchCriteria;
|
||||
import com.cloud.utils.db.Transaction;
|
||||
import com.cloud.utils.exception.CSExceptionErrorCode;
|
||||
|
||||
|
||||
public class ApiServer implements HttpRequestHandler {
|
||||
private static final Logger s_logger = Logger.getLogger(ApiServer.class.getName());
|
||||
@ -386,6 +386,7 @@ public class ApiServer implements HttpRequestHandler {
|
||||
if (UserContext.current().getCaller().getType() != Account.ACCOUNT_TYPE_ADMIN){
|
||||
// hide internal details to non-admin user for security reason
|
||||
errorMsg = BaseCmd.USER_ERROR_MESSAGE;
|
||||
|
||||
}
|
||||
throw new ServerApiException(ApiErrorCode.INSUFFICIENT_CAPACITY_ERROR, errorMsg, ex);
|
||||
}
|
||||
|
||||
@ -341,8 +341,8 @@ public class ConfigurationServerImpl implements ConfigurationServer {
|
||||
}
|
||||
|
||||
// now insert the user
|
||||
insertSql = "INSERT INTO `cloud`.`user` (id, uuid, username, account_id, firstname, lastname, created, state) " +
|
||||
"VALUES (" + id + ", UUID(), '" + username + "', 2, '" + firstname + "','" + lastname + "',now(), 'disabled')";
|
||||
insertSql = "INSERT INTO `cloud`.`user` (id, username, password, account_id, firstname, lastname, created, state) " +
|
||||
"VALUES (" + id + ",'" + username + "', RAND(), 2, '" + firstname + "','" + lastname + "',now(), 'disabled')";
|
||||
|
||||
txn = Transaction.currentTxn();
|
||||
try {
|
||||
|
||||
@ -47,6 +47,7 @@ import javax.naming.ConfigurationException;
|
||||
import org.apache.cloudstack.api.command.admin.storage.*;
|
||||
import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd;
|
||||
import org.apache.cloudstack.api.command.user.volume.UploadVolumeCmd;
|
||||
import org.apache.cloudstack.api.command.user.volume.ResizeVolumeCmd;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.cloud.agent.AgentManager;
|
||||
@ -69,6 +70,8 @@ import com.cloud.agent.api.storage.CreateCommand;
|
||||
import com.cloud.agent.api.storage.DeleteTemplateCommand;
|
||||
import com.cloud.agent.api.storage.DeleteVolumeCommand;
|
||||
import com.cloud.agent.api.storage.DestroyCommand;
|
||||
import com.cloud.agent.api.storage.ResizeVolumeCommand;
|
||||
import com.cloud.agent.api.storage.ResizeVolumeAnswer;
|
||||
import com.cloud.agent.api.to.StorageFilerTO;
|
||||
import com.cloud.agent.api.to.VolumeTO;
|
||||
import com.cloud.agent.manager.Commands;
|
||||
@ -2098,6 +2101,183 @@ public class StorageManagerImpl implements StorageManager, Manager, ClusterManag
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@DB
|
||||
@ActionEvent(eventType = EventTypes.EVENT_VOLUME_RESIZE, eventDescription = "resizing volume", async = true)
|
||||
public VolumeVO resizeVolume(ResizeVolumeCmd cmd) {
|
||||
VolumeVO volume = _volsDao.findById(cmd.getEntityId());
|
||||
Long newSize = null;
|
||||
boolean shrinkOk = cmd.getShrinkOk();
|
||||
boolean success = false;
|
||||
DiskOfferingVO diskOffering = _diskOfferingDao.findById(volume.getDiskOfferingId());
|
||||
DiskOfferingVO newDiskOffering = null;
|
||||
|
||||
newDiskOffering = _diskOfferingDao.findById(cmd.getNewDiskOfferingId());
|
||||
|
||||
/* Volumes with no hypervisor have never been assigned, and can be resized by recreating.
|
||||
perhaps in the future we can just update the db entry for the volume */
|
||||
if(_volsDao.getHypervisorType(volume.getId()) == HypervisorType.None){
|
||||
throw new InvalidParameterValueException("Can't resize a volume that has never been attached, not sure which hypervisor type. Recreate volume to resize.");
|
||||
}
|
||||
|
||||
/* Only works for KVM/Xen for now */
|
||||
if(_volsDao.getHypervisorType(volume.getId()) != HypervisorType.KVM
|
||||
&& _volsDao.getHypervisorType(volume.getId()) != HypervisorType.XenServer){
|
||||
throw new InvalidParameterValueException("Cloudstack currently only supports volumes marked as KVM or XenServer hypervisor for resize");
|
||||
}
|
||||
|
||||
if (volume == null) {
|
||||
throw new InvalidParameterValueException("No such volume");
|
||||
}
|
||||
|
||||
if (volume.getState() != Volume.State.Ready) {
|
||||
throw new InvalidParameterValueException("Volume should be in ready state before attempting a resize");
|
||||
}
|
||||
|
||||
if (!volume.getVolumeType().equals(Volume.Type.DATADISK)) {
|
||||
throw new InvalidParameterValueException("Can only resize DATA volumes");
|
||||
}
|
||||
|
||||
/* figure out whether or not a new disk offering or size parameter is required, get the correct size value */
|
||||
if (newDiskOffering == null) {
|
||||
if (diskOffering.isCustomized()) {
|
||||
newSize = cmd.getSize();
|
||||
|
||||
if (newSize == null) {
|
||||
throw new InvalidParameterValueException("new offering is of custom size, need to specify a size");
|
||||
}
|
||||
|
||||
newSize = ( newSize << 30 );
|
||||
} else {
|
||||
throw new InvalidParameterValueException("current offering" + volume.getDiskOfferingId() + " cannot be resized, need to specify a disk offering");
|
||||
}
|
||||
} else {
|
||||
|
||||
if (newDiskOffering.getRemoved() != null || !DiskOfferingVO.Type.Disk.equals(newDiskOffering.getType())) {
|
||||
throw new InvalidParameterValueException("Disk offering ID is missing or invalid");
|
||||
}
|
||||
|
||||
if(diskOffering.getTags() != null) {
|
||||
if(!newDiskOffering.getTags().equals(diskOffering.getTags())){
|
||||
throw new InvalidParameterValueException("Tags on new and old disk offerings must match");
|
||||
}
|
||||
} else if (newDiskOffering.getTags() != null ){
|
||||
throw new InvalidParameterValueException("There are no tags on current disk offering, new disk offering needs to have no tags");
|
||||
}
|
||||
|
||||
if (newDiskOffering.getDomainId() == null) {
|
||||
// do nothing as offering is public
|
||||
} else {
|
||||
_configMgr.checkDiskOfferingAccess(UserContext.current().getCaller(), newDiskOffering);
|
||||
}
|
||||
|
||||
if (newDiskOffering.isCustomized()) {
|
||||
newSize = cmd.getSize();
|
||||
|
||||
if (newSize == null) {
|
||||
throw new InvalidParameterValueException("new offering is of custom size, need to specify a size");
|
||||
}
|
||||
|
||||
newSize = ( newSize << 30 );
|
||||
} else {
|
||||
newSize = newDiskOffering.getDiskSize();
|
||||
}
|
||||
}
|
||||
|
||||
if (newSize == null) {
|
||||
throw new InvalidParameterValueException("could not detect a size parameter or fetch one from the diskofferingid parameter");
|
||||
}
|
||||
|
||||
if (!validateVolumeSizeRange(newSize)) {
|
||||
throw new InvalidParameterValueException("Requested size out of range");
|
||||
}
|
||||
|
||||
/* does the caller have the authority to act on this volume? */
|
||||
_accountMgr.checkAccess(UserContext.current().getCaller(), null, true, volume);
|
||||
|
||||
UserVmVO userVm = _userVmDao.findById(volume.getInstanceId());
|
||||
|
||||
StoragePool pool = _storagePoolDao.findById(volume.getPoolId());
|
||||
long currentSize = volume.getSize();
|
||||
|
||||
/* lets make certain they (think they) know what they're doing if they
|
||||
want to shrink, by forcing them to provide the shrinkok parameter. This will
|
||||
be checked again at the hypervisor level where we can see the actual disk size */
|
||||
if (currentSize > newSize && !shrinkOk) {
|
||||
throw new InvalidParameterValueException("Going from existing size of " + currentSize + " to size of "
|
||||
+ newSize + " would shrink the volume, need to sign off by supplying the shrinkok parameter with value of true");
|
||||
}
|
||||
|
||||
/* get a list of hosts to send the commands to, try the system the
|
||||
associated vm is running on first, then the last known place it ran.
|
||||
If not attached to a userVm, we pass 'none' and resizevolume.sh is
|
||||
ok with that since it only needs the vm name to live resize */
|
||||
long[] hosts = null;
|
||||
String instanceName = "none";
|
||||
if (userVm != null) {
|
||||
instanceName = userVm.getInstanceName();
|
||||
if(userVm.getHostId() != null) {
|
||||
hosts = new long[] { userVm.getHostId() };
|
||||
} else if(userVm.getLastHostId() != null) {
|
||||
hosts = new long[] { userVm.getLastHostId() };
|
||||
}
|
||||
|
||||
/*Xen only works offline, SR does not support VDI.resizeOnline*/
|
||||
if(_volsDao.getHypervisorType(volume.getId()) == HypervisorType.XenServer
|
||||
&& ! userVm.getState().equals(State.Stopped)) {
|
||||
throw new InvalidParameterValueException("VM must be stopped or disk detached in order to resize with the Xen HV");
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
try {
|
||||
stateTransitTo(volume, Volume.Event.ResizeRequested);
|
||||
} catch (NoTransitionException etrans) {
|
||||
throw new CloudRuntimeException("Unable to change volume state for resize: " + etrans.toString());
|
||||
}
|
||||
|
||||
ResizeVolumeCommand resizeCmd = new ResizeVolumeCommand(volume.getPath(), new StorageFilerTO(pool),
|
||||
currentSize, newSize, shrinkOk, instanceName);
|
||||
ResizeVolumeAnswer answer = (ResizeVolumeAnswer) sendToPool(pool, hosts, resizeCmd);
|
||||
|
||||
/* need to fetch/store new volume size in database. This value comes from
|
||||
hypervisor rather than trusting that a success means we have a volume of the
|
||||
size we requested */
|
||||
if (answer != null && answer.getResult()) {
|
||||
long finalSize = answer.getNewSize();
|
||||
s_logger.debug("Resize: volume started at size " + currentSize + " and ended at size " + finalSize);
|
||||
volume.setSize(finalSize);
|
||||
if (newDiskOffering != null) {
|
||||
volume.setDiskOfferingId(cmd.getNewDiskOfferingId());
|
||||
}
|
||||
_volsDao.update(volume.getId(), volume);
|
||||
|
||||
success = true;
|
||||
return volume;
|
||||
} else if (answer != null) {
|
||||
s_logger.debug("Resize: returned '" + answer.getDetails() + "'");
|
||||
}
|
||||
} catch (StorageUnavailableException e) {
|
||||
s_logger.debug("volume failed to resize: "+e);
|
||||
return null;
|
||||
} finally {
|
||||
if(success) {
|
||||
try {
|
||||
stateTransitTo(volume, Volume.Event.OperationSucceeded);
|
||||
} catch (NoTransitionException etrans) {
|
||||
throw new CloudRuntimeException("Failed to change volume state: " + etrans.toString());
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
stateTransitTo(volume, Volume.Event.OperationFailed);
|
||||
} catch (NoTransitionException etrans) {
|
||||
throw new CloudRuntimeException("Failed to change volume state: " + etrans.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@DB
|
||||
public boolean destroyVolume(VolumeVO volume) throws ConcurrentOperationException {
|
||||
|
||||
0
setup/db/create-schema-view.sql
Executable file → Normal file
0
setup/db/create-schema-view.sql
Executable file → Normal file
@ -54,12 +54,20 @@ class Services:
|
||||
"cpunumber": 1,
|
||||
"cpuspeed": 100, # in MHz
|
||||
"memory": 128, # In MBs
|
||||
"storagetype": "local"
|
||||
},
|
||||
"disk_offering": {
|
||||
"displaytext": "Small",
|
||||
"name": "Small",
|
||||
"storagetype": "local",
|
||||
"disksize": 1
|
||||
},
|
||||
'resized_disk_offering': {
|
||||
"displaytext": "Resized",
|
||||
"name": "Resized",
|
||||
"storagetype": "local",
|
||||
"disksize": 3
|
||||
},
|
||||
"volume_offerings": {
|
||||
0: {
|
||||
"diskname": "TestDiskServ",
|
||||
@ -77,8 +85,8 @@ class Services:
|
||||
"diskdevice": "/dev/xvdb",
|
||||
"ostype": 'CentOS 5.3 (64-bit)',
|
||||
"mode": 'basic',
|
||||
"sleep": 60,
|
||||
"timeout": 10,
|
||||
"sleep": 10,
|
||||
"timeout": 600,
|
||||
}
|
||||
|
||||
|
||||
@ -237,7 +245,7 @@ class TestCreateVolume(cloudstackTestCase):
|
||||
ssh = self.virtual_machine.get_ssh_client(
|
||||
reconnect=True
|
||||
)
|
||||
c = "fdisk -l"
|
||||
c = "/sbin/fdisk -l"
|
||||
res = ssh.execute(c)
|
||||
|
||||
except Exception as e:
|
||||
@ -283,6 +291,16 @@ class TestVolumes(cloudstackTestCase):
|
||||
cls.api_client,
|
||||
cls.services["disk_offering"]
|
||||
)
|
||||
cls.resized_disk_offering = DiskOffering.create(
|
||||
cls.api_client,
|
||||
cls.services["resized_disk_offering"]
|
||||
)
|
||||
cls.custom_resized_disk_offering = DiskOffering.create(
|
||||
cls.api_client,
|
||||
cls.services["resized_disk_offering"],
|
||||
custom=True
|
||||
)
|
||||
|
||||
template = get_template(
|
||||
cls.api_client,
|
||||
cls.zone.id,
|
||||
@ -292,6 +310,8 @@ class TestVolumes(cloudstackTestCase):
|
||||
cls.services["zoneid"] = cls.zone.id
|
||||
cls.services["template"] = template.id
|
||||
cls.services["diskofferingid"] = cls.disk_offering.id
|
||||
cls.services['resizeddiskofferingid'] = cls.resized_disk_offering.id
|
||||
cls.services['customresizeddiskofferingid'] = cls.custom_resized_disk_offering.id
|
||||
|
||||
# Create VMs, VMs etc
|
||||
cls.account = Account.create(
|
||||
@ -321,6 +341,8 @@ class TestVolumes(cloudstackTestCase):
|
||||
domainid=cls.account.account.domainid
|
||||
)
|
||||
cls._cleanup = [
|
||||
cls.resized_disk_offering,
|
||||
cls.custom_resized_disk_offering,
|
||||
cls.service_offering,
|
||||
cls.disk_offering,
|
||||
cls.account
|
||||
@ -500,7 +522,102 @@ class TestVolumes(cloudstackTestCase):
|
||||
)
|
||||
|
||||
@attr(tags = ["advanced", "advancedns", "smoke"])
|
||||
def test_07_delete_detached_volume(self):
|
||||
def test_07_resize_fail(self):
|
||||
"""Verify invalid options fail to Resize a volume"""
|
||||
# Verify the size is the new size is what we wanted it to be.
|
||||
self.debug("Fail Resize Volume ID: %s" % self.volume.id)
|
||||
|
||||
# first, an invalid id
|
||||
cmd = resizeVolume.resizeVolumeCmd()
|
||||
cmd.id = "invalid id"
|
||||
cmd.diskofferingid = self.services['resizeddiskofferingid']
|
||||
success = False
|
||||
try:
|
||||
response = self.apiClient.resizeVolume(cmd)
|
||||
except Exception as ex:
|
||||
if str(ex) == "HTTP Error 431: 431":
|
||||
success = True
|
||||
self.assertEqual(success, True, "ResizeVolume - verify invalid id is handled appropriately")
|
||||
|
||||
# Next, we'll try an invalid disk offering id
|
||||
cmd.id = self.volume.id
|
||||
cmd.diskofferingid = "invalid id"
|
||||
success = False
|
||||
try:
|
||||
response = self.apiClient.resizeVolume(cmd)
|
||||
except Exception as ex:
|
||||
if "need to specify a disk offering" in str(ex):
|
||||
success = True
|
||||
self.assertEqual(success, True, "ResizeVolume - verify disk offering is handled appropriately")
|
||||
|
||||
# Ok, now let's try and resize a volume that is not custom.
|
||||
cmd.id = self.volume.id
|
||||
cmd.diskofferingid = self.services['diskofferingid']
|
||||
cmd.size = 4
|
||||
currentSize = self.volume.size
|
||||
|
||||
self.apiClient.resizeVolume(cmd)
|
||||
count = 0
|
||||
success = True
|
||||
while count < 10:
|
||||
list_volume_response = list_volumes(
|
||||
self.apiClient,
|
||||
id=self.volume.id,
|
||||
type='DATADISK'
|
||||
)
|
||||
for vol in list_volume_response:
|
||||
if vol.id == self.volume.id and vol.size != currentSize:
|
||||
success = False
|
||||
if success:
|
||||
break
|
||||
else:
|
||||
time.sleep(1)
|
||||
count += 1
|
||||
|
||||
self.assertEqual(
|
||||
success,
|
||||
True,
|
||||
"Verify the volume did not resize"
|
||||
)
|
||||
|
||||
|
||||
@attr(tags = ["advanced", "advancedns", "smoke"])
|
||||
def test_08_resize_volume(self):
|
||||
"""Resize a volume"""
|
||||
# Verify the size is the new size is what we wanted it to be.
|
||||
self.debug("Resize Volume ID: %s" % self.volume.id)
|
||||
|
||||
cmd = resizeVolume.resizeVolumeCmd()
|
||||
cmd.id = self.volume.id
|
||||
cmd.diskofferingid = self.services['resizeddiskofferingid']
|
||||
|
||||
self.apiClient.resizeVolume(cmd)
|
||||
|
||||
count = 0
|
||||
success = False
|
||||
while count < 3:
|
||||
list_volume_response = list_volumes(
|
||||
self.apiClient,
|
||||
id=self.volume.id,
|
||||
type='DATADISK'
|
||||
)
|
||||
for vol in list_volume_response:
|
||||
if vol.id == self.volume.id and vol.size == 3221225472L:
|
||||
success = True
|
||||
if success:
|
||||
break
|
||||
else:
|
||||
time.sleep(10)
|
||||
count += 1
|
||||
|
||||
self.assertEqual(
|
||||
success,
|
||||
True,
|
||||
"Check if the volume resized appropriately"
|
||||
)
|
||||
|
||||
@attr(tags = ["advanced", "advancedns", "smoke"])
|
||||
def test_09_delete_detached_volume(self):
|
||||
"""Delete a Volume unattached to an VM
|
||||
"""
|
||||
# Validate the following
|
||||
|
||||
@ -509,6 +509,12 @@ class Volume:
|
||||
[setattr(cmd, k, v) for k, v in kwargs.items()]
|
||||
return(apiclient.listVolumes(cmd))
|
||||
|
||||
def resize(cls, apiclient, **kwargs):
|
||||
"""Resize a volume"""
|
||||
cmd = resizeVolume.resizeVolumeCmd()
|
||||
cmd.id = self.id
|
||||
[setattr(cmd, k, v) for k, v in kwargs.items()]
|
||||
return(apiclient.resizeVolume(cmd))
|
||||
|
||||
class Snapshot:
|
||||
"""Manage Snapshot Lifecycle
|
||||
|
||||
@ -545,7 +545,8 @@
|
||||
|
||||
if(checkedSecurityGroupIdArray.length > 0)
|
||||
array1.push("&securitygroupids=" + checkedSecurityGroupIdArray.join(","));
|
||||
|
||||
|
||||
/*
|
||||
if(selectedZoneObj.networktype == "Advanced" && selectedZoneObj.securitygroupsenabled == true) { // Advanced SG-enabled zone
|
||||
var networkData = {
|
||||
zoneId: selectedZoneObj.id,
|
||||
@ -577,6 +578,8 @@
|
||||
return;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
else if (step5ContainerType == 'nothing-to-select') {
|
||||
if(args.context.networks != null) { //from VPC tier
|
||||
|
||||
@ -1148,6 +1148,7 @@
|
||||
var array1 = [];
|
||||
if(args.context.zones[0].networktype == "Advanced" && args.context.zones[0].securitygroupsenabled == true) {
|
||||
array1.push({id: 'account-specific', description: 'Account'});
|
||||
array1.push({id: 'zone-wide', description: 'All'});
|
||||
}
|
||||
else {
|
||||
array1.push({id: 'zone-wide', description: 'All'});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user