mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
Summary: adding resizeVolume api call
Detail: This merges the resizevolume feature branch, which provides the ability to migrate a disk between disk offerings, thereby changing its size, or specifying a new size if current disk offering is custom. BUG-ID: CLOUDSTACK-644 Signed-off-by: Marcus Sorensen <marcus@betterservers.com> 1358358209 -0700
This commit is contained in:
parent
a22bfd7e7b
commit
975021dda1
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_EXTRACT = "VOLUME.EXTRACT";
|
||||||
public static final String EVENT_VOLUME_UPLOAD = "VOLUME.UPLOAD";
|
public static final String EVENT_VOLUME_UPLOAD = "VOLUME.UPLOAD";
|
||||||
public static final String EVENT_VOLUME_MIGRATE = "VOLUME.MIGRATE";
|
public static final String EVENT_VOLUME_MIGRATE = "VOLUME.MIGRATE";
|
||||||
|
public static final String EVENT_VOLUME_RESIZE = "VOLUME.RESIZE";
|
||||||
|
|
||||||
// Domains
|
// Domains
|
||||||
public static final String EVENT_DOMAIN_CREATE = "DOMAIN.CREATE";
|
public static final String EVENT_DOMAIN_CREATE = "DOMAIN.CREATE";
|
||||||
|
|||||||
@ -54,7 +54,6 @@ public class CloudException extends Exception {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public ArrayList<String> getIdProxyList() {
|
public ArrayList<String> getIdProxyList() {
|
||||||
return idList;
|
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.admin.storage.UpdateStoragePoolCmd;
|
||||||
import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd;
|
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.UploadVolumeCmd;
|
||||||
|
import org.apache.cloudstack.api.command.user.volume.ResizeVolumeCmd;
|
||||||
import com.cloud.exception.ConcurrentOperationException;
|
import com.cloud.exception.ConcurrentOperationException;
|
||||||
import com.cloud.exception.InsufficientCapacityException;
|
import com.cloud.exception.InsufficientCapacityException;
|
||||||
import com.cloud.exception.PermissionDeniedException;
|
import com.cloud.exception.PermissionDeniedException;
|
||||||
@ -70,6 +71,15 @@ public interface StorageService{
|
|||||||
Volume createVolume(CreateVolumeCmd cmd);
|
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
|
* Delete the storage pool
|
||||||
*
|
*
|
||||||
|
|||||||
@ -36,6 +36,7 @@ public interface Volume extends ControlledEntity, Identity, InternalIdentity, Ba
|
|||||||
Ready("The volume is ready to be used."),
|
Ready("The volume is ready to be used."),
|
||||||
Migrating("The volume is migrating to other storage pool"),
|
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"),
|
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"),
|
Expunging("The volume is being expunging"),
|
||||||
Destroy("The volume is destroyed, and can't be recovered."),
|
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");
|
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.OperationFailed, Allocated);
|
||||||
s_fsm.addTransition(Creating, Event.OperationSucceeded, Ready);
|
s_fsm.addTransition(Creating, Event.OperationSucceeded, Ready);
|
||||||
s_fsm.addTransition(Creating, Event.DestroyRequested, Destroy);
|
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(Allocated, Event.UploadRequested, UploadOp);
|
||||||
s_fsm.addTransition(UploadOp, Event.CopyRequested, Creating);// CopyRequested for volume from sec to primary storage
|
s_fsm.addTransition(UploadOp, Event.CopyRequested, Creating);// CopyRequested for volume from sec to primary storage
|
||||||
s_fsm.addTransition(Creating, Event.CopySucceeded, Ready);
|
s_fsm.addTransition(Creating, Event.CopySucceeded, Ready);
|
||||||
@ -92,7 +96,8 @@ public interface Volume extends ControlledEntity, Identity, InternalIdentity, Ba
|
|||||||
MigrationRequested,
|
MigrationRequested,
|
||||||
SnapshotRequested,
|
SnapshotRequested,
|
||||||
DestroyRequested,
|
DestroyRequested,
|
||||||
ExpungingRequested;
|
ExpungingRequested,
|
||||||
|
ResizeRequested;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -387,6 +387,7 @@ public class ApiConstants {
|
|||||||
public static final String ESP_LIFETIME = "esplifetime";
|
public static final String ESP_LIFETIME = "esplifetime";
|
||||||
public static final String DPD = "dpd";
|
public static final String DPD = "dpd";
|
||||||
public static final String FOR_VPC = "forvpc";
|
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_DEVICE_ID = "nvpdeviceid";
|
||||||
public static final String NICIRA_NVP_TRANSPORT_ZONE_UUID = "transportzoneuuid";
|
public static final String NICIRA_NVP_TRANSPORT_ZONE_UUID = "transportzoneuuid";
|
||||||
public static final String NICIRA_NVP_DEVICE_NAME = "niciradevicename";
|
public static final String NICIRA_NVP_DEVICE_NAME = "niciradevicename";
|
||||||
|
|||||||
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,153 @@
|
|||||||
|
// 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.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(BaseCmd.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
|
listVolumes=15
|
||||||
extractVolume=15
|
extractVolume=15
|
||||||
migrateVolume=15
|
migrateVolume=15
|
||||||
|
resizeVolume=15
|
||||||
|
|
||||||
#### registration command: FIXME -- this really should be something in management server that
|
#### registration command: FIXME -- this really should be something in management server that
|
||||||
#### generates a new key for the user and they just have to
|
#### generates a new key for the user and they just have to
|
||||||
|
|||||||
@ -162,6 +162,8 @@ import com.cloud.agent.api.storage.CreatePrivateTemplateAnswer;
|
|||||||
import com.cloud.agent.api.storage.DestroyCommand;
|
import com.cloud.agent.api.storage.DestroyCommand;
|
||||||
import com.cloud.agent.api.storage.PrimaryStorageDownloadAnswer;
|
import com.cloud.agent.api.storage.PrimaryStorageDownloadAnswer;
|
||||||
import com.cloud.agent.api.storage.PrimaryStorageDownloadCommand;
|
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.IpAddressTO;
|
||||||
import com.cloud.agent.api.to.NicTO;
|
import com.cloud.agent.api.to.NicTO;
|
||||||
import com.cloud.agent.api.to.StorageFilerTO;
|
import com.cloud.agent.api.to.StorageFilerTO;
|
||||||
@ -255,6 +257,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements
|
|||||||
private String _patchdomrPath;
|
private String _patchdomrPath;
|
||||||
private String _createvmPath;
|
private String _createvmPath;
|
||||||
private String _manageSnapshotPath;
|
private String _manageSnapshotPath;
|
||||||
|
private String _resizeVolumePath;
|
||||||
private String _createTmplPath;
|
private String _createTmplPath;
|
||||||
private String _heartBeatPath;
|
private String _heartBeatPath;
|
||||||
private String _securityGroupPath;
|
private String _securityGroupPath;
|
||||||
@ -534,6 +537,12 @@ public class LibvirtComputingResource extends ServerResourceBase implements
|
|||||||
"Unable to find the managesnapshot.sh");
|
"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
|
_createTmplPath = Script
|
||||||
.findScript(storageScriptsDir, "createtmplt.sh");
|
.findScript(storageScriptsDir, "createtmplt.sh");
|
||||||
if (_createTmplPath == null) {
|
if (_createTmplPath == null) {
|
||||||
@ -1062,6 +1071,8 @@ public class LibvirtComputingResource extends ServerResourceBase implements
|
|||||||
return execute((CleanupNetworkRulesCmd) cmd);
|
return execute((CleanupNetworkRulesCmd) cmd);
|
||||||
} else if (cmd instanceof CopyVolumeCommand) {
|
} else if (cmd instanceof CopyVolumeCommand) {
|
||||||
return execute((CopyVolumeCommand) cmd);
|
return execute((CopyVolumeCommand) cmd);
|
||||||
|
} else if (cmd instanceof ResizeVolumeCommand) {
|
||||||
|
return execute((ResizeVolumeCommand) cmd);
|
||||||
} else if (cmd instanceof CheckNetworkCommand) {
|
} else if (cmd instanceof CheckNetworkCommand) {
|
||||||
return execute((CheckNetworkCommand) cmd);
|
return execute((CheckNetworkCommand) cmd);
|
||||||
} else {
|
} 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) {
|
public Answer execute(DestroyCommand cmd) {
|
||||||
VolumeTO vol = cmd.getVolume();
|
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.DestroyCommand;
|
||||||
import com.cloud.agent.api.storage.PrimaryStorageDownloadAnswer;
|
import com.cloud.agent.api.storage.PrimaryStorageDownloadAnswer;
|
||||||
import com.cloud.agent.api.storage.PrimaryStorageDownloadCommand;
|
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.IpAddressTO;
|
||||||
import com.cloud.agent.api.to.NicTO;
|
import com.cloud.agent.api.to.NicTO;
|
||||||
import com.cloud.agent.api.to.PortForwardingRuleTO;
|
import com.cloud.agent.api.to.PortForwardingRuleTO;
|
||||||
@ -468,6 +470,8 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe
|
|||||||
return execute((DeleteStoragePoolCommand) cmd);
|
return execute((DeleteStoragePoolCommand) cmd);
|
||||||
} else if (clazz == CopyVolumeCommand.class) {
|
} else if (clazz == CopyVolumeCommand.class) {
|
||||||
return execute((CopyVolumeCommand) cmd);
|
return execute((CopyVolumeCommand) cmd);
|
||||||
|
} else if (clazz == ResizeVolumeCommand.class) {
|
||||||
|
return execute((ResizeVolumeCommand) cmd);
|
||||||
} else if (clazz == AttachVolumeCommand.class) {
|
} else if (clazz == AttachVolumeCommand.class) {
|
||||||
return execute((AttachVolumeCommand) cmd);
|
return execute((AttachVolumeCommand) cmd);
|
||||||
} else if (clazz == AttachIsoCommand.class) {
|
} 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) {
|
protected SR getISOSRbyVmName(Connection conn, String vmName) {
|
||||||
try {
|
try {
|
||||||
Set<SR> srs = SR.getByNameLabel(conn, vmName + "-ISO");
|
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);
|
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
|
||||||
@ -47,6 +47,7 @@ import javax.naming.ConfigurationException;
|
|||||||
import org.apache.cloudstack.api.command.admin.storage.*;
|
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.CreateVolumeCmd;
|
||||||
import org.apache.cloudstack.api.command.user.volume.UploadVolumeCmd;
|
import org.apache.cloudstack.api.command.user.volume.UploadVolumeCmd;
|
||||||
|
import org.apache.cloudstack.api.command.user.volume.ResizeVolumeCmd;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
|
|
||||||
import com.cloud.agent.AgentManager;
|
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.DeleteTemplateCommand;
|
||||||
import com.cloud.agent.api.storage.DeleteVolumeCommand;
|
import com.cloud.agent.api.storage.DeleteVolumeCommand;
|
||||||
import com.cloud.agent.api.storage.DestroyCommand;
|
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.StorageFilerTO;
|
||||||
import com.cloud.agent.api.to.VolumeTO;
|
import com.cloud.agent.api.to.VolumeTO;
|
||||||
import com.cloud.agent.manager.Commands;
|
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
|
@Override
|
||||||
@DB
|
@DB
|
||||||
public boolean destroyVolume(VolumeVO volume) throws ConcurrentOperationException {
|
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,
|
"cpunumber": 1,
|
||||||
"cpuspeed": 100, # in MHz
|
"cpuspeed": 100, # in MHz
|
||||||
"memory": 128, # In MBs
|
"memory": 128, # In MBs
|
||||||
|
"storagetype": "local"
|
||||||
},
|
},
|
||||||
"disk_offering": {
|
"disk_offering": {
|
||||||
"displaytext": "Small",
|
"displaytext": "Small",
|
||||||
"name": "Small",
|
"name": "Small",
|
||||||
|
"storagetype": "local",
|
||||||
"disksize": 1
|
"disksize": 1
|
||||||
},
|
},
|
||||||
|
'resized_disk_offering': {
|
||||||
|
"displaytext": "Resized",
|
||||||
|
"name": "Resized",
|
||||||
|
"storagetype": "local",
|
||||||
|
"disksize": 3
|
||||||
|
},
|
||||||
"volume_offerings": {
|
"volume_offerings": {
|
||||||
0: {
|
0: {
|
||||||
"diskname": "TestDiskServ",
|
"diskname": "TestDiskServ",
|
||||||
@ -77,8 +85,8 @@ class Services:
|
|||||||
"diskdevice": "/dev/xvdb",
|
"diskdevice": "/dev/xvdb",
|
||||||
"ostype": 'CentOS 5.3 (64-bit)',
|
"ostype": 'CentOS 5.3 (64-bit)',
|
||||||
"mode": 'basic',
|
"mode": 'basic',
|
||||||
"sleep": 60,
|
"sleep": 10,
|
||||||
"timeout": 10,
|
"timeout": 600,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -237,7 +245,7 @@ class TestCreateVolume(cloudstackTestCase):
|
|||||||
ssh = self.virtual_machine.get_ssh_client(
|
ssh = self.virtual_machine.get_ssh_client(
|
||||||
reconnect=True
|
reconnect=True
|
||||||
)
|
)
|
||||||
c = "fdisk -l"
|
c = "/sbin/fdisk -l"
|
||||||
res = ssh.execute(c)
|
res = ssh.execute(c)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -283,6 +291,16 @@ class TestVolumes(cloudstackTestCase):
|
|||||||
cls.api_client,
|
cls.api_client,
|
||||||
cls.services["disk_offering"]
|
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(
|
template = get_template(
|
||||||
cls.api_client,
|
cls.api_client,
|
||||||
cls.zone.id,
|
cls.zone.id,
|
||||||
@ -292,6 +310,8 @@ class TestVolumes(cloudstackTestCase):
|
|||||||
cls.services["zoneid"] = cls.zone.id
|
cls.services["zoneid"] = cls.zone.id
|
||||||
cls.services["template"] = template.id
|
cls.services["template"] = template.id
|
||||||
cls.services["diskofferingid"] = cls.disk_offering.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
|
# Create VMs, VMs etc
|
||||||
cls.account = Account.create(
|
cls.account = Account.create(
|
||||||
@ -321,6 +341,8 @@ class TestVolumes(cloudstackTestCase):
|
|||||||
domainid=cls.account.account.domainid
|
domainid=cls.account.account.domainid
|
||||||
)
|
)
|
||||||
cls._cleanup = [
|
cls._cleanup = [
|
||||||
|
cls.resized_disk_offering,
|
||||||
|
cls.custom_resized_disk_offering,
|
||||||
cls.service_offering,
|
cls.service_offering,
|
||||||
cls.disk_offering,
|
cls.disk_offering,
|
||||||
cls.account
|
cls.account
|
||||||
@ -500,7 +522,102 @@ class TestVolumes(cloudstackTestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@attr(tags = ["advanced", "advancedns", "smoke"])
|
@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
|
"""Delete a Volume unattached to an VM
|
||||||
"""
|
"""
|
||||||
# Validate the following
|
# Validate the following
|
||||||
|
|||||||
@ -509,6 +509,12 @@ class Volume:
|
|||||||
[setattr(cmd, k, v) for k, v in kwargs.items()]
|
[setattr(cmd, k, v) for k, v in kwargs.items()]
|
||||||
return(apiclient.listVolumes(cmd))
|
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:
|
class Snapshot:
|
||||||
"""Manage Snapshot Lifecycle
|
"""Manage Snapshot Lifecycle
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user