From adbebc1892cf88df8c801291a88bc836c99b8c14 Mon Sep 17 00:00:00 2001 From: Devdeep Singh Date: Fri, 17 May 2013 11:14:25 +0530 Subject: [PATCH 001/108] Changes for implicitly dedicating a resource. It includes a following: 1. A new implicit planner which extends the functionality provided by FirstFitPlanner. 2. Implicit planner can be used in either strict or preferred mode. In strict mode it tries to deploy a vm of a given account on a host on which vms of the account are already running. If no such host is found it'll search for an empty host to service the request. Otherwise the deploy vm request fails. 3. In preferred mode, if a host which is running vms of the account or an empty host isn't found, the planner then tries to deploy on any other host provided it isn't running implicitly dedicated strict vms of any other account. 4. Updated the createServiceOffering api to configure the details for the planner that the service offering is using. 5. Made db changes to store the service offering details for the planner. 6. Unit tests for testing the implicit planner functionality. 7. Marvin test for validating the functionality. --- .../com/cloud/deploy/DeploymentPlanner.java | 7 + .../apache/cloudstack/api/ApiConstants.java | 1 + .../offering/CreateServiceOfferingCmd.java | 16 + client/pom.xml | 5 + client/tomcatconf/applicationContext.xml.in | 6 +- client/tomcatconf/componentContext.xml.in | 1 + .../service/ServiceOfferingDetailsVO.java | 73 +++ .../com/cloud/service/ServiceOfferingVO.java | 27 + .../cloud/service/dao/ServiceOfferingDao.java | 2 + .../service/dao/ServiceOfferingDaoImpl.java | 19 +- .../dao/ServiceOfferingDetailsDao.java | 29 + .../dao/ServiceOfferingDetailsDaoImpl.java | 98 +++ .../implicit-dedication/pom.xml | 29 + .../deploy/ImplicitDedicationPlanner.java | 249 ++++++++ .../implicitplanner/ImplicitPlannerTest.java | 586 ++++++++++++++++++ plugins/pom.xml | 1 + .../configuration/ConfigurationManager.java | 3 +- .../ConfigurationManagerImpl.java | 20 +- .../vpc/MockConfigurationManagerImpl.java | 2 +- .../ChildTestConfiguration.java | 27 +- setup/db/db/schema-410to420.sql | 11 +- .../component/test_implicit_planner.py | 232 +++++++ tools/marvin/marvin/integration/lib/base.py | 4 + 23 files changed, 1430 insertions(+), 18 deletions(-) create mode 100644 engine/schema/src/com/cloud/service/ServiceOfferingDetailsVO.java create mode 100644 engine/schema/src/com/cloud/service/dao/ServiceOfferingDetailsDao.java create mode 100644 engine/schema/src/com/cloud/service/dao/ServiceOfferingDetailsDaoImpl.java create mode 100644 plugins/deployment-planners/implicit-dedication/pom.xml create mode 100644 plugins/deployment-planners/implicit-dedication/src/com/cloud/deploy/ImplicitDedicationPlanner.java create mode 100644 plugins/deployment-planners/implicit-dedication/test/org/apache/cloudstack/implicitplanner/ImplicitPlannerTest.java create mode 100644 test/integration/component/test_implicit_planner.py diff --git a/api/src/com/cloud/deploy/DeploymentPlanner.java b/api/src/com/cloud/deploy/DeploymentPlanner.java index eb56a591f6b..769da39f3ff 100644 --- a/api/src/com/cloud/deploy/DeploymentPlanner.java +++ b/api/src/com/cloud/deploy/DeploymentPlanner.java @@ -213,6 +213,13 @@ public interface DeploymentPlanner extends Adapter { _hostIds.add(hostId); } + public void addHostList(Collection hostList) { + if (_hostIds == null) { + _hostIds = new HashSet(); + } + _hostIds.addAll(hostList); + } + public boolean shouldAvoid(Host host) { if (_dcIds != null && _dcIds.contains(host.getDataCenterId())) { return true; diff --git a/api/src/org/apache/cloudstack/api/ApiConstants.java b/api/src/org/apache/cloudstack/api/ApiConstants.java index 8d7739c13e1..cf093bf4c7c 100755 --- a/api/src/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/org/apache/cloudstack/api/ApiConstants.java @@ -312,6 +312,7 @@ public class ApiConstants { public static final String ACCEPT = "accept"; public static final String SORT_KEY = "sortkey"; public static final String ACCOUNT_DETAILS = "accountdetails"; + public static final String SERVICE_OFFERING_DETAILS = "serviceofferingdetails"; public static final String SERVICE_PROVIDER_LIST = "serviceproviderlist"; public static final String SERVICE_CAPABILITY_LIST = "servicecapabilitylist"; public static final String CAN_CHOOSE_SERVICE_CAPABILITY = "canchooseservicecapability"; diff --git a/api/src/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java b/api/src/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java index c155b706fc0..4c54a4e5ec6 100644 --- a/api/src/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java +++ b/api/src/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java @@ -16,6 +16,9 @@ // under the License. package org.apache.cloudstack.api.command.admin.offering; +import java.util.Collection; +import java.util.Map; + import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; @@ -87,6 +90,9 @@ public class CreateServiceOfferingCmd extends BaseCmd { @Parameter(name = ApiConstants.DEPLOYMENT_PLANNER, type = CommandType.STRING, description = "The deployment planner heuristics used to deploy a VM of this offering. If null, value of global config vm.deployment.planner is used") private String deploymentPlanner; + @Parameter(name = ApiConstants.SERVICE_OFFERING_DETAILS, type = CommandType.MAP, description = "details for planner, used to store specific parameters") + private Map details; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -155,6 +161,16 @@ public class CreateServiceOfferingCmd extends BaseCmd { return deploymentPlanner; } + public Map getDetails() { + if (details == null || details.isEmpty()) { + return null; + } + + Collection paramsCollection = details.values(); + Map params = (Map)(paramsCollection.toArray())[0]; + return params; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/client/pom.xml b/client/pom.xml index 197ba27975c..0c38ecb65d2 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -131,6 +131,11 @@ cloud-plugin-planner-user-concentrated-pod ${project.version} + + org.apache.cloudstack + cloud-plugin-planner-implicit-dedication + ${project.version} + org.apache.cloudstack cloud-plugin-host-allocator-random diff --git a/client/tomcatconf/applicationContext.xml.in b/client/tomcatconf/applicationContext.xml.in index 1d1eca4c191..b500fde8549 100644 --- a/client/tomcatconf/applicationContext.xml.in +++ b/client/tomcatconf/applicationContext.xml.in @@ -370,7 +370,7 @@ - + diff --git a/engine/schema/src/com/cloud/service/ServiceOfferingDetailsVO.java b/engine/schema/src/com/cloud/service/ServiceOfferingDetailsVO.java new file mode 100644 index 00000000000..b005c738e82 --- /dev/null +++ b/engine/schema/src/com/cloud/service/ServiceOfferingDetailsVO.java @@ -0,0 +1,73 @@ +// 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.service; + +import org.apache.cloudstack.api.InternalIdentity; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name="service_offering_details") +public class ServiceOfferingDetailsVO implements InternalIdentity { + @Id + @GeneratedValue(strategy=GenerationType.IDENTITY) + @Column(name="id") + private long id; + + @Column(name="service_offering_id") + private long serviceOfferingId; + + @Column(name="name") + private String name; + + @Column(name="value") + private String value; + + protected ServiceOfferingDetailsVO() { + } + + public ServiceOfferingDetailsVO(long serviceOfferingId, String name, String value) { + this.serviceOfferingId = serviceOfferingId; + this.name = name; + this.value = value; + } + + public long getServiceOfferingId() { + return serviceOfferingId; + } + + public String getName() { + return name; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public long getId() { + return id; + } +} \ No newline at end of file diff --git a/engine/schema/src/com/cloud/service/ServiceOfferingVO.java b/engine/schema/src/com/cloud/service/ServiceOfferingVO.java index fd31d301bc3..9a262c540b7 100755 --- a/engine/schema/src/com/cloud/service/ServiceOfferingVO.java +++ b/engine/schema/src/com/cloud/service/ServiceOfferingVO.java @@ -16,6 +16,8 @@ // under the License. package com.cloud.service; +import java.util.Map; + import javax.persistence.Column; import javax.persistence.DiscriminatorValue; import javax.persistence.Entity; @@ -71,6 +73,12 @@ public class ServiceOfferingVO extends DiskOfferingVO implements ServiceOffering @Column(name = "deployment_planner") private String deploymentPlanner = null; + // This is a delayed load value. If the value is null, + // then this field has not been loaded yet. + // Call service offering dao to load it. + @Transient + Map details; + protected ServiceOfferingVO() { super(); } @@ -225,4 +233,23 @@ public class ServiceOfferingVO extends DiskOfferingVO implements ServiceOffering return deploymentPlanner; } + public Map getDetails() { + return details; + } + + public String getDetail(String name) { + assert (details != null) : "Did you forget to load the details?"; + + return details != null ? details.get(name) : null; + } + + public void setDetail(String name, String value) { + assert (details != null) : "Did you forget to load the details?"; + + details.put(name, value); + } + + public void setDetails(Map details) { + this.details = details; + } } diff --git a/engine/schema/src/com/cloud/service/dao/ServiceOfferingDao.java b/engine/schema/src/com/cloud/service/dao/ServiceOfferingDao.java index 589de7cc055..7da72088431 100644 --- a/engine/schema/src/com/cloud/service/dao/ServiceOfferingDao.java +++ b/engine/schema/src/com/cloud/service/dao/ServiceOfferingDao.java @@ -31,4 +31,6 @@ public interface ServiceOfferingDao extends GenericDao List findServiceOfferingByDomainId(Long domainId); List findSystemOffering(Long domainId, Boolean isSystem, String vm_type); ServiceOfferingVO persistDeafultServiceOffering(ServiceOfferingVO offering); + void loadDetails(ServiceOfferingVO serviceOffering); + void saveDetails(ServiceOfferingVO serviceOffering); } diff --git a/engine/schema/src/com/cloud/service/dao/ServiceOfferingDaoImpl.java b/engine/schema/src/com/cloud/service/dao/ServiceOfferingDaoImpl.java index 062103e3198..14b2abf8fc4 100644 --- a/engine/schema/src/com/cloud/service/dao/ServiceOfferingDaoImpl.java +++ b/engine/schema/src/com/cloud/service/dao/ServiceOfferingDaoImpl.java @@ -18,15 +18,16 @@ package com.cloud.service.dao; import java.util.Date; import java.util.List; +import java.util.Map; import javax.ejb.Local; +import javax.inject.Inject; import javax.persistence.EntityExistsException; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; import com.cloud.service.ServiceOfferingVO; -import com.cloud.storage.DiskOfferingVO; import com.cloud.utils.db.DB; import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.SearchBuilder; @@ -37,6 +38,8 @@ import com.cloud.utils.db.SearchCriteria; public class ServiceOfferingDaoImpl extends GenericDaoBase implements ServiceOfferingDao { protected static final Logger s_logger = Logger.getLogger(ServiceOfferingDaoImpl.class); + @Inject protected ServiceOfferingDetailsDao detailsDao; + protected final SearchBuilder UniqueNameSearch; protected final SearchBuilder ServiceOfferingsByDomainIdSearch; protected final SearchBuilder SystemServiceOffering; @@ -154,4 +157,18 @@ public class ServiceOfferingDaoImpl extends GenericDaoBase details = detailsDao.findDetails(serviceOffering.getId()); + serviceOffering.setDetails(details); + } + + @Override + public void saveDetails(ServiceOfferingVO serviceOffering) { + Map details = serviceOffering.getDetails(); + if (details != null) { + detailsDao.persist(serviceOffering.getId(), details); + } + } } diff --git a/engine/schema/src/com/cloud/service/dao/ServiceOfferingDetailsDao.java b/engine/schema/src/com/cloud/service/dao/ServiceOfferingDetailsDao.java new file mode 100644 index 00000000000..38169105819 --- /dev/null +++ b/engine/schema/src/com/cloud/service/dao/ServiceOfferingDetailsDao.java @@ -0,0 +1,29 @@ +// 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.service.dao; + +import java.util.Map; + +import com.cloud.service.ServiceOfferingDetailsVO; +import com.cloud.utils.db.GenericDao; + +public interface ServiceOfferingDetailsDao extends GenericDao { + Map findDetails(long serviceOfferingId); + void persist(long serviceOfferingId, Map details); + ServiceOfferingDetailsVO findDetail(long serviceOfferingId, String name); + void deleteDetails(long serviceOfferingId); +} \ No newline at end of file diff --git a/engine/schema/src/com/cloud/service/dao/ServiceOfferingDetailsDaoImpl.java b/engine/schema/src/com/cloud/service/dao/ServiceOfferingDetailsDaoImpl.java new file mode 100644 index 00000000000..91d736a38c4 --- /dev/null +++ b/engine/schema/src/com/cloud/service/dao/ServiceOfferingDetailsDaoImpl.java @@ -0,0 +1,98 @@ +// 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.service.dao; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.ejb.Local; + +import org.springframework.stereotype.Component; + +import com.cloud.service.ServiceOfferingDetailsVO; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.Transaction; + +@Component +@Local(value=ServiceOfferingDetailsDao.class) +public class ServiceOfferingDetailsDaoImpl extends GenericDaoBase + implements ServiceOfferingDetailsDao { + protected final SearchBuilder ServiceOfferingSearch; + protected final SearchBuilder DetailSearch; + + public ServiceOfferingDetailsDaoImpl() { + ServiceOfferingSearch = createSearchBuilder(); + ServiceOfferingSearch.and("serviceOfferingId", ServiceOfferingSearch.entity().getServiceOfferingId(), SearchCriteria.Op.EQ); + ServiceOfferingSearch.done(); + + DetailSearch = createSearchBuilder(); + DetailSearch.and("serviceOfferingId", DetailSearch.entity().getServiceOfferingId(), SearchCriteria.Op.EQ); + DetailSearch.and("name", DetailSearch.entity().getName(), SearchCriteria.Op.EQ); + DetailSearch.done(); + } + + @Override + public ServiceOfferingDetailsVO findDetail(long serviceOfferingId, String name) { + SearchCriteria sc = DetailSearch.create(); + sc.setParameters("serviceOfferingId", serviceOfferingId); + sc.setParameters("name", name); + ServiceOfferingDetailsVO detail = findOneIncludingRemovedBy(sc); + return detail; + } + + @Override + public Map findDetails(long serviceOfferingId) { + SearchCriteria sc = ServiceOfferingSearch.create(); + sc.setParameters("serviceOfferingId", serviceOfferingId); + List results = search(sc, null); + Map details = new HashMap(results.size()); + for (ServiceOfferingDetailsVO result : results) { + details.put(result.getName(), result.getValue()); + } + + return details; + } + + @Override + public void deleteDetails(long serviceOfferingId) { + SearchCriteria sc = ServiceOfferingSearch.create(); + sc.setParameters("serviceOfferingId", serviceOfferingId); + List results = search(sc, null); + for (ServiceOfferingDetailsVO result : results) { + remove(result.getId()); + } + } + + @Override + public void persist(long serviceOfferingId, Map details) { + Transaction txn = Transaction.currentTxn(); + txn.start(); + SearchCriteria sc = ServiceOfferingSearch.create(); + sc.setParameters("serviceOfferingId", serviceOfferingId); + expunge(sc); + + for (Map.Entry detail : details.entrySet()) { + String value = detail.getValue(); + ServiceOfferingDetailsVO vo = new ServiceOfferingDetailsVO(serviceOfferingId, detail.getKey(), value); + persist(vo); + } + txn.commit(); + } +} diff --git a/plugins/deployment-planners/implicit-dedication/pom.xml b/plugins/deployment-planners/implicit-dedication/pom.xml new file mode 100644 index 00000000000..18555923668 --- /dev/null +++ b/plugins/deployment-planners/implicit-dedication/pom.xml @@ -0,0 +1,29 @@ + + + 4.0.0 + cloud-plugin-planner-implicit-dedication + Apache CloudStack Plugin - Implicit Dedication Planner + + org.apache.cloudstack + cloudstack-plugins + 4.2.0-SNAPSHOT + ../../pom.xml + + diff --git a/plugins/deployment-planners/implicit-dedication/src/com/cloud/deploy/ImplicitDedicationPlanner.java b/plugins/deployment-planners/implicit-dedication/src/com/cloud/deploy/ImplicitDedicationPlanner.java new file mode 100644 index 00000000000..d47d8f52c46 --- /dev/null +++ b/plugins/deployment-planners/implicit-dedication/src/com/cloud/deploy/ImplicitDedicationPlanner.java @@ -0,0 +1,249 @@ +// 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.deploy; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.ejb.Local; +import javax.inject.Inject; +import javax.naming.ConfigurationException; + +import org.apache.log4j.Logger; + +import com.cloud.configuration.Config; +import com.cloud.exception.InsufficientServerCapacityException; +import com.cloud.host.HostVO; +import com.cloud.resource.ResourceManager; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.service.dao.ServiceOfferingDetailsDao; +import com.cloud.user.Account; +import com.cloud.utils.DateUtil; +import com.cloud.utils.NumbersUtil; +import com.cloud.vm.UserVmVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachineProfile; + +@Local(value=DeploymentPlanner.class) +public class ImplicitDedicationPlanner extends FirstFitPlanner implements DeploymentClusterPlanner { + + private static final Logger s_logger = Logger.getLogger(ImplicitDedicationPlanner.class); + + @Inject + private ServiceOfferingDao serviceOfferingDao; + @Inject + private ServiceOfferingDetailsDao serviceOfferingDetailsDao; + @Inject + private ResourceManager resourceMgr; + + private int capacityReleaseInterval; + + @Override + public boolean configure(final String name, final Map params) throws ConfigurationException { + super.configure(name, params); + capacityReleaseInterval = NumbersUtil.parseInt(_configDao.getValue(Config.CapacitySkipcountingHours.key()), 3600); + return true; + } + + @Override + public List orderClusters(VirtualMachineProfile vmProfile, + DeploymentPlan plan, ExcludeList avoid) throws InsufficientServerCapacityException { + List clusterList = super.orderClusters(vmProfile, plan, avoid); + Set hostsToAvoid = avoid.getHostsToAvoid(); + Account account = vmProfile.getOwner(); + + if (clusterList == null || clusterList.isEmpty()) { + return clusterList; + } + + // Check if strict or preferred mode should be used. + boolean preferred = isServiceOfferingUsingPlannerInPreferredMode(vmProfile.getServiceOfferingId()); + + // Get the list of all the hosts in the given clusters + List allHosts = new ArrayList(); + for (Long cluster : clusterList) { + List hostsInCluster = resourceMgr.listAllHostsInCluster(cluster); + for (HostVO hostVO : hostsInCluster) { + allHosts.add(hostVO.getId()); + } + } + + // Go over all the hosts in the cluster and get a list of + // 1. All empty hosts, not running any vms. + // 2. Hosts running vms for this account and created by a service offering which uses an + // implicit dedication planner. + // 3. Hosts running vms created by implicit planner and in strict mode of other accounts. + // 4. Hosts running vms from other account or from this account but created by a service offering which uses + // any planner besides implicit. + Set emptyHosts = new HashSet(); + Set hostRunningVmsOfAccount = new HashSet(); + Set hostRunningStrictImplicitVmsOfOtherAccounts = new HashSet(); + Set allOtherHosts = new HashSet(); + for (Long host : allHosts) { + List userVms = getVmsOnHost(host); + if (userVms == null || userVms.isEmpty()) { + emptyHosts.add(host); + } else if (checkHostSuitabilityForImplicitDedication(account.getAccountId(), userVms)) { + hostRunningVmsOfAccount.add(host); + } else if (checkIfAllVmsCreatedInStrictMode(account.getAccountId(), userVms)) { + hostRunningStrictImplicitVmsOfOtherAccounts.add(host); + } else { + allOtherHosts.add(host); + } + } + + // Hosts running vms of other accounts created by ab implicit planner in strict mode should always be avoided. + avoid.addHostList(hostRunningStrictImplicitVmsOfOtherAccounts); + + if (!hostRunningVmsOfAccount.isEmpty() && (hostsToAvoid == null || + !hostsToAvoid.containsAll(hostRunningVmsOfAccount))) { + // Check if any of hosts that are running implicit dedicated vms are available (not in avoid list). + // If so, we'll try and use these hosts. + avoid.addHostList(emptyHosts); + avoid.addHostList(allOtherHosts); + clusterList = getUpdatedClusterList(clusterList, avoid.getHostsToAvoid()); + } else if (!emptyHosts.isEmpty() && (hostsToAvoid == null || !hostsToAvoid.containsAll(emptyHosts))) { + // If there aren't implicit resources try on empty hosts + avoid.addHostList(allOtherHosts); + clusterList = getUpdatedClusterList(clusterList, avoid.getHostsToAvoid()); + } else if (!preferred) { + // If in strict mode, there is nothing else to try. + clusterList = null; + } else { + // If in preferred mode, check if hosts are available to try, otherwise return an empty cluster list. + if (!allOtherHosts.isEmpty() && (hostsToAvoid == null || !hostsToAvoid.containsAll(allOtherHosts))) { + clusterList = getUpdatedClusterList(clusterList, avoid.getHostsToAvoid()); + } else { + clusterList = null; + } + } + + return clusterList; + } + + private List getVmsOnHost(long hostId) { + List vms = _vmDao.listUpByHostId(hostId); + List vmsByLastHostId = _vmDao.listByLastHostId(hostId); + if (vmsByLastHostId.size() > 0) { + // check if any VMs are within skip.counting.hours, if yes we have to consider the host. + for (UserVmVO stoppedVM : vmsByLastHostId) { + long secondsSinceLastUpdate = (DateUtil.currentGMTTime().getTime() - stoppedVM.getUpdateTime() + .getTime()) / 1000; + if (secondsSinceLastUpdate < capacityReleaseInterval) { + vms.add(stoppedVM); + } + } + } + + return vms; + } + + private boolean checkHostSuitabilityForImplicitDedication(Long accountId, List allVmsOnHost) { + boolean suitable = true; + for (UserVmVO vm : allVmsOnHost) { + if (vm.getAccountId() != accountId) { + s_logger.info("Host " + vm.getHostId() + " found to be unsuitable for implicit dedication as it is " + + "running instances of another account"); + suitable = false; + break; + } else { + if (!isImplicitPlannerUsedByOffering(vm.getServiceOfferingId())) { + s_logger.info("Host " + vm.getHostId() + " found to be unsuitable for implicit dedication as it " + + "is running instances of this account which haven't been created using implicit dedication."); + suitable = false; + break; + } + } + } + return suitable; + } + + private boolean checkIfAllVmsCreatedInStrictMode(Long accountId, List allVmsOnHost) { + boolean createdByImplicitStrict = true; + for (UserVmVO vm : allVmsOnHost) { + if (!isImplicitPlannerUsedByOffering(vm.getServiceOfferingId())) { + s_logger.info("Host " + vm.getHostId() + " found to be running a vm created by a planner other" + + " than implicit."); + createdByImplicitStrict = false; + break; + } else if (isServiceOfferingUsingPlannerInPreferredMode(vm.getServiceOfferingId())) { + s_logger.info("Host " + vm.getHostId() + " found to be running a vm created by an implicit planner" + + " in preferred mode."); + createdByImplicitStrict = false; + break; + } + } + return createdByImplicitStrict; + } + + private boolean isImplicitPlannerUsedByOffering(long offeringId) { + boolean implicitPlannerUsed = false; + ServiceOfferingVO offering = serviceOfferingDao.findByIdIncludingRemoved(offeringId); + if (offering == null) { + s_logger.error("Couldn't retrieve the offering by the given id : " + offeringId); + } else { + String plannerName = offering.getDeploymentPlanner(); + if (plannerName == null) { + plannerName = _globalDeploymentPlanner; + } + + if (plannerName != null && this.getName().equals(plannerName)) { + implicitPlannerUsed = true; + } + } + + return implicitPlannerUsed; + } + + private boolean isServiceOfferingUsingPlannerInPreferredMode(long serviceOfferingId) { + boolean preferred = false; + Map details = serviceOfferingDetailsDao.findDetails(serviceOfferingId); + if (details != null && !details.isEmpty()) { + String preferredAttribute = details.get("ImplicitDedicationMode"); + if (preferredAttribute != null && preferredAttribute.equals("Preferred")) { + preferred = true; + } + } + return preferred; + } + + private List getUpdatedClusterList(List clusterList, Set hostsSet) { + List updatedClusterList = new ArrayList(); + for (Long cluster : clusterList) { + List hosts = resourceMgr.listAllHostsInCluster(cluster); + Set hostsInClusterSet = new HashSet(); + for (HostVO host : hosts) { + hostsInClusterSet.add(host.getId()); + } + + if (!hostsSet.containsAll(hostsInClusterSet)) { + updatedClusterList.add(cluster); + } + } + + return updatedClusterList; + } + + @Override + public PlannerResourceUsage getResourceUsage() { + return PlannerResourceUsage.Dedicated; + } +} \ No newline at end of file diff --git a/plugins/deployment-planners/implicit-dedication/test/org/apache/cloudstack/implicitplanner/ImplicitPlannerTest.java b/plugins/deployment-planners/implicit-dedication/test/org/apache/cloudstack/implicitplanner/ImplicitPlannerTest.java new file mode 100644 index 00000000000..44507600db9 --- /dev/null +++ b/plugins/deployment-planners/implicit-dedication/test/org/apache/cloudstack/implicitplanner/ImplicitPlannerTest.java @@ -0,0 +1,586 @@ +// 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.implicitplanner; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; + +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.test.utils.SpringUtils; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.FilterType; +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.core.type.filter.TypeFilter; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +import com.cloud.capacity.CapacityManager; +import com.cloud.capacity.CapacityVO; +import com.cloud.capacity.dao.CapacityDao; +import com.cloud.configuration.dao.ConfigurationDao; +import com.cloud.dc.ClusterDetailsDao; +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.dao.ClusterDao; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.dc.dao.HostPodDao; +import com.cloud.deploy.DataCenterDeployment; +import com.cloud.deploy.DeploymentPlanner.ExcludeList; +import com.cloud.deploy.ImplicitDedicationPlanner; +import com.cloud.exception.InsufficientServerCapacityException; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.resource.ResourceManager; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.service.dao.ServiceOfferingDetailsDao; +import com.cloud.storage.StorageManager; +import com.cloud.storage.dao.DiskOfferingDao; +import com.cloud.storage.dao.GuestOSCategoryDao; +import com.cloud.storage.dao.GuestOSDao; +import com.cloud.storage.dao.StoragePoolHostDao; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.user.Account; +import com.cloud.user.AccountManager; +import com.cloud.user.AccountVO; +import com.cloud.user.UserContext; +import com.cloud.utils.Pair; +import com.cloud.utils.component.ComponentContext; +import com.cloud.vm.UserVmVO; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachineProfileImpl; +import com.cloud.vm.dao.UserVmDao; +import com.cloud.vm.dao.VMInstanceDao; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(loader = AnnotationConfigContextLoader.class) +public class ImplicitPlannerTest { + + @Inject + ImplicitDedicationPlanner planner = new ImplicitDedicationPlanner(); + @Inject + HostDao hostDao; + @Inject + DataCenterDao dcDao; + @Inject + HostPodDao podDao; + @Inject + ClusterDao clusterDao; + @Inject + GuestOSDao guestOSDao; + @Inject + GuestOSCategoryDao guestOSCategoryDao; + @Inject + DiskOfferingDao diskOfferingDao; + @Inject + StoragePoolHostDao poolHostDao; + @Inject + UserVmDao vmDao; + @Inject + VMInstanceDao vmInstanceDao; + @Inject + VolumeDao volsDao; + @Inject + CapacityManager capacityMgr; + @Inject + ConfigurationDao configDao; + @Inject + PrimaryDataStoreDao storagePoolDao; + @Inject + CapacityDao capacityDao; + @Inject + AccountManager accountMgr; + @Inject + StorageManager storageMgr; + @Inject + DataStoreManager dataStoreMgr; + @Inject + ClusterDetailsDao clusterDetailsDao; + @Inject + ServiceOfferingDao serviceOfferingDao; + @Inject + ServiceOfferingDetailsDao serviceOfferingDetailsDao; + @Inject + ResourceManager resourceMgr; + + private static long domainId = 5L; + long dataCenterId = 1L; + long accountId = 200L; + long offeringId = 12L; + int noOfCpusInOffering = 1; + int cpuSpeedInOffering = 500; + int ramInOffering = 512; + AccountVO acct = new AccountVO(accountId); + + @BeforeClass + public static void setUp() throws ConfigurationException { + } + + @Before + public void testSetUp() { + ComponentContext.initComponentsLifeCycle(); + + acct.setType(Account.ACCOUNT_TYPE_NORMAL); + acct.setAccountName("user1"); + acct.setDomainId(domainId); + acct.setId(accountId); + + UserContext.registerContext(1, acct, null, true); + } + + @Test + public void checkWhenDcInAvoidList() throws InsufficientServerCapacityException { + DataCenterVO mockDc = mock(DataCenterVO.class); + ExcludeList avoids = mock(ExcludeList.class); + @SuppressWarnings("unchecked") + VirtualMachineProfileImpl vmProfile = mock(VirtualMachineProfileImpl.class); + VMInstanceVO vm = mock(VMInstanceVO.class); + DataCenterDeployment plan = mock(DataCenterDeployment.class); + + when(avoids.shouldAvoid(mockDc)).thenReturn(true); + when(vmProfile.getVirtualMachine()).thenReturn(vm); + when(vm.getDataCenterId()).thenReturn(1L); + when(dcDao.findById(1L)).thenReturn(mockDc); + + List clusterList = planner.orderClusters(vmProfile, plan, avoids); + assertTrue("Cluster list should be null/empty if the dc is in avoid list", + (clusterList == null || clusterList.isEmpty())); + } + + @Test + public void checkStrictModeWithCurrentAccountVmsPresent() throws InsufficientServerCapacityException { + @SuppressWarnings("unchecked") + VirtualMachineProfileImpl vmProfile = mock(VirtualMachineProfileImpl.class); + DataCenterDeployment plan = mock(DataCenterDeployment.class); + ExcludeList avoids = new ExcludeList(); + + initializeForTest(vmProfile, plan); + + initializeForImplicitPlannerTest(false); + + List clusterList = planner.orderClusters(vmProfile, plan, avoids); + + // Validations. + // Check cluster 2 and 3 are not in the cluster list. + // Host 6 and 7 should also be in avoid list. + assertFalse("Cluster list should not be null/empty", (clusterList == null || clusterList.isEmpty())); + boolean foundNeededCluster = false; + for (Long cluster : clusterList) { + if (cluster != 1) { + fail("Found a cluster that shouldn't have been present, cluster id : " + cluster); + }else { + foundNeededCluster = true; + } + } + assertTrue("Didn't find cluster 1 in the list. It should have been present", foundNeededCluster); + + Set hostsInAvoidList = avoids.getHostsToAvoid(); + assertFalse("Host 5 shouldn't have be in the avoid list, but it is present", hostsInAvoidList.contains(5L)); + Set hostsThatShouldBeInAvoidList = new HashSet(); + hostsThatShouldBeInAvoidList.add(6L); + hostsThatShouldBeInAvoidList.add(7L); + assertTrue("Hosts 6 and 7 that should have been present were not found in avoid list" , + hostsInAvoidList.containsAll(hostsThatShouldBeInAvoidList)); + } + + @Test + public void checkStrictModeHostWithCurrentAccountVmsFull() throws InsufficientServerCapacityException { + @SuppressWarnings("unchecked") + VirtualMachineProfileImpl vmProfile = mock(VirtualMachineProfileImpl.class); + DataCenterDeployment plan = mock(DataCenterDeployment.class); + ExcludeList avoids = new ExcludeList(); + + initializeForTest(vmProfile, plan); + + initializeForImplicitPlannerTest(false); + + // Mark the host 5 with current account vms to be in avoid list. + avoids.addHost(5L); + List clusterList = planner.orderClusters(vmProfile, plan, avoids); + + // Validations. + // Check cluster 1 and 3 are not in the cluster list. + // Host 5 and 7 should also be in avoid list. + assertFalse("Cluster list should not be null/empty", (clusterList == null || clusterList.isEmpty())); + boolean foundNeededCluster = false; + for (Long cluster : clusterList) { + if (cluster != 2) { + fail("Found a cluster that shouldn't have been present, cluster id : " + cluster); + }else { + foundNeededCluster = true; + } + } + assertTrue("Didn't find cluster 2 in the list. It should have been present", foundNeededCluster); + + Set hostsInAvoidList = avoids.getHostsToAvoid(); + assertFalse("Host 6 shouldn't have be in the avoid list, but it is present", hostsInAvoidList.contains(6L)); + Set hostsThatShouldBeInAvoidList = new HashSet(); + hostsThatShouldBeInAvoidList.add(5L); + hostsThatShouldBeInAvoidList.add(7L); + assertTrue("Hosts 5 and 7 that should have been present were not found in avoid list" , + hostsInAvoidList.containsAll(hostsThatShouldBeInAvoidList)); + } + + @Test + public void checkStrictModeNoHostsAvailable() throws InsufficientServerCapacityException { + @SuppressWarnings("unchecked") + VirtualMachineProfileImpl vmProfile = mock(VirtualMachineProfileImpl.class); + DataCenterDeployment plan = mock(DataCenterDeployment.class); + ExcludeList avoids = new ExcludeList(); + + initializeForTest(vmProfile, plan); + + initializeForImplicitPlannerTest(false); + + // Mark the host 5 and 6 to be in avoid list. + avoids.addHost(5L); + avoids.addHost(6L); + List clusterList = planner.orderClusters(vmProfile, plan, avoids); + + // Validations. + // Check cluster list is empty. + assertTrue("Cluster list should not be null/empty", (clusterList == null || clusterList.isEmpty())); + } + + @Test + public void checkPreferredModePreferredHostAvailable() throws InsufficientServerCapacityException { + @SuppressWarnings("unchecked") + VirtualMachineProfileImpl vmProfile = mock(VirtualMachineProfileImpl.class); + DataCenterDeployment plan = mock(DataCenterDeployment.class); + ExcludeList avoids = new ExcludeList(); + + initializeForTest(vmProfile, plan); + + initializeForImplicitPlannerTest(true); + + // Mark the host 5 and 6 to be in avoid list. + avoids.addHost(5L); + avoids.addHost(6L); + List clusterList = planner.orderClusters(vmProfile, plan, avoids); + + // Validations. + // Check cluster 1 and 2 are not in the cluster list. + // Host 5 and 6 should also be in avoid list. + assertFalse("Cluster list should not be null/empty", (clusterList == null || clusterList.isEmpty())); + boolean foundNeededCluster = false; + for (Long cluster : clusterList) { + if (cluster != 3) { + fail("Found a cluster that shouldn't have been present, cluster id : " + cluster); + } else { + foundNeededCluster = true; + } + } + assertTrue("Didn't find cluster 3 in the list. It should have been present", foundNeededCluster); + + Set hostsInAvoidList = avoids.getHostsToAvoid(); + assertFalse("Host 7 shouldn't have be in the avoid list, but it is present", hostsInAvoidList.contains(7L)); + Set hostsThatShouldBeInAvoidList = new HashSet(); + hostsThatShouldBeInAvoidList.add(5L); + hostsThatShouldBeInAvoidList.add(6L); + assertTrue("Hosts 5 and 6 that should have been present were not found in avoid list" , + hostsInAvoidList.containsAll(hostsThatShouldBeInAvoidList)); + } + + @Test + public void checkPreferredModeNoHostsAvailable() throws InsufficientServerCapacityException { + @SuppressWarnings("unchecked") + VirtualMachineProfileImpl vmProfile = mock(VirtualMachineProfileImpl.class); + DataCenterDeployment plan = mock(DataCenterDeployment.class); + ExcludeList avoids = new ExcludeList(); + + initializeForTest(vmProfile, plan); + + initializeForImplicitPlannerTest(false); + + // Mark the host 5, 6 and 7 to be in avoid list. + avoids.addHost(5L); + avoids.addHost(6L); + avoids.addHost(7L); + List clusterList = planner.orderClusters(vmProfile, plan, avoids); + + // Validations. + // Check cluster list is empty. + assertTrue("Cluster list should not be null/empty", (clusterList == null || clusterList.isEmpty())); + } + + private void initializeForTest(VirtualMachineProfileImpl vmProfile, DataCenterDeployment plan) { + DataCenterVO mockDc = mock(DataCenterVO.class); + VMInstanceVO vm = mock(VMInstanceVO.class); + UserVmVO userVm = mock(UserVmVO.class); + ServiceOfferingVO offering = mock(ServiceOfferingVO.class); + + AccountVO account = mock(AccountVO.class); + when(account.getId()).thenReturn(accountId); + when(account.getAccountId()).thenReturn(accountId); + when(vmProfile.getOwner()).thenReturn(account); + when(vmProfile.getVirtualMachine()).thenReturn(vm); + when(vmProfile.getId()).thenReturn(12L); + when(vmDao.findById(12L)).thenReturn(userVm); + when(userVm.getAccountId()).thenReturn(accountId); + + when(vm.getDataCenterId()).thenReturn(dataCenterId); + when(dcDao.findById(1L)).thenReturn(mockDc); + when(plan.getDataCenterId()).thenReturn(dataCenterId); + when(plan.getClusterId()).thenReturn(null); + when(plan.getPodId()).thenReturn(null); + when(configDao.getValue(anyString())).thenReturn("false").thenReturn("CPU"); + + // Mock offering details. + when(vmProfile.getServiceOffering()).thenReturn(offering); + when(offering.getId()).thenReturn(offeringId); + when(vmProfile.getServiceOfferingId()).thenReturn(offeringId); + when(offering.getCpu()).thenReturn(noOfCpusInOffering); + when(offering.getSpeed()).thenReturn(cpuSpeedInOffering); + when(offering.getRamSize()).thenReturn(ramInOffering); + + List clustersWithEnoughCapacity = new ArrayList(); + clustersWithEnoughCapacity.add(1L); + clustersWithEnoughCapacity.add(2L); + clustersWithEnoughCapacity.add(3L); + when(capacityDao.listClustersInZoneOrPodByHostCapacities(dataCenterId, noOfCpusInOffering * cpuSpeedInOffering, + ramInOffering * 1024L * 1024L, CapacityVO.CAPACITY_TYPE_CPU, true)).thenReturn(clustersWithEnoughCapacity); + + Map clusterCapacityMap = new HashMap(); + clusterCapacityMap.put(1L, 2048D); + clusterCapacityMap.put(2L, 2048D); + clusterCapacityMap.put(3L, 2048D); + Pair, Map> clustersOrderedByCapacity = + new Pair, Map>(clustersWithEnoughCapacity, clusterCapacityMap); + when(capacityDao.orderClustersByAggregateCapacity(dataCenterId, CapacityVO.CAPACITY_TYPE_CPU, + true)).thenReturn(clustersOrderedByCapacity); + + List disabledClusters = new ArrayList(); + List clustersWithDisabledPods = new ArrayList(); + when(clusterDao.listDisabledClusters(dataCenterId, null)).thenReturn(disabledClusters); + when(clusterDao.listClustersWithDisabledPods(dataCenterId)).thenReturn(clustersWithDisabledPods); + } + + private void initializeForImplicitPlannerTest(boolean preferred) { + String plannerMode = new String("Strict"); + if (preferred) { + plannerMode = new String("Preferred"); + } + + Map details = new HashMap(); + details.put("ImplicitDedicationMode", plannerMode); + when(serviceOfferingDetailsDao.findDetails(offeringId)).thenReturn(details); + + // Initialize hosts in clusters + HostVO host1 = mock(HostVO.class); + when(host1.getId()).thenReturn(5L); + HostVO host2 = mock(HostVO.class); + when(host2.getId()).thenReturn(6L); + HostVO host3 = mock(HostVO.class); + when(host3.getId()).thenReturn(7L); + List hostsInCluster1 = new ArrayList(); + List hostsInCluster2 = new ArrayList(); + List hostsInCluster3 = new ArrayList(); + hostsInCluster1.add(host1); + hostsInCluster2.add(host2); + hostsInCluster3.add(host3); + when(resourceMgr.listAllHostsInCluster(1)).thenReturn(hostsInCluster1); + when(resourceMgr.listAllHostsInCluster(2)).thenReturn(hostsInCluster2); + when(resourceMgr.listAllHostsInCluster(3)).thenReturn(hostsInCluster3); + + // Mock vms on each host. + long offeringIdForVmsOfThisAccount = 15L; + long offeringIdForVmsOfOtherAccount = 16L; + UserVmVO vm1 = mock(UserVmVO.class); + when(vm1.getAccountId()).thenReturn(accountId); + when(vm1.getServiceOfferingId()).thenReturn(offeringIdForVmsOfThisAccount); + UserVmVO vm2 = mock(UserVmVO.class); + when(vm2.getAccountId()).thenReturn(accountId); + when(vm2.getServiceOfferingId()).thenReturn(offeringIdForVmsOfThisAccount); + // Vm from different account + UserVmVO vm3 = mock(UserVmVO.class); + when(vm3.getAccountId()).thenReturn(201L); + when(vm3.getServiceOfferingId()).thenReturn(offeringIdForVmsOfOtherAccount); + List userVmsForHost1 = new ArrayList(); + List userVmsForHost2 = new ArrayList(); + List userVmsForHost3 = new ArrayList(); + List stoppedVmsForHost = new ArrayList(); + // Host 2 is empty. + userVmsForHost1.add(vm1); + userVmsForHost1.add(vm2); + userVmsForHost3.add(vm3); + when(vmDao.listUpByHostId(5L)).thenReturn(userVmsForHost1); + when(vmDao.listUpByHostId(6L)).thenReturn(userVmsForHost2); + when(vmDao.listUpByHostId(7L)).thenReturn(userVmsForHost3); + when(vmDao.listByLastHostId(5L)).thenReturn(stoppedVmsForHost); + when(vmDao.listByLastHostId(6L)).thenReturn(stoppedVmsForHost); + when(vmDao.listByLastHostId(7L)).thenReturn(stoppedVmsForHost); + + // Mock the offering with which the vm was created. + ServiceOfferingVO offeringForVmOfThisAccount = mock(ServiceOfferingVO.class); + when(serviceOfferingDao.findByIdIncludingRemoved(offeringIdForVmsOfThisAccount)).thenReturn(offeringForVmOfThisAccount); + when(offeringForVmOfThisAccount.getDeploymentPlanner()).thenReturn(planner.getName()); + + ServiceOfferingVO offeringForVMOfOtherAccount = mock(ServiceOfferingVO.class); + when(serviceOfferingDao.findByIdIncludingRemoved(offeringIdForVmsOfOtherAccount)).thenReturn(offeringForVMOfOtherAccount); + when(offeringForVMOfOtherAccount.getDeploymentPlanner()).thenReturn("FirstFitPlanner"); + } + + @Configuration + @ComponentScan(basePackageClasses = { ImplicitDedicationPlanner.class }, + includeFilters = {@Filter(value = TestConfiguration.Library.class, type = FilterType.CUSTOM)}, + useDefaultFilters = false) + public static class TestConfiguration extends SpringUtils.CloudStackTestConfiguration { + + @Bean + public HostDao hostDao() { + return Mockito.mock(HostDao.class); + } + + @Bean + public DataCenterDao dcDao() { + return Mockito.mock(DataCenterDao.class); + } + + @Bean + public HostPodDao hostPodDao() { + return Mockito.mock(HostPodDao.class); + } + + @Bean + public ClusterDao clusterDao() { + return Mockito.mock(ClusterDao.class); + } + + @Bean + public GuestOSDao guestOsDao() { + return Mockito.mock(GuestOSDao.class); + } + + @Bean + public GuestOSCategoryDao guestOsCategoryDao() { + return Mockito.mock(GuestOSCategoryDao.class); + } + + @Bean + public DiskOfferingDao diskOfferingDao() { + return Mockito.mock(DiskOfferingDao.class); + } + + @Bean + public StoragePoolHostDao storagePoolHostDao() { + return Mockito.mock(StoragePoolHostDao.class); + } + + @Bean + public UserVmDao userVmDao() { + return Mockito.mock(UserVmDao.class); + } + + @Bean + public VMInstanceDao vmInstanceDao() { + return Mockito.mock(VMInstanceDao.class); + } + + @Bean + public VolumeDao volumeDao() { + return Mockito.mock(VolumeDao.class); + } + + @Bean + public CapacityManager capacityManager() { + return Mockito.mock(CapacityManager.class); + } + + @Bean + public ConfigurationDao configurationDao() { + return Mockito.mock(ConfigurationDao.class); + } + + @Bean + public PrimaryDataStoreDao primaryDataStoreDao() { + return Mockito.mock(PrimaryDataStoreDao.class); + } + + @Bean + public CapacityDao capacityDao() { + return Mockito.mock(CapacityDao.class); + } + + @Bean + public AccountManager accountManager() { + return Mockito.mock(AccountManager.class); + } + + @Bean + public StorageManager storageManager() { + return Mockito.mock(StorageManager.class); + } + + @Bean + public DataStoreManager dataStoreManager() { + return Mockito.mock(DataStoreManager.class); + } + + @Bean + public ClusterDetailsDao clusterDetailsDao() { + return Mockito.mock(ClusterDetailsDao.class); + } + + @Bean + public ServiceOfferingDao serviceOfferingDao() { + return Mockito.mock(ServiceOfferingDao.class); + } + + @Bean + public ServiceOfferingDetailsDao serviceOfferingDetailsDao() { + return Mockito.mock(ServiceOfferingDetailsDao.class); + } + + @Bean + public ResourceManager resourceManager() { + return Mockito.mock(ResourceManager.class); + } + + public static class Library implements TypeFilter { + @Override + public boolean match(MetadataReader mdr, MetadataReaderFactory arg1) throws IOException { + ComponentScan cs = TestConfiguration.class.getAnnotation(ComponentScan.class); + return SpringUtils.includedInBasePackageClasses(mdr.getClassMetadata().getClassName(), cs); + } + } + } +} \ No newline at end of file diff --git a/plugins/pom.xml b/plugins/pom.xml index e49fac9533a..2efa2488e86 100755 --- a/plugins/pom.xml +++ b/plugins/pom.xml @@ -38,6 +38,7 @@ affinity-group-processors/host-anti-affinity deployment-planners/user-concentrated-pod deployment-planners/user-dispersing + deployment-planners/implicit-dedication host-allocators/random hypervisors/ovm hypervisors/xen diff --git a/server/src/com/cloud/configuration/ConfigurationManager.java b/server/src/com/cloud/configuration/ConfigurationManager.java index d0ae914c20f..8db037b24ff 100755 --- a/server/src/com/cloud/configuration/ConfigurationManager.java +++ b/server/src/com/cloud/configuration/ConfigurationManager.java @@ -80,10 +80,11 @@ public interface ConfigurationManager extends ConfigurationService, Manager { * @param id * @param useVirtualNetwork * @param deploymentPlanner + * @param details * @return ID */ ServiceOfferingVO createServiceOffering(long userId, boolean isSystem, VirtualMachine.Type vm_typeType, String name, int cpu, int ramSize, int speed, String displayText, boolean localStorageRequired, - boolean offerHA, boolean limitResourceUse, boolean volatileVm, String tags, Long domainId, String hostTag, Integer networkRate, String deploymentPlanner); + boolean offerHA, boolean limitResourceUse, boolean volatileVm, String tags, Long domainId, String hostTag, Integer networkRate, String deploymentPlanner, Map details); /** * Creates a new disk offering diff --git a/server/src/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/com/cloud/configuration/ConfigurationManagerImpl.java index 9e0c847ed57..52d617646af 100755 --- a/server/src/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/com/cloud/configuration/ConfigurationManagerImpl.java @@ -39,7 +39,6 @@ import javax.naming.NamingException; import javax.naming.directory.DirContext; import javax.naming.directory.InitialDirContext; - import com.cloud.dc.*; import com.cloud.dc.dao.*; import com.cloud.user.*; @@ -105,7 +104,6 @@ import com.cloud.dc.dao.DcDetailsDao; import com.cloud.dc.dao.HostPodDao; import com.cloud.dc.dao.PodVlanMapDao; import com.cloud.dc.dao.VlanDao; - import com.cloud.deploy.DataCenterDeployment; import com.cloud.domain.Domain; import com.cloud.domain.DomainVO; @@ -165,6 +163,7 @@ import com.cloud.server.ConfigurationServer; import com.cloud.server.ManagementService; import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.service.dao.ServiceOfferingDetailsDao; import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.SwiftVO; import com.cloud.storage.dao.DiskOfferingDao; @@ -277,6 +276,8 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati @Inject ServiceOfferingDao _serviceOfferingDao; @Inject + ServiceOfferingDetailsDao _serviceOfferingDetailsDao; + @Inject DiskOfferingDao _diskOfferingDao; @Inject NetworkOfferingDao _networkOfferingDao; @@ -2050,19 +2051,26 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati } } - return createServiceOffering(userId, cmd.getIsSystem(), vmType, cmd.getServiceOfferingName(), cpuNumber.intValue(), memory.intValue(), cpuSpeed.intValue(), cmd.getDisplayText(), - localStorageRequired, offerHA, limitCpuUse, volatileVm, cmd.getTags(), cmd.getDomainId(), cmd.getHostTag(), cmd.getNetworkRate(), cmd.getDeploymentPlanner()); + return createServiceOffering(userId, cmd.getIsSystem(), vmType, cmd.getServiceOfferingName(), + cpuNumber.intValue(), memory.intValue(), cpuSpeed.intValue(), cmd.getDisplayText(), + localStorageRequired, offerHA, limitCpuUse, volatileVm, cmd.getTags(), cmd.getDomainId(), + cmd.getHostTag(), cmd.getNetworkRate(), cmd.getDeploymentPlanner(), cmd.getDetails()); } @Override @ActionEvent(eventType = EventTypes.EVENT_SERVICE_OFFERING_CREATE, eventDescription = "creating service offering") - public ServiceOfferingVO createServiceOffering(long userId, boolean isSystem, VirtualMachine.Type vm_type, String name, int cpu, int ramSize, int speed, String displayText, - boolean localStorageRequired, boolean offerHA, boolean limitResourceUse, boolean volatileVm, String tags, Long domainId, String hostTag, Integer networkRate, String deploymentPlanner) { + public ServiceOfferingVO createServiceOffering(long userId, boolean isSystem, VirtualMachine.Type vm_type, + String name, int cpu, int ramSize, int speed, String displayText, boolean localStorageRequired, + boolean offerHA, boolean limitResourceUse, boolean volatileVm, String tags, Long domainId, String hostTag, + Integer networkRate, String deploymentPlanner, Map details) { tags = cleanupTags(tags); ServiceOfferingVO offering = new ServiceOfferingVO(name, cpu, ramSize, speed, networkRate, null, offerHA, limitResourceUse, volatileVm, displayText, localStorageRequired, false, tags, isSystem, vm_type, domainId, hostTag, deploymentPlanner); if ((offering = _serviceOfferingDao.persist(offering)) != null) { + if (details != null) { + _serviceOfferingDetailsDao.persist(offering.getId(), details); + } UserContext.current().setEventDetails("Service offering id=" + offering.getId()); return offering; } else { diff --git a/server/test/com/cloud/vpc/MockConfigurationManagerImpl.java b/server/test/com/cloud/vpc/MockConfigurationManagerImpl.java index ba18fa1c11d..4fb182aae14 100755 --- a/server/test/com/cloud/vpc/MockConfigurationManagerImpl.java +++ b/server/test/com/cloud/vpc/MockConfigurationManagerImpl.java @@ -431,7 +431,7 @@ public class MockConfigurationManagerImpl extends ManagerBase implements Configu */ @Override public ServiceOfferingVO createServiceOffering(long userId, boolean isSystem, Type vm_typeType, String name, int cpu, int ramSize, int speed, String displayText, boolean localStorageRequired, boolean offerHA, - boolean limitResourceUse, boolean volatileVm, String tags, Long domainId, String hostTag, Integer networkRate, String deploymentPlanner) { + boolean limitResourceUse, boolean volatileVm, String tags, Long domainId, String hostTag, Integer networkRate, String deploymentPlanner, Map details) { // TODO Auto-generated method stub return null; } diff --git a/server/test/org/apache/cloudstack/networkoffering/ChildTestConfiguration.java b/server/test/org/apache/cloudstack/networkoffering/ChildTestConfiguration.java index 7ffbe32d8bd..a8256990973 100644 --- a/server/test/org/apache/cloudstack/networkoffering/ChildTestConfiguration.java +++ b/server/test/org/apache/cloudstack/networkoffering/ChildTestConfiguration.java @@ -19,14 +19,8 @@ package org.apache.cloudstack.networkoffering; import java.io.IOException; -import com.cloud.dc.ClusterDetailsDao; -import com.cloud.dc.dao.*; -import com.cloud.server.ConfigurationServer; -import com.cloud.user.*; import org.apache.cloudstack.acl.SecurityChecker; -import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDaoImpl; -import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao; import org.apache.cloudstack.test.utils.SpringUtils; import org.mockito.Mockito; import org.springframework.context.annotation.Bean; @@ -44,6 +38,18 @@ import com.cloud.api.query.dao.UserAccountJoinDaoImpl; import com.cloud.capacity.dao.CapacityDaoImpl; import com.cloud.cluster.agentlb.dao.HostTransferMapDaoImpl; import com.cloud.configuration.dao.ConfigurationDao; +import com.cloud.dc.ClusterDetailsDao; +import com.cloud.dc.dao.AccountVlanMapDaoImpl; +import com.cloud.dc.dao.ClusterDaoImpl; +import com.cloud.dc.dao.DataCenterDaoImpl; +import com.cloud.dc.dao.DataCenterIpAddressDaoImpl; +import com.cloud.dc.dao.DataCenterLinkLocalIpAddressDao; +import com.cloud.dc.dao.DataCenterVnetDaoImpl; +import com.cloud.dc.dao.DcDetailsDaoImpl; +import com.cloud.dc.dao.HostPodDaoImpl; +import com.cloud.dc.dao.PodVlanDaoImpl; +import com.cloud.dc.dao.PodVlanMapDaoImpl; +import com.cloud.dc.dao.VlanDaoImpl; import com.cloud.domain.dao.DomainDaoImpl; import com.cloud.event.dao.UsageEventDaoImpl; import com.cloud.host.dao.HostDaoImpl; @@ -80,10 +86,11 @@ import com.cloud.network.vpc.dao.PrivateIpDaoImpl; import com.cloud.network.vpn.RemoteAccessVpnService; import com.cloud.offerings.dao.NetworkOfferingDao; import com.cloud.offerings.dao.NetworkOfferingServiceMapDao; -import com.cloud.offerings.dao.NetworkOfferingServiceMapDaoImpl; import com.cloud.projects.ProjectManager; +import com.cloud.server.ConfigurationServer; import com.cloud.server.ManagementService; import com.cloud.service.dao.ServiceOfferingDaoImpl; +import com.cloud.service.dao.ServiceOfferingDetailsDaoImpl; import com.cloud.storage.dao.DiskOfferingDaoImpl; import com.cloud.storage.dao.S3DaoImpl; import com.cloud.storage.dao.SnapshotDaoImpl; @@ -94,6 +101,11 @@ import com.cloud.storage.s3.S3Manager; import com.cloud.storage.secondary.SecondaryStorageVmManager; import com.cloud.storage.swift.SwiftManager; import com.cloud.tags.dao.ResourceTagsDaoImpl; +import com.cloud.user.AccountDetailsDao; +import com.cloud.user.AccountManager; +import com.cloud.user.ResourceLimitService; +import com.cloud.user.UserContext; +import com.cloud.user.UserContextInitializer; import com.cloud.user.dao.AccountDaoImpl; import com.cloud.user.dao.UserDaoImpl; import com.cloud.vm.dao.InstanceGroupDaoImpl; @@ -110,6 +122,7 @@ import com.cloud.vm.dao.VMInstanceDaoImpl; DomainDaoImpl.class, SwiftDaoImpl.class, ServiceOfferingDaoImpl.class, + ServiceOfferingDetailsDaoImpl.class, VlanDaoImpl.class, IPAddressDaoImpl.class, ResourceTagsDaoImpl.class, diff --git a/setup/db/db/schema-410to420.sql b/setup/db/db/schema-410to420.sql index 442a5446be5..fe66207e5e5 100644 --- a/setup/db/db/schema-410to420.sql +++ b/setup/db/db/schema-410to420.sql @@ -393,7 +393,16 @@ CREATE TABLE `cloud`.`vm_snapshots` ( ALTER TABLE `cloud`.`hypervisor_capabilities` ADD COLUMN `vm_snapshot_enabled` tinyint(1) DEFAULT 0 NOT NULL COMMENT 'Whether VM snapshot is supported by hypervisor'; UPDATE `cloud`.`hypervisor_capabilities` SET `vm_snapshot_enabled`=1 WHERE `hypervisor_type` in ('VMware', 'XenServer'); - +CREATE TABLE `cloud`.`service_offering_details` ( + `id` bigint unsigned NOT NULL auto_increment, + `service_offering_id` bigint unsigned NOT NULL COMMENT 'service offering id', + `name` varchar(255) NOT NULL, + `value` varchar(255) NOT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `fk_service_offering_details__service_offering_id` FOREIGN KEY (`service_offering_id`) REFERENCES `service_offering`(`id`) ON DELETE CASCADE, + CONSTRAINT UNIQUE KEY `uk_service_offering_id_name` (`service_offering_id`, `name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + DROP VIEW IF EXISTS `cloud`.`user_vm_view`; CREATE VIEW `cloud`.`user_vm_view` AS select diff --git a/test/integration/component/test_implicit_planner.py b/test/integration/component/test_implicit_planner.py new file mode 100644 index 00000000000..ffcd248b462 --- /dev/null +++ b/test/integration/component/test_implicit_planner.py @@ -0,0 +1,232 @@ +# 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. +""" P1 tests for Storage motion +""" +#Import Local Modules +import marvin +from marvin.cloudstackTestCase import * +from marvin.cloudstackAPI import * +from marvin.remoteSSHClient import remoteSSHClient +from marvin.integration.lib.utils import * +from marvin.integration.lib.base import * +from marvin.integration.lib.common import * +from nose.plugins.attrib import attr +#Import System modules +import time + +_multiprocess_shared_ = True +class Services: + """Test VM Life Cycle Services + """ + + def __init__(self): + self.services = { + "disk_offering":{ + "displaytext": "Small", + "name": "Small", + "disksize": 1 + }, + "account": { + "email": "test@test.com", + "firstname": "Test", + "lastname": "User", + "username": "test", + # Random characters are appended in create account to + # ensure unique username generated each time + "password": "password", + }, + "small": + # Create a small virtual machine instance with disk offering + { + "displayname": "testserver", + "username": "root", # VM creds for SSH + "password": "password", + "ssh_port": 22, + "hypervisor": 'XenServer', + "privateport": 22, + "publicport": 22, + "protocol": 'TCP', + }, + "service_offerings": + { + "implicitplanner": + { + # Small service offering ID to for change VM + # service offering from medium to small + "name": "Implicit Strict", + "displaytext": "Implicit Strict", + "cpunumber": 1, + "cpuspeed": 500, + "memory": 512, + "deploymentplanner": "ImplicitDedicationPlanner" + } + }, + "template": { + "displaytext": "Cent OS Template", + "name": "Cent OS Template", + "passwordenabled": True, + }, + "diskdevice": '/dev/xvdd', + # Disk device where ISO is attached to instance + "mount_dir": "/mnt/tmp", + "sleep": 60, + "timeout": 10, + #Migrate VM to hostid + "ostype": 'CentOS 5.3 (64-bit)', + # CentOS 5.3 (64-bit) + } + +class TestImplicitPlanner(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + cls.api_client = super(TestImplicitPlanner, cls).getClsTestClient().getApiClient() + cls.services = Services().services + + # Get Zone, Domain and templates + domain = get_domain(cls.api_client, cls.services) + cls.zone = get_zone(cls.api_client, cls.services) + cls.services['mode'] = cls.zone.networktype + + template = get_template( + cls.api_client, + cls.zone.id, + cls.services["ostype"] + ) + # Set Zones and disk offerings + cls.services["small"]["zoneid"] = cls.zone.id + cls.services["small"]["template"] = template.id + + # Create VMs, NAT Rules etc + cls.account = Account.create( + cls.api_client, + cls.services["account"], + domainid=domain.id + ) + + cls.small_offering = ServiceOffering.create( + cls.api_client, + cls.services["service_offerings"]["implicitplanner"] + ) + + cls._cleanup = [ + cls.small_offering, + cls.account + ] + + @classmethod + def tearDownClass(cls): + cls.api_client = super(TestImplicitPlanner, cls).getClsTestClient().getApiClient() + cleanup_resources(cls.api_client, cls._cleanup) + return + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + + def tearDown(self): + #Clean up, terminate the created ISOs + cleanup_resources(self.apiclient, self.cleanup) + return + + # This test requires multi host and at least one host which is empty (no vms should + # be running on that host). It uses an implicit planner to deploy instances and the + # instances of a new account should go to an host that doesn't have vms of any other + # account. + @attr(tags = ["advanced", "basic", "multihosts", "implicitplanner"]) + def test_01_deploy_vm_with_implicit_planner(self): + """Test implicit planner is placing vms of an account on implicitly dedicated hosts. + """ + # Validate the following + # 1. Deploy a vm using implicit planner. It should go on to a + # host that is empty (not running vms of any other account) + # 2. Deploy another vm it should get deployed on the same host. + + #create a virtual machine + virtual_machine_1 = VirtualMachine.create( + self.api_client, + self.services["small"], + accountid=self.account.name, + domainid=self.account.domainid, + serviceofferingid=self.small_offering.id, + mode=self.services["mode"] + ) + + list_vm_response_1 = list_virtual_machines( + self.apiclient, + id=virtual_machine_1.id + ) + self.assertEqual( + isinstance(list_vm_response_1, list), + True, + "Check list response returns a valid list" + ) + + self.assertNotEqual( + list_vm_response_1, + None, + "Check virtual machine is listVirtualMachines" + ) + + vm_response_1 = list_vm_response_1[0] + + self.assertEqual( + vm_response_1.id, + virtual_machine_1.id, + "Check virtual machine ID of VM" + ) + + virtual_machine_2 = VirtualMachine.create( + self.api_client, + self.services["small"], + accountid=self.account.name, + domainid=self.account.domainid, + serviceofferingid=self.small_offering.id, + mode=self.services["mode"] + ) + + list_vm_response_2 = list_virtual_machines( + self.apiclient, + id=virtual_machine_2.id + ) + self.assertEqual( + isinstance(list_vm_response_2, list), + True, + "Check list response returns a valid list" + ) + + self.assertNotEqual( + list_vm_response_2, + None, + "Check virtual machine is listVirtualMachines" + ) + + vm_response_2 = list_vm_response_2[0] + + self.assertEqual( + vm_response_2.id, + virtual_machine_2.id, + "Check virtual machine ID of VM" + ) + + self.assertEqual( + vm_response_1.hostid, + vm_response_2.hostid, + "Check both vms have the same host id" + ) + return \ No newline at end of file diff --git a/tools/marvin/marvin/integration/lib/base.py b/tools/marvin/marvin/integration/lib/base.py index ecdc8412fdb..a811f144980 100755 --- a/tools/marvin/marvin/integration/lib/base.py +++ b/tools/marvin/marvin/integration/lib/base.py @@ -1268,6 +1268,10 @@ class ServiceOffering: if "tags" in services: cmd.tags = services["tags"] + + if "deploymentplanner" in services: + cmd.deploymentplanner = services["deploymentplanner"] + # Service Offering private to that domain if domainid: cmd.domainid = domainid From 046580fcf117aadf77179011ecfb5dfffdcca65f Mon Sep 17 00:00:00 2001 From: Likitha Shetty Date: Fri, 17 May 2013 13:12:36 +0530 Subject: [PATCH 002/108] CLOUDSTACK-2552. Modify AWSAPI to decrypt db values using the decrypted database_key and not management_server_key --- .../bridge/persist/dao/CloudStackUserDaoImpl.java | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/awsapi/src/com/cloud/bridge/persist/dao/CloudStackUserDaoImpl.java b/awsapi/src/com/cloud/bridge/persist/dao/CloudStackUserDaoImpl.java index f108a20e5b4..5aac3960d02 100644 --- a/awsapi/src/com/cloud/bridge/persist/dao/CloudStackUserDaoImpl.java +++ b/awsapi/src/com/cloud/bridge/persist/dao/CloudStackUserDaoImpl.java @@ -19,15 +19,14 @@ package com.cloud.bridge.persist.dao; import javax.ejb.Local; import org.apache.log4j.Logger; -import org.jasypt.encryption.pbe.StandardPBEStringEncryptor; import org.springframework.stereotype.Component; import com.cloud.bridge.model.CloudStackUserVO; -import com.cloud.bridge.util.EncryptionSecretKeyCheckerUtil; import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.Transaction; +import com.cloud.utils.crypt.DBEncryptionUtil; @Component @Local(value={CloudStackUserDao.class}) @@ -51,13 +50,8 @@ public class CloudStackUserDaoImpl extends GenericDaoBase Date: Wed, 15 May 2013 17:50:43 +0530 Subject: [PATCH 003/108] CLOUDSTACK-2513: VPN tests refer to invalid connection.user in cloudConnection cloudConnection object should always have "user" and "passwd" attributes. And they are "None" while creating userAPIClient. As we already have "user" and "password" for mgmt server. Signed-off-by: Prasanna Santhanam --- tools/marvin/marvin/cloudstackConnection.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tools/marvin/marvin/cloudstackConnection.py b/tools/marvin/marvin/cloudstackConnection.py index b5ff5bf7b3f..e3977dcf7d4 100644 --- a/tools/marvin/marvin/cloudstackConnection.py +++ b/tools/marvin/marvin/cloudstackConnection.py @@ -43,10 +43,8 @@ class cloudConnection(object): self.securityKey = securityKey self.mgtSvr = mgtSvr self.port = port - if user: - self.user = user - if passwd: - self.passwd = passwd + self.user = user + self.passwd = passwd self.logging = logging self.path = path self.retries = 5 From 55d304a5bbca27994c01c0f7c63b6168d2a90f60 Mon Sep 17 00:00:00 2001 From: SrikanteswaraRao Talluri Date: Tue, 14 May 2013 20:06:47 +0530 Subject: [PATCH 004/108] CLOUDSTACK-2478: Fix test_volumes.py script for BVT failures removed storage type in compute offering and disk offering Signed-off-by: Prasanna Santhanam --- test/integration/smoke/test_volumes.py | 84 ++++++++++++++++---------- 1 file changed, 52 insertions(+), 32 deletions(-) diff --git a/test/integration/smoke/test_volumes.py b/test/integration/smoke/test_volumes.py index 4bf8203e74c..89b013a516f 100644 --- a/test/integration/smoke/test_volumes.py +++ b/test/integration/smoke/test_volumes.py @@ -53,19 +53,17 @@ class Services: "displaytext": "Tiny Instance", "cpunumber": 1, "cpuspeed": 100, # in MHz - "memory": 128, # In MBs - "storagetype": "local" + "memory": 260 # In MBs + }, "disk_offering": { "displaytext": "Small", "name": "Small", - "storagetype": "local", "disksize": 1 }, 'resized_disk_offering': { "displaytext": "Resized", "name": "Resized", - "storagetype": "local", "disksize": 3 }, "volume_offerings": { @@ -152,7 +150,7 @@ class TestCreateVolume(cloudstackTestCase): self.dbclient = self.testClient.getDbConnection() self.cleanup = [] - @attr(tags = ["advanced", "advancedns", "smoke"]) + @attr(tags = ["advanced", "advancedns", "smoke", "basic"]) def test_01_create_volume(self): """Test Volume creation for all Disk Offerings (incl. custom) """ @@ -346,8 +344,9 @@ class TestVolumes(cloudstackTestCase): cls.custom_resized_disk_offering, cls.service_offering, cls.disk_offering, + cls.volume, cls.account - ] + ] @classmethod def tearDownClass(cls): @@ -359,14 +358,17 @@ class TestVolumes(cloudstackTestCase): def setUp(self): self.apiClient = self.testClient.getApiClient() self.dbclient = self.testClient.getDbConnection() + self.attached = False self.cleanup = [] def tearDown(self): #Clean up, terminate the created volumes + if self.attached: + self.virtual_machine.detach_volume(self.apiClient, self.volume) cleanup_resources(self.apiClient, self.cleanup) return - @attr(tags = ["advanced", "advancedns", "smoke"]) + @attr(tags = ["advanced", "advancedns", "smoke", "basic"]) def test_02_attach_volume(self): """Attach a created Volume to a Running VM """ @@ -381,7 +383,7 @@ class TestVolumes(cloudstackTestCase): self.virtual_machine.id )) self.virtual_machine.attach_volume(self.apiClient, self.volume) - + self.attached = True list_volume_response = list_volumes( self.apiClient, id=self.volume.id @@ -412,7 +414,7 @@ class TestVolumes(cloudstackTestCase): (self.virtual_machine.ipaddress, e)) return - @attr(tags = ["advanced", "advancedns", "smoke"]) + @attr(tags = ["advanced", "advancedns", "smoke", "basic"]) def test_03_download_attached_volume(self): """Download a Volume attached to a VM """ @@ -423,6 +425,8 @@ class TestVolumes(cloudstackTestCase): self.debug("Extract attached Volume ID: %s" % self.volume.id) + self.virtual_machine.attach_volume(self.apiClient, self.volume) + self.attached = True cmd = extractVolume.extractVolumeCmd() cmd.id = self.volume.id cmd.mode = "HTTP_DOWNLOAD" @@ -432,7 +436,7 @@ class TestVolumes(cloudstackTestCase): with self.assertRaises(Exception): self.apiClient.extractVolume(cmd) - @attr(tags = ["advanced", "advancedns", "smoke"]) + @attr(tags = ["advanced", "advancedns", "smoke", "basic"]) def test_04_delete_attached_volume(self): """Delete a Volume attached to a VM """ @@ -444,19 +448,16 @@ class TestVolumes(cloudstackTestCase): self.debug("Trying to delete attached Volume ID: %s" % self.volume.id) - + self.virtual_machine.attach_volume(self.apiClient, self.volume) + self.attached = True cmd = deleteVolume.deleteVolumeCmd() cmd.id = self.volume.id #Proper exception should be raised; deleting attach VM is not allowed #with self.assertRaises(Exception): - result = self.apiClient.deleteVolume(cmd) - self.assertEqual( - result, - None, - "Check for delete download error while volume is attached" - ) + with self.assertRaises(Exception): + self.apiClient.deleteVolume(cmd) - @attr(tags = ["advanced", "advancedns", "smoke"]) + @attr(tags = ["advanced", "advancedns", "smoke", "basic"]) def test_05_detach_volume(self): """Detach a Volume attached to a VM """ @@ -470,8 +471,9 @@ class TestVolumes(cloudstackTestCase): self.volume.id, self.virtual_machine.id )) - + self.virtual_machine.attach_volume(self.apiClient, self.volume) self.virtual_machine.detach_volume(self.apiClient, self.volume) + self.attached = False #Sleep to ensure the current state will reflected in other calls time.sleep(self.services["sleep"]) list_volume_response = list_volumes( @@ -497,7 +499,7 @@ class TestVolumes(cloudstackTestCase): ) return - @attr(tags = ["advanced", "advancedns", "smoke"]) + @attr(tags = ["advanced", "advancedns", "smoke", "basic"]) def test_06_download_detached_volume(self): """Download a Volume unattached to an VM """ @@ -506,6 +508,10 @@ class TestVolumes(cloudstackTestCase): self.debug("Extract detached Volume ID: %s" % self.volume.id) + self.virtual_machine.attach_volume(self.apiClient, self.volume) + self.virtual_machine.detach_volume(self.apiClient, self.volume) + self.attached = False + cmd = extractVolume.extractVolumeCmd() cmd.id = self.volume.id cmd.mode = "HTTP_DOWNLOAD" @@ -528,7 +534,7 @@ class TestVolumes(cloudstackTestCase): % (extract_vol.url, self.volume.id) ) - @attr(tags = ["advanced", "advancedns", "smoke"]) + @attr(tags = ["advanced", "advancedns", "smoke", "basic"]) 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. @@ -543,7 +549,7 @@ class TestVolumes(cloudstackTestCase): response = self.apiClient.resizeVolume(cmd) except Exception as ex: #print str(ex) - if "HTTP Error 431:" in str(ex): + if "invalid" in str(ex): success = True self.assertEqual( success, @@ -557,7 +563,7 @@ class TestVolumes(cloudstackTestCase): try: response = self.apiClient.resizeVolume(cmd) except Exception as ex: - if "HTTP Error 431:" in str(ex): + if "invalid" in str(ex): success = True self.assertEqual( success, @@ -576,6 +582,7 @@ class TestVolumes(cloudstackTestCase): ) #attach the volume self.virtual_machine.attach_volume(self.apiClient, self.volume) + self.attached = True #stop the vm if it is on xenserver if self.services['hypervisor'].lower() == "xenserver": self.virtual_machine.stop(self.apiClient) @@ -603,10 +610,11 @@ class TestVolumes(cloudstackTestCase): True, "Verify the volume did not resize" ) - self.virtual_machine.detach_volume(self.apiClient, self.volume) - self.cleanup.append(self.volume) + if self.services['hypervisor'].lower() == "xenserver": + self.virtual_machine.start(self.apiClient) - @attr(tags = ["advanced", "advancedns", "smoke"]) + + @attr(tags = ["advanced", "advancedns", "smoke", "basic"]) def test_08_resize_volume(self): """Resize a volume""" # Verify the size is the new size is what we wanted it to be. @@ -616,6 +624,8 @@ class TestVolumes(cloudstackTestCase): self.virtual_machine.id )) self.virtual_machine.attach_volume(self.apiClient, self.volume) + self.attached = True + if self.services['hypervisor'].lower() == "xenserver": self.virtual_machine.stop(self.apiClient) self.debug("Resize Volume ID: %s" % self.volume.id) @@ -635,7 +645,7 @@ class TestVolumes(cloudstackTestCase): type='DATADISK' ) for vol in list_volume_response: - if vol.id == self.volume.id and vol.size == 3221225472L: + if vol.id == self.volume.id and vol.size == 3221225472L and vol.state == 'Ready': success = True if success: break @@ -649,10 +659,10 @@ class TestVolumes(cloudstackTestCase): "Check if the volume resized appropriately" ) - self.virtual_machine.detach_volume(self.apiClient, self.volume) - self.cleanup.append(self.volume) + if self.services['hypervisor'].lower() == "xenserver": + self.virtual_machine.start(self.apiClient) - @attr(tags = ["advanced", "advancedns", "smoke"]) + @attr(tags = ["advanced", "advancedns", "smoke","basic"]) def test_09_delete_detached_volume(self): """Delete a Volume unattached to an VM """ @@ -665,13 +675,23 @@ class TestVolumes(cloudstackTestCase): self.debug("Delete Volume ID: %s" % self.volume.id) + self.volume_1 = Volume.create( + self.api_client, + self.services, + account=self.account.name, + domainid=self.account.domainid + ) + + self.virtual_machine.attach_volume(self.apiClient, self.volume_1) + self.virtual_machine.detach_volume(self.apiClient, self.volume_1) + cmd = deleteVolume.deleteVolumeCmd() - cmd.id = self.volume.id + cmd.id = self.volume_1.id self.apiClient.deleteVolume(cmd) list_volume_response = list_volumes( self.apiClient, - id=self.volume.id, + id=self.volume_1.id, type='DATADISK' ) self.assertEqual( From 2867c6a6df6fe552cd8cf1f8bb7447bf8084d859 Mon Sep 17 00:00:00 2001 From: SrikanteswaraRao Talluri Date: Thu, 16 May 2013 17:29:12 +0530 Subject: [PATCH 005/108] CLOUDSTACK-2542: Fix router test for basic zone Signed-off-by: Prasanna Santhanam --- test/integration/smoke/test_routers.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/test/integration/smoke/test_routers.py b/test/integration/smoke/test_routers.py index 9ec2e918c42..f6ca2790069 100644 --- a/test/integration/smoke/test_routers.py +++ b/test/integration/smoke/test_routers.py @@ -141,11 +141,17 @@ class TestRouterServices(cloudstackTestCase): # by checking status of dnsmasq process # Find router associated with user account - list_router_response = list_routers( - self.apiclient, - account=self.account.name, - domainid=self.account.domainid - ) + if self.zone.networktype == "Basic": + list_router_response = list_routers( + self.apiclient, + listall="true" + ) + else: + list_router_response = list_routers( + self.apiclient, + account=self.account.name, + domainid=self.account.domainid + ) self.assertEqual( isinstance(list_router_response, list), True, From 28c5fbcb05750b91a54acfa84f0b59b03c7ce794 Mon Sep 17 00:00:00 2001 From: SrikanteswaraRao Talluri Date: Wed, 15 May 2013 00:22:32 +0530 Subject: [PATCH 006/108] CLOUDSTACK-2483: Fix base.py migrate volume method Signed-off-by: Prasanna Santhanam --- tools/marvin/marvin/integration/lib/base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/marvin/marvin/integration/lib/base.py b/tools/marvin/marvin/integration/lib/base.py index a811f144980..f3a96bd9bec 100755 --- a/tools/marvin/marvin/integration/lib/base.py +++ b/tools/marvin/marvin/integration/lib/base.py @@ -685,6 +685,7 @@ class Volume: timeout = timeout - 1 return + @classmethod def migrate(cls, apiclient, **kwargs): """Migrate a volume""" cmd = migrateVolume.migrateVolumeCmd() From 107f4924757d6d59bff4256a56df3c8e81763818 Mon Sep 17 00:00:00 2001 From: Rajesh Battala Date: Thu, 9 May 2013 18:02:41 +0530 Subject: [PATCH 007/108] Fixed CLOUDSTACK-2081 Volume which is added thru upload volume is failed to attach to the instance saying Volume state must be in Allocated, Ready or in Uploaded state( Though uploaded Volume state is uploaded) --- .../apache/cloudstack/storage/volume/VolumeServiceImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/storage/volume/src/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java b/engine/storage/volume/src/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java index 26253544e77..7fdf6bb18d3 100644 --- a/engine/storage/volume/src/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java +++ b/engine/storage/volume/src/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java @@ -748,11 +748,11 @@ public class VolumeServiceImpl implements VolumeService { protected Void registerVolumeCallback(AsyncCallbackDispatcher callback, CreateVolumeContext context) { CreateCmdResult result = callback.getResult(); VolumeObject vo = (VolumeObject)context.volume; - /*if (result.isFailed()) { + if (result.isFailed()) { vo.stateTransit(Volume.Event.OperationFailed); } else { vo.stateTransit(Volume.Event.OperationSucceeded); - }*/ + } VolumeApiResult res = new VolumeApiResult(vo); context.future.complete(res); return null; From e520ff456801e9a7b57fe56ee78f4f649675c082 Mon Sep 17 00:00:00 2001 From: Devdeep Singh Date: Fri, 17 May 2013 17:01:17 +0530 Subject: [PATCH 008/108] CLOUDSTACK-2122. Virtual machine id should be a required parameter for findHostsForMigration api. Fixing it. --- .../api/command/admin/host/FindHostsForMigrationCmd.java | 2 +- server/src/com/cloud/server/ManagementServerImpl.java | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/api/src/org/apache/cloudstack/api/command/admin/host/FindHostsForMigrationCmd.java b/api/src/org/apache/cloudstack/api/command/admin/host/FindHostsForMigrationCmd.java index e6e45cc7246..b2d77b85dd2 100644 --- a/api/src/org/apache/cloudstack/api/command/admin/host/FindHostsForMigrationCmd.java +++ b/api/src/org/apache/cloudstack/api/command/admin/host/FindHostsForMigrationCmd.java @@ -45,7 +45,7 @@ public class FindHostsForMigrationCmd extends BaseListCmd { ///////////////////////////////////////////////////// @Parameter(name=ApiConstants.VIRTUAL_MACHINE_ID, type=CommandType.UUID, entityType = UserVmResponse.class, - required=false, description="find hosts to which this VM can be migrated and flag the hosts with enough " + + required=true, description="find hosts to which this VM can be migrated and flag the hosts with enough " + "CPU/RAM to host the VM") private Long virtualMachineId; diff --git a/server/src/com/cloud/server/ManagementServerImpl.java b/server/src/com/cloud/server/ManagementServerImpl.java index f74b7ad964c..06c0f964ccb 100755 --- a/server/src/com/cloud/server/ManagementServerImpl.java +++ b/server/src/com/cloud/server/ManagementServerImpl.java @@ -1086,17 +1086,16 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe VMInstanceVO vm = _vmInstanceDao.findById(vmId); if (vm == null) { - InvalidParameterValueException ex = new InvalidParameterValueException("Unable to find the VM with specified id"); - ex.addProxyObject(vm, vmId, "vmId"); + InvalidParameterValueException ex = new InvalidParameterValueException("Unable to find the VM with given id"); throw ex; } if (vm.getState() != State.Running) { if (s_logger.isDebugEnabled()) { - s_logger.debug("VM is not Running, unable to migrate the vm" + vm); + s_logger.debug("VM is not running, cannot migrate the vm" + vm); } - InvalidParameterValueException ex = new InvalidParameterValueException("VM is not Running, unable to" + - " migrate the vm with specified id"); + InvalidParameterValueException ex = new InvalidParameterValueException("VM is not Running, cannot " + + "migrate the vm with specified id"); ex.addProxyObject(vm, vmId, "vmId"); throw ex; } From 4eb310e926771c30515034bb056f71f52afe1a19 Mon Sep 17 00:00:00 2001 From: Nitin Mehta Date: Fri, 17 May 2013 17:38:21 +0530 Subject: [PATCH 009/108] iCLOUDSTACK-2321 Fix the response of scaleVMCmd Add Scale System vm command --- api/src/com/cloud/event/EventTypes.java | 3 + .../com/cloud/server/ManagementService.java | 12 +- api/src/com/cloud/vm/UserVmService.java | 2 +- .../admin/systemvm/ScaleSystemVMCmd.java | 131 ++++++++++++++++++ .../api/command/user/vm/ScaleVMCmd.java | 23 ++- .../api/command/test/ScaleVMCmdTest.java | 32 ++++- client/tomcatconf/commands.properties.in | 1 + .../com/cloud/server/ManagementServer.java | 6 + .../cloud/server/ManagementServerImpl.java | 47 ++++--- server/src/com/cloud/vm/UserVmManager.java | 6 +- .../src/com/cloud/vm/UserVmManagerImpl.java | 17 ++- .../com/cloud/vm/MockUserVmManagerImpl.java | 9 +- 12 files changed, 239 insertions(+), 50 deletions(-) create mode 100644 api/src/org/apache/cloudstack/api/command/admin/systemvm/ScaleSystemVMCmd.java diff --git a/api/src/com/cloud/event/EventTypes.java b/api/src/com/cloud/event/EventTypes.java index ee7f5b7d89f..9c83f13ea2a 100755 --- a/api/src/com/cloud/event/EventTypes.java +++ b/api/src/com/cloud/event/EventTypes.java @@ -92,6 +92,8 @@ public class EventTypes { public static final String EVENT_PROXY_STOP = "PROXY.STOP"; public static final String EVENT_PROXY_REBOOT = "PROXY.REBOOT"; public static final String EVENT_PROXY_HA = "PROXY.HA"; + public static final String EVENT_PROXY_SCALE = "PROXY.SCALE"; + // VNC Console Events public static final String EVENT_VNC_CONNECT = "VNC.CONNECT"; @@ -213,6 +215,7 @@ public class EventTypes { public static final String EVENT_SSVM_STOP = "SSVM.STOP"; public static final String EVENT_SSVM_REBOOT = "SSVM.REBOOT"; public static final String EVENT_SSVM_HA = "SSVM.HA"; + public static final String EVENT_SSVM_SCALE = "SSVM.SCALE"; // Service Offerings public static final String EVENT_SERVICE_OFFERING_CREATE = "SERVICE.OFFERING.CREATE"; diff --git a/api/src/com/cloud/server/ManagementService.java b/api/src/com/cloud/server/ManagementService.java index 59b83c9bbce..24d33d5a3f8 100755 --- a/api/src/com/cloud/server/ManagementService.java +++ b/api/src/com/cloud/server/ManagementService.java @@ -22,6 +22,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import com.cloud.exception.*; import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.command.admin.cluster.ListClustersCmd; import org.apache.cloudstack.api.command.admin.config.ListCfgsByCmd; @@ -34,11 +35,7 @@ import org.apache.cloudstack.api.command.admin.resource.DeleteAlertsCmd; import org.apache.cloudstack.api.command.admin.resource.ListAlertsCmd; import org.apache.cloudstack.api.command.admin.resource.ListCapacityCmd; import org.apache.cloudstack.api.command.admin.resource.UploadCustomCertificateCmd; -import org.apache.cloudstack.api.command.admin.systemvm.DestroySystemVmCmd; -import org.apache.cloudstack.api.command.admin.systemvm.ListSystemVMsCmd; -import org.apache.cloudstack.api.command.admin.systemvm.RebootSystemVmCmd; -import org.apache.cloudstack.api.command.admin.systemvm.StopSystemVmCmd; -import org.apache.cloudstack.api.command.admin.systemvm.UpgradeSystemVMCmd; +import org.apache.cloudstack.api.command.admin.systemvm.*; import org.apache.cloudstack.api.command.admin.vlan.ListVlanIpRangesCmd; import org.apache.cloudstack.api.command.user.address.ListPublicIpAddressesCmd; import org.apache.cloudstack.api.command.user.config.ListCapabilitiesCmd; @@ -64,10 +61,6 @@ import com.cloud.configuration.Configuration; import com.cloud.dc.Pod; import com.cloud.dc.Vlan; import com.cloud.domain.Domain; -import com.cloud.exception.ConcurrentOperationException; -import com.cloud.exception.InternalErrorException; -import com.cloud.exception.PermissionDeniedException; -import com.cloud.exception.ResourceUnavailableException; import com.cloud.host.Host; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.hypervisor.HypervisorCapabilities; @@ -422,4 +415,5 @@ public interface ManagementService { List listDeploymentPlanners(); + VirtualMachine upgradeSystemVM(ScaleSystemVMCmd cmd) throws ResourceUnavailableException, ManagementServerException, VirtualMachineMigrationException, ConcurrentOperationException; } diff --git a/api/src/com/cloud/vm/UserVmService.java b/api/src/com/cloud/vm/UserVmService.java index 0a0660ad493..7d459b99a9e 100755 --- a/api/src/com/cloud/vm/UserVmService.java +++ b/api/src/com/cloud/vm/UserVmService.java @@ -461,6 +461,6 @@ public interface UserVmService { UserVm restoreVM(RestoreVMCmd cmd) throws InsufficientCapacityException, ResourceUnavailableException; - boolean upgradeVirtualMachine(ScaleVMCmd scaleVMCmd) throws ResourceUnavailableException, ConcurrentOperationException, ManagementServerException, VirtualMachineMigrationException; + UserVm upgradeVirtualMachine(ScaleVMCmd cmd) throws ResourceUnavailableException, ConcurrentOperationException, ManagementServerException, VirtualMachineMigrationException; } diff --git a/api/src/org/apache/cloudstack/api/command/admin/systemvm/ScaleSystemVMCmd.java b/api/src/org/apache/cloudstack/api/command/admin/systemvm/ScaleSystemVMCmd.java new file mode 100644 index 00000000000..a077e246a46 --- /dev/null +++ b/api/src/org/apache/cloudstack/api/command/admin/systemvm/ScaleSystemVMCmd.java @@ -0,0 +1,131 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.admin.systemvm; + +import com.cloud.event.EventTypes; +import com.cloud.exception.*; +import org.apache.cloudstack.api.*; +import org.apache.cloudstack.api.command.user.vm.UpgradeVMCmd; +import org.apache.cloudstack.api.response.ServiceOfferingResponse; +import org.apache.cloudstack.api.response.SystemVmResponse; +import org.apache.log4j.Logger; + +import com.cloud.offering.ServiceOffering; +import com.cloud.user.Account; +import com.cloud.user.UserContext; +import com.cloud.vm.VirtualMachine; + +@APICommand(name = "scaleSystemVm", responseObject=SystemVmResponse.class, description="Scale the service offering for a system vm (console proxy or secondary storage). " + + "The system vm must be in a \"Stopped\" state for " + + "this command to take effect.") +public class ScaleSystemVMCmd extends BaseAsyncCmd { + public static final Logger s_logger = Logger.getLogger(UpgradeVMCmd.class.getName()); + private static final String s_name = "changeserviceforsystemvmresponse"; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name=ApiConstants.ID, type=CommandType.UUID, entityType=SystemVmResponse.class, + required=true, description="The ID of the system vm") + private Long id; + + @Parameter(name=ApiConstants.SERVICE_OFFERING_ID, type=CommandType.UUID, entityType=ServiceOfferingResponse.class, + required=true, description="the service offering ID to apply to the system vm") + private Long serviceOfferingId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + public Long getServiceOfferingId() { + return serviceOfferingId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + Account account = UserContext.current().getCaller(); + if (account != null) { + return account.getId(); + } + + return Account.ACCOUNT_ID_SYSTEM; // no account info given, parent this command to SYSTEM so ERROR events are tracked + } + + @Override + public void execute(){ + UserContext.current().setEventDetails("SystemVm Id: "+getId()); + + ServiceOffering serviceOffering = _configService.getServiceOffering(serviceOfferingId); + if (serviceOffering == null) { + throw new InvalidParameterValueException("Unable to find service offering: " + serviceOfferingId); + } + + VirtualMachine result = null; + try { + result = _mgr.upgradeSystemVM(this); + } catch (ResourceUnavailableException ex) { + s_logger.warn("Exception: ", ex); + throw new ServerApiException(ApiErrorCode.RESOURCE_UNAVAILABLE_ERROR, ex.getMessage()); + } catch (ConcurrentOperationException ex) { + s_logger.warn("Exception: ", ex); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage()); + } catch (ManagementServerException ex) { + s_logger.warn("Exception: ", ex); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage()); + } catch (VirtualMachineMigrationException ex) { + s_logger.warn("Exception: ", ex); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage()); + } + if (result != null) { + SystemVmResponse response = _responseGenerator.createSystemVmResponse(result); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to scale system vm"); + } + } + + @Override + public String getEventType() { + VirtualMachine.Type type = _mgr.findSystemVMTypeById(getId()); + if(type == VirtualMachine.Type.ConsoleProxy){ + return EventTypes.EVENT_PROXY_SCALE; + } + else{ + return EventTypes.EVENT_SSVM_SCALE; + } + } + + @Override + public String getEventDescription() { + return "scaling system vm: " + getId() + " to service offering: " + getServiceOfferingId(); + } +} diff --git a/api/src/org/apache/cloudstack/api/command/user/vm/ScaleVMCmd.java b/api/src/org/apache/cloudstack/api/command/user/vm/ScaleVMCmd.java index 4f2ac750ce5..758d9c1667b 100644 --- a/api/src/org/apache/cloudstack/api/command/user/vm/ScaleVMCmd.java +++ b/api/src/org/apache/cloudstack/api/command/user/vm/ScaleVMCmd.java @@ -16,6 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.user.vm; +import com.cloud.event.EventTypes; import com.cloud.exception.*; import com.cloud.user.Account; import com.cloud.user.UserContext; @@ -26,9 +27,11 @@ import org.apache.cloudstack.api.response.SuccessResponse; import org.apache.cloudstack.api.response.UserVmResponse; import org.apache.log4j.Logger; +import java.util.List; + @APICommand(name = "scaleVirtualMachine", description="Scales the virtual machine to a new service offering.", responseObject=SuccessResponse.class) -public class ScaleVMCmd extends BaseCmd { +public class ScaleVMCmd extends BaseAsyncCmd { public static final Logger s_logger = Logger.getLogger(ScaleVMCmd.class.getName()); private static final String s_name = "scalevirtualmachineresponse"; @@ -84,7 +87,7 @@ public class ScaleVMCmd extends BaseCmd { @Override public void execute(){ //UserContext.current().setEventDetails("Vm Id: "+getId()); - boolean result; + UserVm result; try { result = _userVmService.upgradeVirtualMachine(this); } catch (ResourceUnavailableException ex) { @@ -100,11 +103,23 @@ public class ScaleVMCmd extends BaseCmd { s_logger.warn("Exception: ", ex); throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage()); } - if (result){ - SuccessResponse response = new SuccessResponse(getCommandName()); + if (result != null){ + List responseList = _responseGenerator.createUserVmResponse("virtualmachine", result); + UserVmResponse response = responseList.get(0); + response.setResponseName(getCommandName()); this.setResponseObject(response); } else { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to scale vm"); } } + + @Override + public String getEventType() { + return EventTypes.EVENT_VM_SCALE; + } + + @Override + public String getEventDescription() { + return "scaling volume: " + getId() + " to service offering: " + getServiceOfferingId(); + } } \ No newline at end of file diff --git a/api/test/org/apache/cloudstack/api/command/test/ScaleVMCmdTest.java b/api/test/org/apache/cloudstack/api/command/test/ScaleVMCmdTest.java index 8a28290e04b..bb022986e2d 100644 --- a/api/test/org/apache/cloudstack/api/command/test/ScaleVMCmdTest.java +++ b/api/test/org/apache/cloudstack/api/command/test/ScaleVMCmdTest.java @@ -24,11 +24,18 @@ import org.apache.cloudstack.api.ResponseGenerator; import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.command.user.vm.ScaleVMCmd; +import org.apache.cloudstack.api.response.SwiftResponse; +import org.apache.cloudstack.api.response.UserVmResponse; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.mockito.Mockito; +import static org.mockito.Matchers.anyInt; + + +import java.util.LinkedList; +import java.util.List; public class ScaleVMCmdTest extends TestCase{ @@ -58,19 +65,34 @@ public class ScaleVMCmdTest extends TestCase{ public void testCreateSuccess() { UserVmService userVmService = Mockito.mock(UserVmService.class); + UserVm userVm = Mockito.mock(UserVm.class); + try { Mockito.when( userVmService.upgradeVirtualMachine(scaleVMCmd)) - .thenReturn(true); + .thenReturn(userVm); }catch (Exception e){ Assert.fail("Received exception when success expected " +e.getMessage()); } - scaleVMCmd._userVmService = userVmService; - responseGenerator = Mockito.mock(ResponseGenerator.class); - + ResponseGenerator responseGenerator = Mockito.mock(ResponseGenerator.class); scaleVMCmd._responseGenerator = responseGenerator; + + UserVmResponse userVmResponse = Mockito.mock(UserVmResponse.class); + //List list = Mockito.mock(UserVmResponse.class); + //list.add(userVmResponse); + //LinkedList mockedList = Mockito.mock(LinkedList.class); + //Mockito.when(mockedList.get(0)).thenReturn(userVmResponse); + + List list = new LinkedList(); + list.add(userVmResponse); + + Mockito.when(responseGenerator.createUserVmResponse("virtualmachine", userVm)).thenReturn( + list); + + scaleVMCmd._userVmService = userVmService; + scaleVMCmd.execute(); } @@ -83,7 +105,7 @@ public class ScaleVMCmdTest extends TestCase{ try { Mockito.when( userVmService.upgradeVirtualMachine(scaleVMCmd)) - .thenReturn(false); + .thenReturn(null); }catch (Exception e){ Assert.fail("Received exception when success expected " +e.getMessage()); } diff --git a/client/tomcatconf/commands.properties.in b/client/tomcatconf/commands.properties.in index 4cd9065b641..68a7511560b 100644 --- a/client/tomcatconf/commands.properties.in +++ b/client/tomcatconf/commands.properties.in @@ -205,6 +205,7 @@ destroySystemVm=1 listSystemVms=3 migrateSystemVm=1 changeServiceForSystemVm=1 +scaleSystemVm=1 #### configuration commands updateConfiguration=1 diff --git a/server/src/com/cloud/server/ManagementServer.java b/server/src/com/cloud/server/ManagementServer.java index 240464e4938..969bc6557e1 100755 --- a/server/src/com/cloud/server/ManagementServer.java +++ b/server/src/com/cloud/server/ManagementServer.java @@ -19,6 +19,11 @@ package com.cloud.server; import java.util.Date; import java.util.List; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.ManagementServerException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.exception.VirtualMachineMigrationException; +import org.apache.cloudstack.api.command.admin.systemvm.ScaleSystemVMCmd; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import com.cloud.event.EventVO; @@ -100,4 +105,5 @@ public interface ManagementServer extends ManagementService, PluggableService { void resetEncryptionKeyIV(); public void enableAdminUser(String password); + } diff --git a/server/src/com/cloud/server/ManagementServerImpl.java b/server/src/com/cloud/server/ManagementServerImpl.java index 06c0f964ccb..8b3eea4a1f2 100755 --- a/server/src/com/cloud/server/ManagementServerImpl.java +++ b/server/src/com/cloud/server/ManagementServerImpl.java @@ -43,6 +43,8 @@ import javax.crypto.spec.SecretKeySpec; import javax.inject.Inject; import javax.naming.ConfigurationException; +import com.cloud.exception.*; +import com.cloud.vm.*; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.SecurityChecker.AccessType; import org.apache.cloudstack.affinity.AffinityGroupProcessor; @@ -151,13 +153,7 @@ import org.apache.cloudstack.api.command.admin.storage.PreparePrimaryStorageForM import org.apache.cloudstack.api.command.admin.storage.UpdateStoragePoolCmd; import org.apache.cloudstack.api.command.admin.swift.AddSwiftCmd; import org.apache.cloudstack.api.command.admin.swift.ListSwiftsCmd; -import org.apache.cloudstack.api.command.admin.systemvm.DestroySystemVmCmd; -import org.apache.cloudstack.api.command.admin.systemvm.ListSystemVMsCmd; -import org.apache.cloudstack.api.command.admin.systemvm.MigrateSystemVMCmd; -import org.apache.cloudstack.api.command.admin.systemvm.RebootSystemVmCmd; -import org.apache.cloudstack.api.command.admin.systemvm.StartSystemVMCmd; -import org.apache.cloudstack.api.command.admin.systemvm.StopSystemVmCmd; -import org.apache.cloudstack.api.command.admin.systemvm.UpgradeSystemVMCmd; +import org.apache.cloudstack.api.command.admin.systemvm.*; import org.apache.cloudstack.api.command.admin.template.PrepareTemplateCmd; import org.apache.cloudstack.api.command.admin.usage.AddTrafficMonitorCmd; import org.apache.cloudstack.api.command.admin.usage.AddTrafficTypeCmd; @@ -472,12 +468,6 @@ import com.cloud.event.ActionEventUtils; import com.cloud.event.EventTypes; import com.cloud.event.EventVO; import com.cloud.event.dao.EventDao; -import com.cloud.exception.ConcurrentOperationException; -import com.cloud.exception.InvalidParameterValueException; -import com.cloud.exception.OperationTimedoutException; -import com.cloud.exception.PermissionDeniedException; -import com.cloud.exception.ResourceUnavailableException; -import com.cloud.exception.StorageUnavailableException; import com.cloud.ha.HighAvailabilityManager; import com.cloud.host.DetailVO; import com.cloud.host.Host; @@ -571,17 +561,7 @@ import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.net.MacAddress; import com.cloud.utils.net.NetUtils; import com.cloud.utils.ssh.SSHKeysHelper; -import com.cloud.vm.ConsoleProxyVO; -import com.cloud.vm.DiskProfile; -import com.cloud.vm.InstanceGroupVO; -import com.cloud.vm.SecondaryStorageVmVO; -import com.cloud.vm.UserVmVO; -import com.cloud.vm.VMInstanceVO; -import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachine.State; -import com.cloud.vm.VirtualMachineManager; -import com.cloud.vm.VirtualMachineProfile; -import com.cloud.vm.VirtualMachineProfileImpl; import com.cloud.vm.dao.ConsoleProxyDao; import com.cloud.vm.dao.DomainRouterDao; import com.cloud.vm.dao.InstanceGroupDao; @@ -717,6 +697,8 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe @Inject ConfigurationServer _configServer; + @Inject + UserVmManager _userVmMgr; private final ScheduledExecutorService _eventExecutor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("EventChecker")); private final ScheduledExecutorService _alertExecutor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("AlertChecker")); @@ -2917,6 +2899,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe cmdList.add(ListAffinityGroupTypesCmd.class); cmdList.add(ListDeploymentPlannersCmd.class); cmdList.add(ReleaseHostReservationCmd.class); + cmdList.add(ScaleSystemVMCmd.class); cmdList.add(AddResourceDetailCmd.class); cmdList.add(RemoveResourceDetailCmd.class); cmdList.add(ListResourceDetailsCmd.class); @@ -4020,10 +4003,28 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe } } + @Override + public VirtualMachine upgradeSystemVM(ScaleSystemVMCmd cmd) throws ResourceUnavailableException, ManagementServerException, VirtualMachineMigrationException, ConcurrentOperationException { + + boolean result = _userVmMgr.upgradeVirtualMachine(cmd.getId(), cmd.getServiceOfferingId()); + if(result){ + VirtualMachine vm = _vmInstanceDao.findById(cmd.getId()); + return vm; + }else{ + return null; + } + } + + @Override public VirtualMachine upgradeSystemVM(UpgradeSystemVMCmd cmd) { Long systemVmId = cmd.getId(); Long serviceOfferingId = cmd.getServiceOfferingId(); + return upgradeStoppedSystemVm(systemVmId, serviceOfferingId); + + } + + private VirtualMachine upgradeStoppedSystemVm(Long systemVmId, Long serviceOfferingId){ Account caller = UserContext.current().getCaller(); VMInstanceVO systemVm = _vmInstanceDao.findByIdTypes(systemVmId, VirtualMachine.Type.ConsoleProxy, VirtualMachine.Type.SecondaryStorageVm); diff --git a/server/src/com/cloud/vm/UserVmManager.java b/server/src/com/cloud/vm/UserVmManager.java index cc1fffd780b..0f8e36804bb 100755 --- a/server/src/com/cloud/vm/UserVmManager.java +++ b/server/src/com/cloud/vm/UserVmManager.java @@ -22,9 +22,7 @@ import java.util.Map; import com.cloud.agent.api.VmStatsEntry; import com.cloud.api.query.vo.UserVmJoinVO; -import com.cloud.exception.ConcurrentOperationException; -import com.cloud.exception.InsufficientCapacityException; -import com.cloud.exception.ResourceUnavailableException; +import com.cloud.exception.*; import com.cloud.projects.Project.ListProjectResourcesCriteria; import com.cloud.server.Criteria; import com.cloud.user.Account; @@ -94,4 +92,6 @@ public interface UserVmManager extends VirtualMachineGuru, UserVmServi Pair> startVirtualMachine(long vmId, Long hostId, Map additionalParams) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException; + boolean upgradeVirtualMachine(Long id, Long serviceOfferingId) throws ResourceUnavailableException, ConcurrentOperationException, ManagementServerException, VirtualMachineMigrationException; + } diff --git a/server/src/com/cloud/vm/UserVmManagerImpl.java b/server/src/com/cloud/vm/UserVmManagerImpl.java index a3b731ab2a5..860daaf9a5e 100755 --- a/server/src/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/com/cloud/vm/UserVmManagerImpl.java @@ -1076,11 +1076,22 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use @Override @ActionEvent(eventType = EventTypes.EVENT_VM_SCALE, eventDescription = "scaling Vm") - public boolean - upgradeVirtualMachine(ScaleVMCmd cmd) throws InvalidParameterValueException, ResourceAllocationException { + public UserVm + upgradeVirtualMachine(ScaleVMCmd cmd) throws ResourceUnavailableException, ConcurrentOperationException, ManagementServerException, VirtualMachineMigrationException{ Long vmId = cmd.getId(); Long newServiceOfferingId = cmd.getServiceOfferingId(); + boolean result = upgradeVirtualMachine(vmId, newServiceOfferingId); + if(result){ + return _vmDao.findById(vmId); + }else{ + return null; + } + + } + + @Override + public boolean upgradeVirtualMachine(Long vmId, Long newServiceOfferingId) throws ResourceUnavailableException, ConcurrentOperationException, ManagementServerException, VirtualMachineMigrationException{ Account caller = UserContext.current().getCaller(); // Verify input parameters @@ -1147,9 +1158,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use } return success; - } + @Override public HashMap getVirtualMachineStatistics(long hostId, String hostName, List vmIds) throws CloudRuntimeException { diff --git a/server/test/com/cloud/vm/MockUserVmManagerImpl.java b/server/test/com/cloud/vm/MockUserVmManagerImpl.java index a88625a42fa..50a90f200c9 100644 --- a/server/test/com/cloud/vm/MockUserVmManagerImpl.java +++ b/server/test/com/cloud/vm/MockUserVmManagerImpl.java @@ -409,8 +409,8 @@ public class MockUserVmManagerImpl extends ManagerBase implements UserVmManager, } @Override - public boolean upgradeVirtualMachine(ScaleVMCmd scaleVMCmd) throws ResourceUnavailableException, ConcurrentOperationException, ManagementServerException, VirtualMachineMigrationException { - return false; //To change body of implemented methods use File | Settings | File Templates. + public UserVm upgradeVirtualMachine(ScaleVMCmd scaleVMCmd) throws ResourceUnavailableException, ConcurrentOperationException, ManagementServerException, VirtualMachineMigrationException { + return null; //To change body of implemented methods use File | Settings | File Templates. } @@ -420,6 +420,11 @@ public class MockUserVmManagerImpl extends ManagerBase implements UserVmManager, return null; } + @Override + public boolean upgradeVirtualMachine(Long id, Long serviceOfferingId) throws ResourceUnavailableException, ConcurrentOperationException, ManagementServerException, VirtualMachineMigrationException { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + @Override public void prepareStop(VirtualMachineProfile profile) { // TODO Auto-generated method stub From b3e9b2a5dc0439cad60058d693cba9d3c714af70 Mon Sep 17 00:00:00 2001 From: radhikap Date: Fri, 17 May 2013 18:57:59 +0530 Subject: [PATCH 010/108] portable IP --- docs/en-US/elastic-ip.xml | 161 ++++++++++++++++++++------------------ 1 file changed, 87 insertions(+), 74 deletions(-) diff --git a/docs/en-US/elastic-ip.xml b/docs/en-US/elastic-ip.xml index 8ecbd75be70..672fc5aef0c 100644 --- a/docs/en-US/elastic-ip.xml +++ b/docs/en-US/elastic-ip.xml @@ -26,78 +26,91 @@ choice from the EIP pool of your account. Later if required you can reassign the IP address to a different VM. This feature is extremely helpful during VM failure. Instead of replacing the VM which is down, the IP address can be reassigned to a new VM in your account. - Similar to the public IP address, Elastic IP addresses are mapped to their associated - private IP addresses by using StaticNAT. The EIP service is equipped with StaticNAT (1:1) - service in an EIP-enabled basic zone. The default network offering, - DefaultSharedNetscalerEIPandELBNetworkOffering, provides your network with EIP and ELB network - services if a NetScaler device is deployed in your zone. Consider the following illustration for - more details. - - - - - - eip-ns-basiczone.png: Elastic IP in a NetScaler-enabled Basic Zone. - - - In the illustration, a NetScaler appliance is the default entry or exit point for the - &PRODUCT; instances, and firewall is the default entry or exit point for the rest of the data - center. Netscaler provides LB services and staticNAT service to the guest networks. The guest - traffic in the pods and the Management Server are on different subnets / VLANs. The policy-based - routing in the data center core switch sends the public traffic through the NetScaler, whereas - the rest of the data center goes through the firewall. - The EIP work flow is as follows: - - - When a user VM is deployed, a public IP is automatically acquired from the pool of - public IPs configured in the zone. This IP is owned by the VM's account. - - - Each VM will have its own private IP. When the user VM starts, Static NAT is provisioned - on the NetScaler device by using the Inbound Network Address Translation (INAT) and Reverse - NAT (RNAT) rules between the public IP and the private IP. - - Inbound NAT (INAT) is a type of NAT supported by NetScaler, in which the destination - IP address is replaced in the packets from the public network, such as the Internet, with - the private IP address of a VM in the private network. Reverse NAT (RNAT) is a type of NAT - supported by NetScaler, in which the source IP address is replaced in the packets - generated by a VM in the private network with the public IP address. - - - - This default public IP will be released in two cases: - - - When the VM is stopped. When the VM starts, it again receives a new public IP, not - necessarily the same one allocated initially, from the pool of Public IPs. - - - The user acquires a public IP (Elastic IP). This public IP is associated with the - account, but will not be mapped to any private IP. However, the user can enable Static - NAT to associate this IP to the private IP of a VM in the account. The Static NAT rule - for the public IP can be disabled at any time. When Static NAT is disabled, a new public - IP is allocated from the pool, which is not necessarily be the same one allocated - initially. - - - - - For the deployments where public IPs are limited resources, you have the flexibility to - choose not to allocate a public IP by default. You can use the Associate Public IP option to - turn on or off the automatic public IP assignment in the EIP-enabled Basic zones. If you turn - off the automatic public IP assignment while creating a network offering, only a private IP is - assigned to a VM when the VM is deployed with that network offering. Later, the user can acquire - an IP for the VM and enable static NAT. - For more information on the Associate Public IP option, see . - For more information on the Associate Public IP option, see the - Administration Guide. - - The Associate Public IP feature is designed only for use with user VMs. The System VMs - continue to get both public IP and private by default, irrespective of the network offering - configuration. - - New deployments which use the default shared network offering with EIP and ELB services to - create a shared network in the Basic zone will continue allocating public IPs to each user - VM. +
+ Elastic IPs in Basic Zone + Similar to the public IP address, Elastic IP addresses are mapped to their associated + private IP addresses by using StaticNAT. The EIP service is equipped with StaticNAT (1:1) + service in an EIP-enabled basic zone. The default network offering, + DefaultSharedNetscalerEIPandELBNetworkOffering, provides your network with EIP and ELB network + services if a NetScaler device is deployed in your zone. Consider the following illustration + for more details. + + + + + + eip-ns-basiczone.png: Elastic IP in a NetScaler-enabled Basic Zone. + + + In the illustration, a NetScaler appliance is the default entry or exit point for the + &PRODUCT; instances, and firewall is the default entry or exit point for the rest of the data + center. Netscaler provides LB services and staticNAT service to the guest networks. The guest + traffic in the pods and the Management Server are on different subnets / VLANs. The + policy-based routing in the data center core switch sends the public traffic through the + NetScaler, whereas the rest of the data center goes through the firewall. + The EIP work flow is as follows: + + + When a user VM is deployed, a public IP is automatically acquired from the pool of + public IPs configured in the zone. This IP is owned by the VM's account. + + + Each VM will have its own private IP. When the user VM starts, Static NAT is + provisioned on the NetScaler device by using the Inbound Network Address Translation + (INAT) and Reverse NAT (RNAT) rules between the public IP and the private IP. + + Inbound NAT (INAT) is a type of NAT supported by NetScaler, in which the destination + IP address is replaced in the packets from the public network, such as the Internet, + with the private IP address of a VM in the private network. Reverse NAT (RNAT) is a type + of NAT supported by NetScaler, in which the source IP address is replaced in the packets + generated by a VM in the private network with the public IP address. + + + + This default public IP will be released in two cases: + + + When the VM is stopped. When the VM starts, it again receives a new public IP, not + necessarily the same one allocated initially, from the pool of Public IPs. + + + The user acquires a public IP (Elastic IP). This public IP is associated with the + account, but will not be mapped to any private IP. However, the user can enable Static + NAT to associate this IP to the private IP of a VM in the account. The Static NAT rule + for the public IP can be disabled at any time. When Static NAT is disabled, a new + public IP is allocated from the pool, which is not necessarily be the same one + allocated initially. + + + + + For the deployments where public IPs are limited resources, you have the flexibility to + choose not to allocate a public IP by default. You can use the Associate Public IP option to + turn on or off the automatic public IP assignment in the EIP-enabled Basic zones. If you turn + off the automatic public IP assignment while creating a network offering, only a private IP is + assigned to a VM when the VM is deployed with that network offering. Later, the user can + acquire an IP for the VM and enable static NAT. + For more information on the Associate Public IP option, see . + For more information on the Associate Public IP option, see the + Administration Guide. + + The Associate Public IP feature is designed only for use with user VMs. The System VMs + continue to get both public IP and private by default, irrespective of the network offering + configuration. + + New deployments which use the default shared network offering with EIP and ELB services to + create a shared network in the Basic zone will continue allocating public IPs to each user + VM. +
+
+ About Portable IP + Portable IPs in &PRODUCT; are nothing but elastic IPs that can be transferred across + geographically separated zones. As an administrator, you can provision a pool of portable IPs + at region level and are available for user consumption. The users can acquire portable IPs if + admin has provisioned portable public IPs at the region level they are part of. These IPs can + be use for any service within an advanced zone. You can also use portable IPs for EIP service + in basic zones. Additionally, a portable IP can be transferred from one network to another + network. +
From 5646f5e9772380cc91a8ccb9894c8ed05ffc891f Mon Sep 17 00:00:00 2001 From: Wido den Hollander Date: Fri, 17 May 2013 17:03:21 +0200 Subject: [PATCH 011/108] rbd: Allow RBD disks to be attached to a Instance --- .../hypervisor/kvm/resource/LibvirtComputingResource.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java index c3140d3921a..b31fb5dfbe5 100755 --- a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java +++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java @@ -3581,6 +3581,7 @@ ServerResource { List disks = null; Domain dm = null; DiskDef diskdef = null; + KVMStoragePool attachingPool = attachingDisk.getPool(); try { if (!attach) { dm = conn.domainLookupByUUID(UUID.nameUUIDFromBytes(vmName @@ -3605,7 +3606,12 @@ ServerResource { } } else { diskdef = new DiskDef(); - if (attachingDisk.getFormat() == PhysicalDiskFormat.QCOW2) { + if (attachingPool.getType() == StoragePoolType.RBD) { + diskdef.defNetworkBasedDisk(attachingDisk.getPath(), + attachingPool.getSourceHost(), attachingPool.getSourcePort(), + attachingPool.getAuthUserName(), attachingPool.getUuid(), devId, + DiskDef.diskBus.VIRTIO, diskProtocol.RBD); + } else if (attachingDisk.getFormat() == PhysicalDiskFormat.QCOW2) { diskdef.defFileBasedDisk(attachingDisk.getPath(), devId, DiskDef.diskBus.VIRTIO, DiskDef.diskFmtType.QCOW2); } else if (attachingDisk.getFormat() == PhysicalDiskFormat.RAW) { From 55c1e282e390e95a2639df28e1cfd743772ee001 Mon Sep 17 00:00:00 2001 From: Prachi Damle Date: Fri, 17 May 2013 11:39:20 -0700 Subject: [PATCH 012/108] CLOUDSTACK-2102: Anti-Affinity - Even after reserved capacity has been released for a Vm in "Stopped" state , we are not allowed to deploy new Vms as part of same anti-affinity group in the last_host_id where the stopped Vm was running. Changes: Do not add the last_host_id of a Stopped VM to avoid set, if the VM has been stopped for more that capacity.skip.counting.hours time limit --- .../affinity/HostAntiAffinityProcessor.java | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/plugins/affinity-group-processors/host-anti-affinity/src/org/apache/cloudstack/affinity/HostAntiAffinityProcessor.java b/plugins/affinity-group-processors/host-anti-affinity/src/org/apache/cloudstack/affinity/HostAntiAffinityProcessor.java index 4c2c7f1c131..6c3f57f1691 100644 --- a/plugins/affinity-group-processors/host-anti-affinity/src/org/apache/cloudstack/affinity/HostAntiAffinityProcessor.java +++ b/plugins/affinity-group-processors/host-anti-affinity/src/org/apache/cloudstack/affinity/HostAntiAffinityProcessor.java @@ -17,17 +17,24 @@ package org.apache.cloudstack.affinity; import java.util.List; +import java.util.Map; import javax.ejb.Local; import javax.inject.Inject; +import javax.naming.ConfigurationException; import org.apache.cloudstack.affinity.dao.AffinityGroupDao; import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; +import org.apache.cloudstack.framework.messagebus.MessageSubscriber; import org.apache.log4j.Logger; +import com.cloud.configuration.Config; +import com.cloud.configuration.dao.ConfigurationDao; import com.cloud.deploy.DeploymentPlan; import com.cloud.deploy.DeploymentPlanner.ExcludeList; import com.cloud.exception.AffinityConflictException; +import com.cloud.utils.DateUtil; +import com.cloud.utils.NumbersUtil; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachineProfile; @@ -46,7 +53,10 @@ public class HostAntiAffinityProcessor extends AffinityProcessorBase implements protected AffinityGroupDao _affinityGroupDao; @Inject protected AffinityGroupVMMapDao _affinityGroupVMMapDao; - + private int _vmCapacityReleaseInterval; + @Inject + protected ConfigurationDao _configDao; + @Override public void process(VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid) @@ -76,12 +86,14 @@ public class HostAntiAffinityProcessor extends AffinityProcessorBase implements } } else if (VirtualMachine.State.Stopped.equals(groupVM.getState()) && groupVM.getLastHostId() != null) { - avoid.addHost(groupVM.getLastHostId()); - if (s_logger.isDebugEnabled()) { - s_logger.debug("Added host " + groupVM.getLastHostId() + " to avoid set, since VM " - + groupVM.getId() + " is present on the host, in Stopped state"); + long secondsSinceLastUpdate = (DateUtil.currentGMTTime().getTime() - groupVM.getUpdateTime().getTime()) / 1000; + if (secondsSinceLastUpdate < _vmCapacityReleaseInterval) { + avoid.addHost(groupVM.getLastHostId()); + if (s_logger.isDebugEnabled()) { + s_logger.debug("Added host " + groupVM.getLastHostId() + " to avoid set, since VM " + + groupVM.getId() + " is present on the host, in Stopped state but has reserved capacity"); + } } - } } } @@ -89,5 +101,12 @@ public class HostAntiAffinityProcessor extends AffinityProcessorBase implements } } + + @Override + public boolean configure(final String name, final Map params) throws ConfigurationException { + super.configure(name, params); + _vmCapacityReleaseInterval = NumbersUtil.parseInt(_configDao.getValue(Config.CapacitySkipcountingHours.key()),3600); + return true; + } } From 31a67706e402fc73c592f871291223107ed0c21c Mon Sep 17 00:00:00 2001 From: Prachi Damle Date: Fri, 17 May 2013 14:36:23 -0700 Subject: [PATCH 013/108] CLOUDSTACK-2070: Anti-Affinity - When Vm deployment fails because of not being able to satisfy the anti-affinity rule , user should be provided with more informative message. Changes: - There is no good mechanism currently to figure out if the deployment failed due to affinity groups only - We can just hint the user that the deployment might have failed due to the affinity groups and ask to review the input --- .../InsufficientServerCapacityException.java | 11 +++++++ .../api/command/user/vm/DeployVMCmd.java | 11 +++++-- .../api/command/user/vm/StartVMCmd.java | 14 ++++++-- .../cloud/entity/api/VMEntityManagerImpl.java | 18 ++++++++++- .../cloud/vm/VirtualMachineManagerImpl.java | 32 +++++++++++++------ 5 files changed, 72 insertions(+), 14 deletions(-) diff --git a/api/src/com/cloud/exception/InsufficientServerCapacityException.java b/api/src/com/cloud/exception/InsufficientServerCapacityException.java index af34e579943..8f889fee4c5 100755 --- a/api/src/com/cloud/exception/InsufficientServerCapacityException.java +++ b/api/src/com/cloud/exception/InsufficientServerCapacityException.java @@ -27,6 +27,8 @@ public class InsufficientServerCapacityException extends InsufficientCapacityExc private static final long serialVersionUID = SerialVersionUID.InsufficientServerCapacityException; + private boolean affinityGroupsApplied = false; + public InsufficientServerCapacityException(String msg, Long clusterId) { this(msg, Cluster.class, clusterId); } @@ -34,4 +36,13 @@ public class InsufficientServerCapacityException extends InsufficientCapacityExc public InsufficientServerCapacityException(String msg, Class scope, Long id) { super(msg, scope, id); } + + public InsufficientServerCapacityException(String msg, Class scope, Long id, boolean affinityGroupsApplied) { + super(msg, scope, id); + this.affinityGroupsApplied = affinityGroupsApplied; + } + + public boolean isAffinityApplied() { + return affinityGroupsApplied; + } } diff --git a/api/src/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java b/api/src/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java index b5cf9f9c054..63198a420be 100755 --- a/api/src/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java +++ b/api/src/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java @@ -51,6 +51,7 @@ import com.cloud.dc.DataCenter.NetworkType; import com.cloud.event.EventTypes; import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.InsufficientServerCapacityException; import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; @@ -424,9 +425,15 @@ public class DeployVMCmd extends BaseAsyncCreateCmd { s_logger.warn("Exception: ", ex); throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage()); } catch (InsufficientCapacityException ex) { + StringBuilder message = new StringBuilder(ex.getMessage()); + if (ex instanceof InsufficientServerCapacityException) { + if(((InsufficientServerCapacityException)ex).isAffinityApplied()){ + message.append(", Please check the affinity groups provided, there may not be sufficient capacity to follow them"); + } + } s_logger.info(ex); - s_logger.info(ex.getMessage(), ex); - throw new ServerApiException(ApiErrorCode.INSUFFICIENT_CAPACITY_ERROR, ex.getMessage()); + s_logger.info(message.toString(), ex); + throw new ServerApiException(ApiErrorCode.INSUFFICIENT_CAPACITY_ERROR, message.toString()); } } else { result = _userVmService.getUserVm(getEntityId()); diff --git a/api/src/org/apache/cloudstack/api/command/user/vm/StartVMCmd.java b/api/src/org/apache/cloudstack/api/command/user/vm/StartVMCmd.java index 3012780cb81..09b34d4af3c 100644 --- a/api/src/org/apache/cloudstack/api/command/user/vm/StartVMCmd.java +++ b/api/src/org/apache/cloudstack/api/command/user/vm/StartVMCmd.java @@ -30,10 +30,10 @@ import com.cloud.async.AsyncJob; import com.cloud.event.EventTypes; import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.InsufficientServerCapacityException; import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.exception.StorageUnavailableException; -import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.user.Account; import com.cloud.user.UserContext; import com.cloud.uservm.UserVm; @@ -112,7 +112,7 @@ public class StartVMCmd extends BaseAsyncCmd { } @Override - public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { + public void execute() throws ResourceUnavailableException, ResourceAllocationException { try { UserContext.current().setEventDetails("Vm Id: " + getId()); @@ -135,6 +135,16 @@ public class StartVMCmd extends BaseAsyncCmd { } catch (ExecutionException ex) { s_logger.warn("Exception: ", ex); throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage()); + } catch (InsufficientCapacityException ex) { + StringBuilder message = new StringBuilder(ex.getMessage()); + if (ex instanceof InsufficientServerCapacityException) { + if (((InsufficientServerCapacityException) ex).isAffinityApplied()) { + message.append(", Please check the affinity groups provided, there may not be sufficient capacity to follow them"); + } + } + s_logger.info(ex); + s_logger.info(message.toString(), ex); + throw new ServerApiException(ApiErrorCode.INSUFFICIENT_CAPACITY_ERROR, message.toString()); } } diff --git a/engine/orchestration/src/org/apache/cloudstack/engine/cloud/entity/api/VMEntityManagerImpl.java b/engine/orchestration/src/org/apache/cloudstack/engine/cloud/entity/api/VMEntityManagerImpl.java index 25e742301cf..38ed7e63605 100755 --- a/engine/orchestration/src/org/apache/cloudstack/engine/cloud/entity/api/VMEntityManagerImpl.java +++ b/engine/orchestration/src/org/apache/cloudstack/engine/cloud/entity/api/VMEntityManagerImpl.java @@ -24,6 +24,7 @@ import java.util.UUID; import javax.inject.Inject; +import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; import org.apache.cloudstack.engine.cloud.entity.api.db.VMEntityVO; import org.apache.cloudstack.engine.cloud.entity.api.db.VMReservationVO; import org.apache.cloudstack.engine.cloud.entity.api.db.dao.VMEntityDao; @@ -60,6 +61,7 @@ import com.cloud.user.dao.AccountDao; import com.cloud.user.dao.UserDao; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachineManager; import com.cloud.vm.VirtualMachineProfile; import com.cloud.vm.VirtualMachineProfileImpl; @@ -111,6 +113,9 @@ public class VMEntityManagerImpl implements VMEntityManager { @Inject DeploymentPlanningManager _dpMgr; + @Inject + protected AffinityGroupVMMapDao _affinityGroupVMMapDao; + @Override public VMEntityVO loadVirtualMachine(String vmId) { // TODO Auto-generated method stub @@ -123,6 +128,16 @@ public class VMEntityManagerImpl implements VMEntityManager { } + protected boolean areAffinityGroupsAssociated(VirtualMachineProfile vmProfile) { + VirtualMachine vm = vmProfile.getVirtualMachine(); + long vmGroupCount = _affinityGroupVMMapDao.countAffinityGroupsForVm(vm.getId()); + + if (vmGroupCount > 0) { + return true; + } + return false; + } + @Override public String reserveVirtualMachine(VMEntityVO vmEntityVO, String plannerToUse, DeploymentPlan planToDeploy, ExcludeList exclude) throws InsufficientCapacityException, ResourceUnavailableException { @@ -194,7 +209,8 @@ public class VMEntityManagerImpl implements VMEntityManager { // call retry it. return UUID.randomUUID().toString(); }else{ - throw new InsufficientServerCapacityException("Unable to create a deployment for " + vmProfile, DataCenter.class, plan.getDataCenterId()); + throw new InsufficientServerCapacityException("Unable to create a deployment for " + vmProfile, + DataCenter.class, plan.getDataCenterId(), areAffinityGroupsAssociated(vmProfile)); } } diff --git a/server/src/com/cloud/vm/VirtualMachineManagerImpl.java b/server/src/com/cloud/vm/VirtualMachineManagerImpl.java index d153bb2cafe..cea05675f32 100755 --- a/server/src/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/server/src/com/cloud/vm/VirtualMachineManagerImpl.java @@ -36,6 +36,7 @@ import javax.ejb.Local; import javax.inject.Inject; import javax.naming.ConfigurationException; +import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory; @@ -253,6 +254,8 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac protected ResourceLimitService _resourceLimitMgr; @Inject protected RulesManager rulesMgr; + @Inject + protected AffinityGroupVMMapDao _affinityGroupVMMapDao; protected List _planners; public List getPlanners() { @@ -666,6 +669,16 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac } } + protected boolean areAffinityGroupsAssociated(VirtualMachineProfile vmProfile) { + VirtualMachine vm = vmProfile.getVirtualMachine(); + long vmGroupCount = _affinityGroupVMMapDao.countAffinityGroupsForVm(vm.getId()); + + if (vmGroupCount > 0) { + return true; + } + return false; + } + @Override public T advanceStart(T vm, Map params, User caller, Account account) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException { @@ -797,7 +810,8 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac reuseVolume = false; continue; } - throw new InsufficientServerCapacityException("Unable to create a deployment for " + vmProfile, DataCenter.class, plan.getDataCenterId()); + throw new InsufficientServerCapacityException("Unable to create a deployment for " + vmProfile, + DataCenter.class, plan.getDataCenterId(), areAffinityGroupsAssociated(vmProfile)); } if (dest != null) { @@ -1152,7 +1166,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac } vmGuru.prepareStop(profile); - + StopCommand stop = new StopCommand(vm); boolean stopped = false; StopAnswer answer = null; @@ -2802,7 +2816,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac VirtualMachineGuru vmGuru = getVmGuru(vmVO); s_logger.debug("Plugging nic for vm " + vm + " in network " + network); - + boolean result = false; try{ result = vmGuru.plugNic(network, nicTO, vmTO, context, dest); @@ -2812,17 +2826,17 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac // insert nic's Id into DB as resource_name UsageEventUtils.publishUsageEvent(EventTypes.EVENT_NETWORK_OFFERING_ASSIGN, vmVO.getAccountId(), vmVO.getDataCenterId(), vmVO.getId(), Long.toString(nic.getId()), nic.getNetworkId(), - null, isDefault, VirtualMachine.class.getName(), vmVO.getUuid()); + null, isDefault, VirtualMachine.class.getName(), vmVO.getUuid()); return nic; } else { s_logger.warn("Failed to plug nic to the vm " + vm + " in network " + network); return null; - } + } }finally{ if(!result){ _networkMgr.removeNic(vmProfile, _nicsDao.findById(nic.getId())); } - } + } } else if (vm.getState() == State.Stopped) { //1) allocate nic return _networkMgr.createNicForVm(network, requested, context, vmProfile, false); @@ -2863,10 +2877,10 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac s_logger.warn("Failed to remove nic from " + vm + " in " + network + ", nic is default."); throw new CloudRuntimeException("Failed to remove nic from " + vm + " in " + network + ", nic is default."); } - + // if specified nic is associated with PF/LB/Static NAT if(rulesMgr.listAssociatedRulesForGuestNic(nic).size() > 0){ - throw new CloudRuntimeException("Failed to remove nic from " + vm + " in " + network + throw new CloudRuntimeException("Failed to remove nic from " + vm + " in " + network + ", nic has associated Port forwarding or Load balancer or Static NAT rules."); } @@ -2884,7 +2898,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac s_logger.debug("Nic is unplugged successfully for vm " + vm + " in network " + network ); long isDefault = (nic.isDefaultNic()) ? 1 : 0; UsageEventUtils.publishUsageEvent(EventTypes.EVENT_NETWORK_OFFERING_REMOVE, vm.getAccountId(), vm.getDataCenterId(), - vm.getId(), Long.toString(nic.getId()), network.getNetworkOfferingId(), null, + vm.getId(), Long.toString(nic.getId()), network.getNetworkOfferingId(), null, isDefault, VirtualMachine.class.getName(), vm.getUuid()); } else { s_logger.warn("Failed to unplug nic for the vm " + vm + " from network " + network); From 7cae8ca23102a834509f0277a378e17670992ac6 Mon Sep 17 00:00:00 2001 From: Alena Prokharchyk Date: Fri, 17 May 2013 15:06:50 -0700 Subject: [PATCH 014/108] CLOUDSTACK-751: 41-42 DB upgrade - insert new global config blacklisted.routes --- setup/db/db/schema-410to420.sql | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/setup/db/db/schema-410to420.sql b/setup/db/db/schema-410to420.sql index fe66207e5e5..c088ac1c2f0 100644 --- a/setup/db/db/schema-410to420.sql +++ b/setup/db/db/schema-410to420.sql @@ -1667,6 +1667,10 @@ CREATE TABLE `cloud`.`nic_ip_alias` ( alter table `cloud`.`vpc_gateways` add column network_acl_id bigint unsigned default 1 NOT NULL; update `cloud`.`vpc_gateways` set network_acl_id = 2; + +INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'VpcManager', 'blacklisted.routes', NULL, 'Routes that are blacklisted, can not be used for Static Routes creation for the VPC Private Gateway'); + + -- Re-enable foreign key checking, at the end of the upgrade path SET foreign_key_checks = 1; From 1851f7f7f6bb4bcf3521ea44c51e9506cb86a72d Mon Sep 17 00:00:00 2001 From: Prachi Damle Date: Fri, 17 May 2013 15:32:21 -0700 Subject: [PATCH 015/108] CLOUDSTACK-2365: Anti-Affinity - As admin , we are allowed to deploy a Vm in an affinity group that belongs to different user. CLOUDSTACK-2349: Anti-Affinity - As admin user , using updateVMAffinityGroup() , we are allowed to update the affinity group of a Vm (that belongs to a regular user) to be set to admin's affinity group. Changes: - Even for root-admin make sure that the affinity group and the VM belong to same account --- server/src/com/cloud/vm/UserVmManagerImpl.java | 8 ++++++++ .../cloudstack/affinity/AffinityGroupServiceImpl.java | 9 +++++++++ 2 files changed, 17 insertions(+) diff --git a/server/src/com/cloud/vm/UserVmManagerImpl.java b/server/src/com/cloud/vm/UserVmManagerImpl.java index 860daaf9a5e..05ff6aa74df 100755 --- a/server/src/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/com/cloud/vm/UserVmManagerImpl.java @@ -2366,6 +2366,14 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use } else { // verify permissions _accountMgr.checkAccess(caller, null, true, owner, ag); + // Root admin has access to both VM and AG by default, but + // make sure the owner of these entities is same + if (caller.getId() == Account.ACCOUNT_ID_SYSTEM || _accountMgr.isRootAdmin(caller.getType())) { + if (ag.getAccountId() != owner.getAccountId()) { + throw new PermissionDeniedException("Affinity Group " + ag + + " does not belong to the VM's account"); + } + } } } } diff --git a/server/src/org/apache/cloudstack/affinity/AffinityGroupServiceImpl.java b/server/src/org/apache/cloudstack/affinity/AffinityGroupServiceImpl.java index fc2cfcf8d95..efe18c3b375 100644 --- a/server/src/org/apache/cloudstack/affinity/AffinityGroupServiceImpl.java +++ b/server/src/org/apache/cloudstack/affinity/AffinityGroupServiceImpl.java @@ -36,6 +36,7 @@ import com.cloud.deploy.DeploymentPlanner; import com.cloud.event.ActionEvent; import com.cloud.event.EventTypes; import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.PermissionDeniedException; import com.cloud.exception.ResourceInUseException; import com.cloud.network.security.SecurityGroup; import com.cloud.user.Account; @@ -332,6 +333,14 @@ public class AffinityGroupServiceImpl extends ManagerBase implements AffinityGro } else { // verify permissions _accountMgr.checkAccess(caller, null, true, owner, ag); + // Root admin has access to both VM and AG by default, but make sure the + // owner of these entities is same + if (caller.getId() == Account.ACCOUNT_ID_SYSTEM || _accountMgr.isRootAdmin(caller.getType())) { + if (ag.getAccountId() != owner.getAccountId()) { + throw new PermissionDeniedException("Affinity Group " + ag + + " does not belong to the VM's account"); + } + } } } _affinityGroupVMMapDao.updateMap(vmId, affinityGroupIds); From 239bb13dde0d32ca3e05a8ea2b35a8885cb30538 Mon Sep 17 00:00:00 2001 From: Wido den Hollander Date: Sat, 18 May 2013 10:10:45 +0200 Subject: [PATCH 016/108] Better parse domain XMLs so network devices can be detached as well --- .../kvm/resource/LibvirtDomainXMLParser.java | 57 ++++++++++++------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java index ac4baf122a9..b8645e1664a 100644 --- a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java +++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java @@ -35,6 +35,7 @@ import org.xml.sax.InputSource; import org.xml.sax.SAXException; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.DiskDef; +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.DiskDef.diskProtocol; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.InterfaceDef; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.InterfaceDef.nicModel; @@ -64,31 +65,45 @@ public class LibvirtDomainXMLParser { NodeList disks = devices.getElementsByTagName("disk"); for (int i = 0; i < disks.getLength(); i++) { Element disk = (Element) disks.item(i); - String diskFmtType = getAttrValue("driver", "type", disk); - String diskFile = getAttrValue("source", "file", disk); - String diskDev = getAttrValue("source", "dev", disk); - - String diskLabel = getAttrValue("target", "dev", disk); - String bus = getAttrValue("target", "bus", disk); String type = disk.getAttribute("type"); - String device = disk.getAttribute("device"); - DiskDef def = new DiskDef(); - if (type.equalsIgnoreCase("file")) { - if (device.equalsIgnoreCase("disk")) { - DiskDef.diskFmtType fmt = null; - if (diskFmtType != null) { - fmt = DiskDef.diskFmtType.valueOf(diskFmtType - .toUpperCase()); + if (type.equalsIgnoreCase("network")) { + String diskFmtType = getAttrValue("driver", "type", disk); + String diskPath = getAttrValue("source", "name", disk); + String protocol = getAttrValue("source", "protocol", disk); + String authUserName = getAttrValue("auth", "username", disk); + String poolUuid = getAttrValue("secret", "uuid", disk); + String host = getAttrValue("host", "name", disk); + int port = Integer.parseInt(getAttrValue("host", "port", disk)); + String diskLabel = getAttrValue("target", "dev", disk); + String bus = getAttrValue("target", "bus", disk); + def.defNetworkBasedDisk(diskPath, host, port, authUserName, poolUuid, diskLabel, + DiskDef.diskBus.valueOf(bus.toUpperCase()), DiskDef.diskProtocol.valueOf(protocol.toUpperCase())); + } else { + String diskFmtType = getAttrValue("driver", "type", disk); + String diskFile = getAttrValue("source", "file", disk); + String diskDev = getAttrValue("source", "dev", disk); + + String diskLabel = getAttrValue("target", "dev", disk); + String bus = getAttrValue("target", "bus", disk); + String device = disk.getAttribute("device"); + + if (type.equalsIgnoreCase("file")) { + if (device.equalsIgnoreCase("disk")) { + DiskDef.diskFmtType fmt = null; + if (diskFmtType != null) { + fmt = DiskDef.diskFmtType.valueOf(diskFmtType + .toUpperCase()); + } + def.defFileBasedDisk(diskFile, diskLabel, + DiskDef.diskBus.valueOf(bus.toUpperCase()), fmt); + } else if (device.equalsIgnoreCase("cdrom")) { + def.defISODisk(diskFile); } - def.defFileBasedDisk(diskFile, diskLabel, - DiskDef.diskBus.valueOf(bus.toUpperCase()), fmt); - } else if (device.equalsIgnoreCase("cdrom")) { - def.defISODisk(diskFile); + } else if (type.equalsIgnoreCase("block")) { + def.defBlockBasedDisk(diskDev, diskLabel, + DiskDef.diskBus.valueOf(bus.toUpperCase())); } - } else if (type.equalsIgnoreCase("block")) { - def.defBlockBasedDisk(diskDev, diskLabel, - DiskDef.diskBus.valueOf(bus.toUpperCase())); } diskDefs.add(def); } From 806aeb990d28179ff532e97dbf64f87cd3d5ca34 Mon Sep 17 00:00:00 2001 From: Prasanna Santhanam Date: Sat, 18 May 2013 11:46:12 +0530 Subject: [PATCH 017/108] CLOUDSTACK-2554: CloudStack fails to load XCP 1.6 hypervisors Missing default constructor fails the agent manager reloading the XCP resource on reboot of management server. This is fixed by using the default constructor as do other Xen resources and include a new resource ala XenServers for XCP1.6. Signed-off-by: Prasanna Santhanam --- .../xen/discoverer/XcpServerDiscoverer.java | 109 +++++++++--------- .../hypervisor/xen/resource/CitrixHelper.java | 4 +- .../xen/resource/XcpServer16Resource.java | 32 +++++ .../xen/resource/XcpServerResource.java | 45 +++----- 4 files changed, 107 insertions(+), 83 deletions(-) create mode 100644 plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XcpServer16Resource.java diff --git a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/discoverer/XcpServerDiscoverer.java b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/discoverer/XcpServerDiscoverer.java index 562a7feb96f..f0121e7220b 100755 --- a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/discoverer/XcpServerDiscoverer.java +++ b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/discoverer/XcpServerDiscoverer.java @@ -16,24 +16,6 @@ // under the License. package com.cloud.hypervisor.xen.discoverer; -import java.net.InetAddress; -import java.net.URI; -import java.net.UnknownHostException; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Queue; -import java.util.Set; - -import javax.ejb.Local; -import javax.inject.Inject; -import javax.naming.ConfigurationException; -import javax.persistence.EntityExistsException; - -import org.apache.log4j.Logger; -import org.apache.xmlrpc.XmlRpcException; - import com.cloud.agent.AgentManager; import com.cloud.agent.Listener; import com.cloud.agent.api.AgentControlAnswer; @@ -65,6 +47,7 @@ import com.cloud.hypervisor.Hypervisor; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.hypervisor.xen.resource.CitrixResourceBase; import com.cloud.hypervisor.xen.resource.XcpOssResource; +import com.cloud.hypervisor.xen.resource.XcpServer16Resource; import com.cloud.hypervisor.xen.resource.XcpServerResource; import com.cloud.hypervisor.xen.resource.XenServer56FP1Resource; import com.cloud.hypervisor.xen.resource.XenServer56Resource; @@ -79,9 +62,9 @@ import com.cloud.resource.ResourceManager; import com.cloud.resource.ResourceStateAdapter; import com.cloud.resource.ServerResource; import com.cloud.resource.UnableDeleteHostException; -import com.cloud.storage.VMTemplateVO; import com.cloud.storage.Storage.ImageFormat; import com.cloud.storage.Storage.TemplateType; +import com.cloud.storage.VMTemplateVO; import com.cloud.storage.dao.VMTemplateDao; import com.cloud.storage.dao.VMTemplateHostDao; import com.cloud.user.Account; @@ -97,6 +80,22 @@ import com.xensource.xenapi.Pool; import com.xensource.xenapi.Session; import com.xensource.xenapi.Types.SessionAuthenticationFailed; import com.xensource.xenapi.Types.XenAPIException; +import org.apache.log4j.Logger; +import org.apache.xmlrpc.XmlRpcException; + +import javax.ejb.Local; +import javax.inject.Inject; +import javax.naming.ConfigurationException; +import javax.persistence.EntityExistsException; +import java.net.InetAddress; +import java.net.URI; +import java.net.UnknownHostException; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; @Local(value=Discoverer.class) public class XcpServerDiscoverer extends DiscovererBase implements Discoverer, Listener, ResourceStateAdapter { @@ -420,6 +419,7 @@ public class XcpServerDiscoverer extends DiscovererBase implements Discoverer, L } else { prodBrand = prodBrand.trim(); } + String prodVersion = record.softwareVersion.get("product_version"); if (prodVersion == null) { prodVersion = record.softwareVersion.get("platform_version").trim(); @@ -427,36 +427,35 @@ public class XcpServerDiscoverer extends DiscovererBase implements Discoverer, L prodVersion = prodVersion.trim(); } - if(prodBrand.equals("XCP") && (prodVersion.equals("1.0.0") || prodVersion.equals("1.1.0") || prodVersion.equals("5.6.100") || prodVersion.startsWith("1.4"))) { - return new XcpServerResource("1.1"); + // Xen Cloud Platform group of hypervisors + if (prodBrand.equals("XCP") && ( + prodVersion.equals("1.0.0") + || prodVersion.equals("1.1.0") + || prodVersion.equals("5.6.100") + || prodVersion.startsWith("1.4") + )) { + return new XcpServerResource(); } else if (prodBrand.equals("XCP") && prodVersion.startsWith("1.6")) { - return new XcpServerResource("1.6"); - } - - if(prodBrand.equals("XenServer") && prodVersion.equals("5.6.0")) - return new XenServer56Resource(); - - if (prodBrand.equals("XenServer") && prodVersion.equals("6.0.0")) - return new XenServer600Resource(); - - if (prodBrand.equals("XenServer") && prodVersion.equals("6.0.2")) - return new XenServer602Resource(); - - if (prodBrand.equals("XenServer") && prodVersion.equals("6.1.0")) + return new XcpServer16Resource(); + } // Citrix Xenserver group of hypervisors + else if (prodBrand.equals("XenServer") && prodVersion.equals("5.6.0")) + return new XenServer56Resource(); + else if (prodBrand.equals("XenServer") && prodVersion.equals("6.0.0")) + return new XenServer600Resource(); + else if (prodBrand.equals("XenServer") && prodVersion.equals("6.0.2")) + return new XenServer602Resource(); + else if (prodBrand.equals("XenServer") && prodVersion.equals("6.1.0")) return new XenServer610Resource(); - - if(prodBrand.equals("XenServer") && prodVersion.equals("5.6.100")) { - String prodVersionTextShort = record.softwareVersion.get("product_version_text_short").trim(); - if("5.6 SP2".equals(prodVersionTextShort)) { - return new XenServer56SP2Resource(); - } else if("5.6 FP1".equals(prodVersionTextShort)) { - return new XenServer56FP1Resource(); - } - } - - if (prodBrand.equals("XCP_Kronos")) { - return new XcpOssResource(); - } + else if (prodBrand.equals("XenServer") && prodVersion.equals("5.6.100")) { + String prodVersionTextShort = record.softwareVersion.get("product_version_text_short").trim(); + if ("5.6 SP2".equals(prodVersionTextShort)) { + return new XenServer56SP2Resource(); + } else if ("5.6 FP1".equals(prodVersionTextShort)) { + return new XenServer56FP1Resource(); + } + } else if (prodBrand.equals("XCP_Kronos")) { + return new XcpOssResource(); + } String msg = "Only support XCP 1.0.0, 1.1.0, 1.4.x, 1.5 beta, 1.6.x; XenServer 5.6, XenServer 5.6 FP1, XenServer 5.6 SP2, Xenserver 6.0, 6.0.2, 6.1.0 but this one is " + prodBrand + " " + prodVersion; _alertMgr.sendAlert(AlertManager.ALERT_TYPE_HOST, dcId, podId, msg, msg); @@ -585,10 +584,12 @@ public class XcpServerDiscoverer extends DiscovererBase implements Discoverer, L Map details = startup.getHostDetails(); String prodBrand = details.get("product_brand").trim(); String prodVersion = details.get("product_version").trim(); - - if(prodBrand.equals("XCP") && (prodVersion.equals("1.0.0") || prodVersion.equals("1.1.0") || prodVersion.equals("5.6.100") || prodVersion.startsWith("1.4") || prodVersion.startsWith("1.6"))) { + + if (prodBrand.equals("XCP") && (prodVersion.equals("1.0.0") || prodVersion.equals("1.1.0") || prodVersion.equals("5.6.100") || prodVersion.startsWith("1.4"))) { resource = XcpServerResource.class.getName(); - } else if(prodBrand.equals("XenServer") && prodVersion.equals("5.6.0")) { + } else if (prodBrand.equals("XCP") && prodVersion.startsWith("1.6")) { + resource = XcpServer16Resource.class.getName(); + } else if (prodBrand.equals("XenServer") && prodVersion.equals("5.6.0")) { resource = XenServer56Resource.class.getName(); } else if (prodBrand.equals("XenServer") && prodVersion.equals("6.0.0")) { resource = XenServer600Resource.class.getName(); @@ -596,15 +597,15 @@ public class XcpServerDiscoverer extends DiscovererBase implements Discoverer, L resource = XenServer602Resource.class.getName(); } else if (prodBrand.equals("XenServer") && prodVersion.equals("6.1.0")) { resource = XenServer610Resource.class.getName(); - } else if(prodBrand.equals("XenServer") && prodVersion.equals("5.6.100")) { + } else if (prodBrand.equals("XenServer") && prodVersion.equals("5.6.100")) { String prodVersionTextShort = details.get("product_version_text_short").trim(); - if("5.6 SP2".equals(prodVersionTextShort)) { + if ("5.6 SP2".equals(prodVersionTextShort)) { resource = XenServer56SP2Resource.class.getName(); - } else if("5.6 FP1".equals(prodVersionTextShort)) { + } else if ("5.6 FP1".equals(prodVersionTextShort)) { resource = XenServer56FP1Resource.class.getName(); } } else if (prodBrand.equals("XCP_Kronos")) { - resource = XcpOssResource.class.getName(); + resource = XcpOssResource.class.getName(); } if( resource == null ){ diff --git a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixHelper.java b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixHelper.java index 34b8f2981e2..0f71c7ba9ad 100644 --- a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixHelper.java +++ b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixHelper.java @@ -16,11 +16,11 @@ // under the License. package com.cloud.hypervisor.xen.resource; +import org.apache.log4j.Logger; + import java.util.ArrayList; import java.util.HashMap; -import org.apache.log4j.Logger; - /** * Reduce bloat inside CitrixResourceBase * diff --git a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XcpServer16Resource.java b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XcpServer16Resource.java new file mode 100644 index 00000000000..8cb7997305f --- /dev/null +++ b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XcpServer16Resource.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.cloud.hypervisor.xen.resource; + +public class XcpServer16Resource extends XcpServerResource { + + public XcpServer16Resource() { + super(); + } + + @Override + protected String getGuestOsType(String stdType, boolean bootFromCD) { + return CitrixHelper.getXcp160GuestOsType(stdType); + } +} diff --git a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XcpServerResource.java b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XcpServerResource.java index 6baf6a09e3f..77dcb8f568a 100644 --- a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XcpServerResource.java +++ b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XcpServerResource.java @@ -16,15 +16,6 @@ // under the License. package com.cloud.hypervisor.xen.resource; -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -import javax.ejb.Local; - -import org.apache.log4j.Logger; -import org.apache.xmlrpc.XmlRpcException; - import com.cloud.agent.api.Answer; import com.cloud.agent.api.Command; import com.cloud.agent.api.NetworkUsageAnswer; @@ -33,18 +24,25 @@ import com.cloud.resource.ServerResource; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.script.Script; import com.xensource.xenapi.Connection; -import com.xensource.xenapi.VM; import com.xensource.xenapi.Types.XenAPIException; +import com.xensource.xenapi.VM; +import org.apache.log4j.Logger; +import org.apache.xmlrpc.XmlRpcException; + +import javax.ejb.Local; +import java.io.File; +import java.util.ArrayList; +import java.util.List; @Local(value=ServerResource.class) public class XcpServerResource extends CitrixResourceBase { - private final static Logger s_logger = Logger.getLogger(XcpServerResource.class); - private String version; - public XcpServerResource(String version) { + private final static Logger s_logger = Logger.getLogger(XcpServerResource.class); + private String version; + + public XcpServerResource() { super(); - this.version = version; } - + @Override public Answer executeRequest(Command cmd) { if (cmd instanceof NetworkUsageCommand) { @@ -53,15 +51,6 @@ public class XcpServerResource extends CitrixResourceBase { return super.executeRequest(cmd); } } - - @Override - protected String getGuestOsType(String stdType, boolean bootFromCD) { - if (version.equalsIgnoreCase("1.6")) { - return CitrixHelper.getXcp160GuestOsType(stdType); - } else { - return CitrixHelper.getXcpGuestOsType(stdType); - } - } @Override protected List getPatchFiles() { @@ -76,6 +65,11 @@ public class XcpServerResource extends CitrixResourceBase { return files; } + @Override + protected String getGuestOsType(String stdType, boolean bootFromCD) { + return CitrixHelper.getXcpGuestOsType(stdType); + } + @Override protected void setMemory(Connection conn, VM vm, long minMemsize, long maxMemsize) throws XmlRpcException, XenAPIException { @@ -90,7 +84,6 @@ public class XcpServerResource extends CitrixResourceBase { //vm.setMemoryStaticMin(conn, maxMemsize ); } - protected NetworkUsageAnswer execute(NetworkUsageCommand cmd) { try { Connection conn = getConnection(); @@ -107,6 +100,4 @@ public class XcpServerResource extends CitrixResourceBase { return new NetworkUsageAnswer(cmd, ex); } } - - } From 6ea2b06aab537f34733872e65041f3499481fb59 Mon Sep 17 00:00:00 2001 From: Prasanna Santhanam Date: Sun, 19 May 2013 15:56:30 +0530 Subject: [PATCH 018/108] Fixing multiple minor annoyances: 1. Keeping the description consistent - Memory not RAM when referring to overcommit 2. getters And setters grouped, provided right casing. 3. Removed wildcard imports Signed-off-by: Prasanna Santhanam --- .../command/admin/cluster/AddClusterCmd.java | 36 ++++++++---------- .../admin/cluster/UpdateClusterCmd.java | 6 +-- .../api/response/ClusterResponse.java | 27 ++++++------- .../src/com/cloud/api/ApiResponseHelper.java | 10 +---- .../cloud/capacity/CapacityManagerImpl.java | 20 +++++----- .../cloud/resource/ResourceManagerImpl.java | 12 +++--- .../cloud/vm/VirtualMachineManagerImpl.java | 4 +- .../cloud/vm/VirtualMachineProfileImpl.java | 38 +++++++++---------- 8 files changed, 71 insertions(+), 82 deletions(-) diff --git a/api/src/org/apache/cloudstack/api/command/admin/cluster/AddClusterCmd.java b/api/src/org/apache/cloudstack/api/command/admin/cluster/AddClusterCmd.java index d55ccd7dd11..c6ca9bc673b 100644 --- a/api/src/org/apache/cloudstack/api/command/admin/cluster/AddClusterCmd.java +++ b/api/src/org/apache/cloudstack/api/command/admin/cluster/AddClusterCmd.java @@ -17,13 +17,11 @@ package org.apache.cloudstack.api.command.admin.cluster; -import java.util.ArrayList; -import java.util.List; - +import com.cloud.exception.DiscoveryException; import com.cloud.exception.InvalidParameterValueException; -import org.apache.cloudstack.api.*; -import org.apache.log4j.Logger; - +import com.cloud.exception.ResourceInUseException; +import com.cloud.org.Cluster; +import com.cloud.user.Account; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; @@ -36,10 +34,8 @@ import org.apache.cloudstack.api.response.PodResponse; import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.log4j.Logger; -import com.cloud.exception.DiscoveryException; -import com.cloud.exception.ResourceInUseException; -import com.cloud.org.Cluster; -import com.cloud.user.Account; +import java.util.ArrayList; +import java.util.List; @APICommand(name = "addCluster", description="Adds a new cluster", responseObject=ClusterResponse.class) public class AddClusterCmd extends BaseCmd { @@ -86,10 +82,10 @@ public class AddClusterCmd extends BaseCmd { private String vsmipaddress; @Parameter (name=ApiConstants.CPU_OVERCOMMIT_RATIO, type = CommandType.STRING, required = false , description = "value of the cpu overcommit ratio, defaults to 1") - private String cpuovercommitRatio; + private String cpuOvercommitRatio; - @Parameter(name = ApiConstants.MEMORY_OVERCOMMIT_RATIO, type = CommandType.STRING, required = false ,description = "value of the default ram overcommit ratio, defaults to 1") - private String memoryovercommitratio; + @Parameter(name = ApiConstants.MEMORY_OVERCOMMIT_RATIO, type = CommandType.STRING, required = false, description = "value of the default memory overcommit ratio, defaults to 1") + private String memoryOvercommitRatio; @Parameter(name = ApiConstants.VSWITCH_TYPE_GUEST_TRAFFIC, type = CommandType.STRING, required = false, description = "Type of virtual switch used for guest traffic in the cluster. Allowed values are, vmwaresvs (for VMware standard vSwitch) and vmwaredvs (for VMware distributed vSwitch)") private String vSwitchTypeGuestTraffic; @@ -186,15 +182,15 @@ public class AddClusterCmd extends BaseCmd { } public Float getCpuOvercommitRatio (){ - if(cpuovercommitRatio != null){ - return Float.parseFloat(cpuovercommitRatio); + if(cpuOvercommitRatio != null){ + return Float.parseFloat(cpuOvercommitRatio); } return 1.0f; } - public Float getMemoryOvercommitRaito (){ - if (memoryovercommitratio != null){ - return Float.parseFloat(memoryovercommitratio); + public Float getMemoryOvercommitRatio(){ + if (memoryOvercommitRatio != null){ + return Float.parseFloat(memoryOvercommitRatio); } return 1.0f; } @@ -202,8 +198,8 @@ public class AddClusterCmd extends BaseCmd { @Override public void execute(){ try { - if ((getMemoryOvercommitRaito().compareTo(1f) < 0) | (getCpuOvercommitRatio().compareTo(1f) < 0)) { - throw new InvalidParameterValueException("Cpu and ram overcommit ratios should not be less than 1"); + if (getMemoryOvercommitRatio().compareTo(1f) < 0 || getCpuOvercommitRatio().compareTo(1f) < 0) { + throw new InvalidParameterValueException("cpu and memory overcommit ratios should be greater than or equal to one"); } List result = _resourceService.discoverCluster(this); ListResponse response = new ListResponse(); diff --git a/api/src/org/apache/cloudstack/api/command/admin/cluster/UpdateClusterCmd.java b/api/src/org/apache/cloudstack/api/command/admin/cluster/UpdateClusterCmd.java index c5130587ec5..a14f9055211 100644 --- a/api/src/org/apache/cloudstack/api/command/admin/cluster/UpdateClusterCmd.java +++ b/api/src/org/apache/cloudstack/api/command/admin/cluster/UpdateClusterCmd.java @@ -57,7 +57,7 @@ public class UpdateClusterCmd extends BaseCmd { @Parameter(name=ApiConstants.CPU_OVERCOMMIT_RATIO, type = CommandType.STRING, description = "Value of cpu overcommit ratio") private String cpuovercommitratio; - @Parameter(name=ApiConstants.MEMORY_OVERCOMMIT_RATIO, type = CommandType.STRING, description = "Value of ram overcommit ratio") + @Parameter(name=ApiConstants.MEMORY_OVERCOMMIT_RATIO, type = CommandType.STRING, description = "Value of memory overcommit ratio") private String memoryovercommitratio; @@ -129,13 +129,13 @@ public class UpdateClusterCmd extends BaseCmd { } if (getMemoryOvercommitRaito() !=null){ if ((getMemoryOvercommitRaito().compareTo(1f) < 0)) { - throw new InvalidParameterValueException("Memory overcommit ratio should be greater than or equal to one"); + throw new InvalidParameterValueException("Memory overcommit ratio should be greater than or equal to one"); } } if (getCpuOvercommitRatio() !=null){ if (getCpuOvercommitRatio().compareTo(1f) < 0) { - throw new InvalidParameterValueException("Cpu overcommit ratio should be greater than or equal to one"); + throw new InvalidParameterValueException("Cpu overcommit ratio should be greater than or equal to one"); } } diff --git a/api/src/org/apache/cloudstack/api/response/ClusterResponse.java b/api/src/org/apache/cloudstack/api/response/ClusterResponse.java index cfd772d7115..ef8cca65040 100644 --- a/api/src/org/apache/cloudstack/api/response/ClusterResponse.java +++ b/api/src/org/apache/cloudstack/api/response/ClusterResponse.java @@ -16,16 +16,15 @@ // under the License. package org.apache.cloudstack.api.response; -import java.util.ArrayList; -import java.util.List; - +import com.cloud.org.Cluster; +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseResponse; import org.apache.cloudstack.api.EntityReference; -import com.cloud.org.Cluster; -import com.cloud.serializer.Param; -import com.google.gson.annotations.SerializedName; +import java.util.ArrayList; +import java.util.List; @EntityReference(value = Cluster.class) public class ClusterResponse extends BaseResponse { @@ -68,7 +67,7 @@ public class ClusterResponse extends BaseResponse { @SerializedName("cpuovercommitratio") @Param(description = "The cpu overcommit ratio of the cluster") private String cpuovercommitratio; - @SerializedName("memoryovercommitratio") @Param (description = "The ram overcommit ratio of the cluster") + @SerializedName("memoryovercommitratio") @Param (description = "The memory overcommit ratio of the cluster") private String memoryovercommitratio; public String getId() { @@ -162,18 +161,20 @@ public class ClusterResponse extends BaseResponse { public void setCapacitites(ArrayList arrayList) { this.capacitites = arrayList; } - public void setCpuovercommitratio(String cpuovercommitratio){ + + public void setCpuOvercommitRatio(String cpuovercommitratio){ this.cpuovercommitratio= cpuovercommitratio; } - public void setRamovercommitratio (String memoryOvercommitRatio){ - this.memoryovercommitratio= memoryOvercommitRatio; - } - public String getCpuovercommitratio (){ + public String getCpuOvercommitRatio(){ return cpuovercommitratio; } - public String getRamovercommitratio (){ + public void setMemoryOvercommitRatio(String memoryovercommitratio){ + this.memoryovercommitratio= memoryovercommitratio; + } + + public String getMemoryOvercommitRatio(){ return memoryovercommitratio; } } diff --git a/server/src/com/cloud/api/ApiResponseHelper.java b/server/src/com/cloud/api/ApiResponseHelper.java index 482594dd8f1..49e36dc47db 100755 --- a/server/src/com/cloud/api/ApiResponseHelper.java +++ b/server/src/com/cloud/api/ApiResponseHelper.java @@ -40,12 +40,6 @@ import com.cloud.network.vpc.PrivateGateway; import com.cloud.network.vpc.StaticRoute; import com.cloud.network.vpc.Vpc; import com.cloud.network.vpc.VpcOffering; -import com.cloud.vm.*; -import com.cloud.network.vpc.NetworkACL; -import com.cloud.network.vpc.PrivateGateway; -import com.cloud.network.vpc.StaticRoute; -import com.cloud.network.vpc.Vpc; -import com.cloud.network.vpc.VpcOffering; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.affinity.AffinityGroup; @@ -975,8 +969,8 @@ public class ApiResponseHelper implements ResponseGenerator { clusterResponse.setManagedState(cluster.getManagedState().toString()); String cpuOvercommitRatio=ApiDBUtils.findClusterDetails(cluster.getId(),"cpuOvercommitRatio").getValue(); String memoryOvercommitRatio=ApiDBUtils.findClusterDetails(cluster.getId(),"memoryOvercommitRatio").getValue(); - clusterResponse.setCpuovercommitratio(cpuOvercommitRatio); - clusterResponse.setRamovercommitratio(memoryOvercommitRatio); + clusterResponse.setCpuOvercommitRatio(cpuOvercommitRatio); + clusterResponse.setMemoryOvercommitRatio(memoryOvercommitRatio); if (showCapacities != null && showCapacities) { diff --git a/server/src/com/cloud/capacity/CapacityManagerImpl.java b/server/src/com/cloud/capacity/CapacityManagerImpl.java index 2f82b6875b3..e58ae4079bc 100755 --- a/server/src/com/cloud/capacity/CapacityManagerImpl.java +++ b/server/src/com/cloud/capacity/CapacityManagerImpl.java @@ -177,10 +177,10 @@ public class CapacityManagerImpl extends ManagerBase implements CapacityManager, ServiceOfferingVO svo = _offeringsDao.findById(vm.getServiceOfferingId()); CapacityVO capacityCpu = _capacityDao.findByHostIdType(hostId, CapacityVO.CAPACITY_TYPE_CPU); CapacityVO capacityMemory = _capacityDao.findByHostIdType(hostId, CapacityVO.CAPACITY_TYPE_MEMORY); - Long clusterId=null; + Long clusterId = null; if (hostId != null) { - HostVO host = _hostDao.findById(hostId); - clusterId= host.getClusterId(); + HostVO host = _hostDao.findById(hostId); + clusterId = host.getClusterId(); } if (capacityCpu == null || capacityMemory == null || svo == null) { return false; @@ -263,8 +263,8 @@ public class CapacityManagerImpl extends ManagerBase implements CapacityManager, long hostId = vm.getHostId(); HostVO host = _hostDao.findById(hostId); long clusterId = host.getClusterId(); - float cpuOvercommitRatio =Float.parseFloat(_clusterDetailsDao.findDetail(clusterId,"cpuOvercommitRatio").getValue()); - float memoryOvercommitRatio = Float.parseFloat(_clusterDetailsDao.findDetail(clusterId,"memoryOvercommitRatio").getValue()); + float cpuOvercommitRatio = Float.parseFloat(_clusterDetailsDao.findDetail(clusterId, "cpuOvercommitRatio").getValue()); + float memoryOvercommitRatio = Float.parseFloat(_clusterDetailsDao.findDetail(clusterId, "memoryOvercommitRatio").getValue()); ServiceOfferingVO svo = _offeringsDao.findById(vm.getServiceOfferingId()); @@ -348,7 +348,7 @@ public class CapacityManagerImpl extends ManagerBase implements CapacityManager, } @Override - public boolean checkIfHostHasCapacity(long hostId, Integer cpu, long ram, boolean checkFromReservedCapacity, float cpuOvercommitRatio,float memoryOvercommitRatio, boolean considerReservedCapacity) { + public boolean checkIfHostHasCapacity(long hostId, Integer cpu, long ram, boolean checkFromReservedCapacity, float cpuOvercommitRatio, float memoryOvercommitRatio, boolean considerReservedCapacity) { boolean hasCapacity = false; if (s_logger.isDebugEnabled()) { @@ -381,7 +381,7 @@ public class CapacityManagerImpl extends ManagerBase implements CapacityManager, long actualTotalCpu = capacityCpu.getTotalCapacity(); long actualTotalMem = capacityMem.getTotalCapacity(); long totalCpu = (long) (actualTotalCpu * cpuOvercommitRatio ); - long totalMem = (long) (actualTotalMem *memoryOvercommitRatio ); + long totalMem = (long) (actualTotalMem * memoryOvercommitRatio); if (s_logger.isDebugEnabled()) { s_logger.debug("Hosts's actual total CPU: " + actualTotalCpu + " and CPU after applying overprovisioning: " + totalCpu); } @@ -744,8 +744,8 @@ public class CapacityManagerImpl extends ManagerBase implements CapacityManager, capacityCPU.addAnd("podId", SearchCriteria.Op.EQ, server.getPodId()); capacityCPU.addAnd("capacityType", SearchCriteria.Op.EQ, CapacityVO.CAPACITY_TYPE_CPU); List capacityVOCpus = _capacityDao.search(capacitySC, null); - Float cpuovercommitratio = Float.parseFloat(_clusterDetailsDao.findDetail(server.getClusterId(),"cpuOvercommitRatio").getValue()); - Float memoryOvercommitRatio = Float.parseFloat(_clusterDetailsDao.findDetail(server.getClusterId(),"memoryOvercommitRatio").getValue()); + Float cpuovercommitratio = Float.parseFloat(_clusterDetailsDao.findDetail(server.getClusterId(), "cpuOvercommitRatio").getValue()); + Float memoryOvercommitRatio = Float.parseFloat(_clusterDetailsDao.findDetail(server.getClusterId(), "memoryOvercommitRatio").getValue()); if (capacityVOCpus != null && !capacityVOCpus.isEmpty()) { CapacityVO CapacityVOCpu = capacityVOCpus.get(0); @@ -778,7 +778,7 @@ public class CapacityManagerImpl extends ManagerBase implements CapacityManager, if (capacityVOMems != null && !capacityVOMems.isEmpty()) { CapacityVO CapacityVOMem = capacityVOMems.get(0); - long newTotalMem = (long)((server.getTotalMemory())* memoryOvercommitRatio); + long newTotalMem = (long) ((server.getTotalMemory()) * memoryOvercommitRatio); if (CapacityVOMem.getTotalCapacity() <= newTotalMem || (CapacityVOMem.getUsedCapacity() + CapacityVOMem.getReservedCapacity() <= newTotalMem)) { CapacityVOMem.setTotalCapacity(newTotalMem); diff --git a/server/src/com/cloud/resource/ResourceManagerImpl.java b/server/src/com/cloud/resource/ResourceManagerImpl.java index c60f0953a78..25f451e7127 100755 --- a/server/src/com/cloud/resource/ResourceManagerImpl.java +++ b/server/src/com/cloud/resource/ResourceManagerImpl.java @@ -505,10 +505,10 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager, clusterId = cluster.getId(); result.add(cluster); - ClusterDetailsVO cluster_detail_cpu = new ClusterDetailsVO(clusterId, "cpuOvercommitRatio", Float.toString(cmd.getCpuOvercommitRatio())); - ClusterDetailsVO cluster_detail_ram = new ClusterDetailsVO(clusterId, "memoryOvercommitRatio", Float.toString(cmd.getMemoryOvercommitRaito())); - _clusterDetailsDao.persist(cluster_detail_cpu); - _clusterDetailsDao.persist(cluster_detail_ram); + ClusterDetailsVO cluster_detail_cpu = new ClusterDetailsVO(clusterId, "cpuOvercommitRatio", Float.toString(cmd.getCpuOvercommitRatio())); + ClusterDetailsVO cluster_detail_ram = new ClusterDetailsVO(clusterId, "memoryOvercommitRatio", Float.toString(cmd.getMemoryOvercommitRatio())); + _clusterDetailsDao.persist(cluster_detail_cpu); + _clusterDetailsDao.persist(cluster_detail_ram); if (clusterType == Cluster.ClusterType.CloudManaged) { return result; @@ -530,8 +530,8 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager, } - if(cmd.getMemoryOvercommitRaito().compareTo(1f) > 0) { - cluster_detail_ram = new ClusterDetailsVO(clusterId, "memoryOvercommitRatio", Float.toString(cmd.getMemoryOvercommitRaito())); + if(cmd.getMemoryOvercommitRatio().compareTo(1f) > 0) { + cluster_detail_ram = new ClusterDetailsVO(clusterId, "memoryOvercommitRatio", Float.toString(cmd.getMemoryOvercommitRatio())); _clusterDetailsDao.persist(cluster_detail_ram); } diff --git a/server/src/com/cloud/vm/VirtualMachineManagerImpl.java b/server/src/com/cloud/vm/VirtualMachineManagerImpl.java index cea05675f32..58d95ab81a1 100755 --- a/server/src/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/server/src/com/cloud/vm/VirtualMachineManagerImpl.java @@ -824,8 +824,8 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac Long cluster_id = dest.getCluster().getId(); ClusterDetailsVO cluster_detail_cpu = _clusterDetailsDao.findDetail(cluster_id,"cpuOvercommitRatio"); ClusterDetailsVO cluster_detail_ram = _clusterDetailsDao.findDetail(cluster_id,"memoryOvercommitRatio"); - vmProfile.setcpuOvercommitRatio(Float.parseFloat(cluster_detail_cpu.getValue())); - vmProfile.setramOvercommitRatio(Float.parseFloat(cluster_detail_ram.getValue())); + vmProfile.setCpuOvercommitRatio(Float.parseFloat(cluster_detail_cpu.getValue())); + vmProfile.setMemoryOvercommitRatio(Float.parseFloat(cluster_detail_ram.getValue())); try { if (!changeState(vm, Event.OperationRetry, destHostId, work, Step.Prepare)) { diff --git a/server/src/com/cloud/vm/VirtualMachineProfileImpl.java b/server/src/com/cloud/vm/VirtualMachineProfileImpl.java index 24f44cb07ac..e777d5ef498 100644 --- a/server/src/com/cloud/vm/VirtualMachineProfileImpl.java +++ b/server/src/com/cloud/vm/VirtualMachineProfileImpl.java @@ -16,12 +16,6 @@ // under the License. package com.cloud.vm; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - - import com.cloud.agent.api.to.VolumeTO; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.offering.ServiceOffering; @@ -34,6 +28,11 @@ import com.cloud.template.VirtualMachineTemplate.BootloaderType; import com.cloud.user.Account; import com.cloud.user.dao.AccountDao; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + /** * Implementation of VirtualMachineProfile. * @@ -239,28 +238,27 @@ public class VirtualMachineProfileImpl implements Virtua return _params; } - public void setServiceOffering(ServiceOfferingVO offering) { - _offering = offering; - } + public void setServiceOffering(ServiceOfferingVO offering) { + _offering = offering; + } - public void setcpuOvercommitRatio(Float cpuOvercommitRatio){ - this.cpuOvercommitRatio= cpuOvercommitRatio; + public void setCpuOvercommitRatio(Float cpuOvercommitRatio) { + this.cpuOvercommitRatio = cpuOvercommitRatio; } - public void setramOvercommitRatio(Float memoryOvercommitRatio){ - this.memoryOvercommitRatio= memoryOvercommitRatio; + public void setMemoryOvercommitRatio(Float memoryOvercommitRatio) { + this.memoryOvercommitRatio = memoryOvercommitRatio; } - @Override - public Float getCpuOvercommitRatio(){ - return this.cpuOvercommitRatio; - } @Override - public Float getMemoryOvercommitRatio(){ + public Float getCpuOvercommitRatio() { + return this.cpuOvercommitRatio; + } + + @Override + public Float getMemoryOvercommitRatio() { return this.memoryOvercommitRatio; } - - } From 7ea2c950f5c1c2446ae94087d7f78812cc94a658 Mon Sep 17 00:00:00 2001 From: Prasanna Santhanam Date: Sun, 19 May 2013 16:07:18 +0530 Subject: [PATCH 019/108] CLOUDSTACK-2554: Ensuring that we honor hypervisor xen's memory constraints When setting memory constraints on Xen guests we should honor: static-min <= dynamic-min <= dynamic-max <= static-max Our VmSpec while allows the guests to like between dynamic-min and dynamic-max the memory set by the resource set the static min to be equal to the dynamic max. This restricts the hypervisor from ensuring optimized memory handling of guests. see: http://wiki.xen.org/wiki/XCP_FAQ_Dynamic_Memory_Control#How_does_XCP_choose_targets_for_guests_in_dynamic_range_mode.3F Another fix was related the restrict_dmc option. when enabled (true) this option disallows scaling a vm. The logic was reverse handled allowing scaling when restrict_dmc was on. This control flow is now fixed. Signed-off-by: Prasanna Santhanam --- .../xen/resource/CitrixResourceBase.java | 25 ++++++- .../xen/resource/XcpServerResource.java | 73 +++++++++++++++---- .../xen/resource/XenServer56FP1Resource.java | 36 +++++---- 3 files changed, 98 insertions(+), 36 deletions(-) diff --git a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java index a1663473894..78bca32c92e 100644 --- a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java +++ b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java @@ -335,6 +335,9 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe long _xs_memory_used = 128 * 1024 * 1024L; // xen hypervisor used 128 M double _xs_virtualization_factor = 63.0/64.0; // 1 - virtualization overhead + //static min values for guests on xen + private static final long mem_128m = 134217728L; + protected boolean _canBridgeFirewall = false; protected boolean _isOvs = false; protected List _tmpDom0Vif = new ArrayList(); @@ -1208,8 +1211,11 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe } Set templates = VM.getByNameLabel(conn, guestOsTypeName); assert templates.size() == 1 : "Should only have 1 template but found " + templates.size(); + if (!templates.iterator().hasNext()) { + throw new CloudRuntimeException("No matching OS type found for starting a [" + vmSpec.getOs() + + "] VM on host " + host.getHostname(conn)); + } VM template = templates.iterator().next(); - VM vm = template.createClone(conn, vmSpec.getName()); VM.Record vmr = vm.getRecord(conn); if (s_logger.isDebugEnabled()) { @@ -3503,8 +3509,21 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe } } + /** + * WARN: static-min <= dynamic-min <= dynamic-max <= static-max + * @see XcpServerResource#setMemory(com.xensource.xenapi.Connection, com.xensource.xenapi.VM, long, long) + * @param conn + * @param vm + * @param minMemsize + * @param maxMemsize + * @throws XmlRpcException + * @throws XenAPIException + */ protected void setMemory(Connection conn, VM vm, long minMemsize, long maxMemsize) throws XmlRpcException, XenAPIException { - vm.setMemoryLimits(conn, maxMemsize, maxMemsize, minMemsize, maxMemsize); + vm.setMemoryStaticMin(conn, mem_128m); + vm.setMemoryDynamicMin(conn, minMemsize); + vm.setMemoryDynamicMax(conn, maxMemsize); + vm.setMemoryStaticMax(conn, maxMemsize); } protected void waitForTask(Connection c, Task task, long pollInterval, long timeout) throws XenAPIException, XmlRpcException { @@ -4299,7 +4318,7 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe * @throws XenAPIException * @throws XmlRpcException * - * @see enableVlanNetwork + * @see CitrixResourceBase#enableVlanNetwork */ protected XsLocalNetwork getNetworkByName(Connection conn, String name) throws XenAPIException, XmlRpcException { Set networks = Network.getByNameLabel(conn, name); diff --git a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XcpServerResource.java b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XcpServerResource.java index 77dcb8f568a..7df4c46a63d 100644 --- a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XcpServerResource.java +++ b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XcpServerResource.java @@ -37,6 +37,8 @@ import java.util.List; @Local(value=ServerResource.class) public class XcpServerResource extends CitrixResourceBase { private final static Logger s_logger = Logger.getLogger(XcpServerResource.class); + private static final long mem_32m = 33554432L; + private String version; public XcpServerResource() { @@ -70,20 +72,6 @@ public class XcpServerResource extends CitrixResourceBase { return CitrixHelper.getXcpGuestOsType(stdType); } - @Override - protected void setMemory(Connection conn, VM vm, long minMemsize, long maxMemsize) throws XmlRpcException, XenAPIException { - - vm.setMemoryStaticMin(conn, 33554432L); - //vm.setMemoryDynamicMin(conn, 33554432L); - //vm.setMemoryDynamicMax(conn, 33554432L); - vm.setMemoryStaticMax(conn, 33554432L); - - //vm.setMemoryStaticMax(conn, maxMemsize ); - vm.setMemoryDynamicMax(conn, maxMemsize ); - vm.setMemoryDynamicMin(conn, minMemsize ); - //vm.setMemoryStaticMin(conn, maxMemsize ); - } - protected NetworkUsageAnswer execute(NetworkUsageCommand cmd) { try { Connection conn = getConnection(); @@ -100,4 +88,61 @@ public class XcpServerResource extends CitrixResourceBase { return new NetworkUsageAnswer(cmd, ex); } } + + /** + XCP provides four memory configuration fields through which + administrators can control this behaviour: + + * static-min + * dynamic-min + * dynamic-max + * static-max + + The fields static-{min,max} act as *hard* lower and upper + bounds for a guest's memory. For a running guest: + * it's not possible to assign the guest more memory than + static-max without first shutting down the guest. + * it's not possible to assign the guest less memory than + static-min without first shutting down the guest. + + The fields dynamic-{min,max} act as *soft* lower and upper + bounds for a guest's memory. It's possible to change these + fields even when a guest is running. + + The dynamic range must lie wholly within the static range. To + put it another way, XCP at all times ensures that: + + static-min <= dynamic-min <= dynamic-max <= static-max + + At all times, XCP will attempt to keep a guest's memory usage + between dynamic-min and dynamic-max. + + If dynamic-min = dynamic-max, then XCP will attempt to keep + a guest's memory allocation at a constant size. + + If dynamic-min < dynamic-max, then XCP will attempt to give + the guest as much memory as possible, while keeping the guest + within dynamic-min and dynamic-max. + + If there is enough memory on a given host to give all resident + guests dynamic-max, then XCP will attempt do so. + + If there is not enough memory to give all guests dynamic-max, + then XCP will ask each of the guests (on that host) to use + an amount of memory that is the same *proportional* distance + between dynamic-min and dynamic-max. + + XCP will refuse to start guests if starting those guests would + cause the sum of all the dynamic-min values to exceed the total + host memory (taking into account various memory overheads). + + cf: http://wiki.xen.org/wiki/XCP_FAQ_Dynamic_Memory_Control + */ + @Override + protected void setMemory(Connection conn, VM vm, long minMemsize, long maxMemsize) throws XmlRpcException, XenAPIException { + vm.setMemoryStaticMin(conn, mem_32m); + vm.setMemoryDynamicMin(conn, minMemsize); + vm.setMemoryDynamicMax(conn, maxMemsize); + vm.setMemoryStaticMax(conn, maxMemsize); + } } diff --git a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XenServer56FP1Resource.java b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XenServer56FP1Resource.java index 24cb75cbf93..ad412c1e3d0 100644 --- a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XenServer56FP1Resource.java +++ b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XenServer56FP1Resource.java @@ -17,18 +17,6 @@ package com.cloud.hypervisor.xen.resource; -import java.io.File; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.ejb.Local; -import org.apache.log4j.Logger; -import org.apache.xmlrpc.XmlRpcException; - import com.cloud.agent.api.FenceAnswer; import com.cloud.agent.api.FenceCommand; import com.cloud.agent.api.to.VirtualMachineTO; @@ -39,13 +27,23 @@ import com.cloud.template.VirtualMachineTemplate.BootloaderType; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.script.Script; import com.xensource.xenapi.Connection; -import com.xensource.xenapi.Console; import com.xensource.xenapi.Host; import com.xensource.xenapi.Types; +import com.xensource.xenapi.Types.XenAPIException; import com.xensource.xenapi.VBD; import com.xensource.xenapi.VDI; import com.xensource.xenapi.VM; -import com.xensource.xenapi.Types.XenAPIException; +import org.apache.log4j.Logger; +import org.apache.xmlrpc.XmlRpcException; + +import javax.ejb.Local; +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; @Local(value=ServerResource.class) public class XenServer56FP1Resource extends XenServer56Resource { @@ -137,17 +135,17 @@ public class XenServer56FP1Resource extends XenServer56Resource { record.nameLabel = vmSpec.getName(); record.actionsAfterCrash = Types.OnCrashBehaviour.DESTROY; record.actionsAfterShutdown = Types.OnNormalExit.DESTROY; - record.memoryDynamicMax = vmSpec.getMaxRam(); record.memoryDynamicMin = vmSpec.getMinRam(); + record.memoryDynamicMax = vmSpec.getMaxRam(); Map hostParams = new HashMap(); hostParams = host.getLicenseParams(conn); if (hostParams.get("restrict_dmc").equalsIgnoreCase("false")) { - record.memoryStaticMax = 8589934592L; //8GB - record.memoryStaticMin = 134217728L; //128MB + record.memoryStaticMin = vmSpec.getMinRam(); + record.memoryStaticMax = vmSpec.getMaxRam(); } else { s_logger.warn("Host "+ _host.uuid + " does not support Dynamic Memory Control, so we cannot scale up the vm"); - record.memoryStaticMax = vmSpec.getMaxRam(); - record.memoryStaticMin = vmSpec.getMinRam(); + record.memoryStaticMin = 134217728L; //128MB + record.memoryStaticMax = 8589934592L; //8GB } if (guestOsTypeName.toLowerCase().contains("windows")) { From 85d54cd1c088997dd08f0328984bee1a55703636 Mon Sep 17 00:00:00 2001 From: Prasanna Santhanam Date: Sun, 19 May 2013 16:12:31 +0530 Subject: [PATCH 020/108] CLOUDSTACK-2554: Incorrect compute of minmemory and cpu 23e54bb0 introduced multiple hypervisors support for cpu and memory overcommit. Here the HypervisorGuru base which determines the min, max range for the memory for all hypervisors computes the minCpu using the MemoryOverCommit ratio and minMemory using the CpuOverCommit ratio. Minor typo/logic issue but massive damage across all HV if enabled ;) Signed-off-by: Prasanna Santhanam --- .../hypervisor/xen/resource/CitrixResourceBase.java | 5 +---- .../hypervisor/xen/resource/XcpServerResource.java | 11 +++++++---- .../src/com/cloud/hypervisor/HypervisorGuruBase.java | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java index 78bca32c92e..19444cccfbc 100644 --- a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java +++ b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java @@ -3520,10 +3520,7 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe * @throws XenAPIException */ protected void setMemory(Connection conn, VM vm, long minMemsize, long maxMemsize) throws XmlRpcException, XenAPIException { - vm.setMemoryStaticMin(conn, mem_128m); - vm.setMemoryDynamicMin(conn, minMemsize); - vm.setMemoryDynamicMax(conn, maxMemsize); - vm.setMemoryStaticMax(conn, maxMemsize); + vm.setMemoryLimits(conn, mem_128m, maxMemsize, minMemsize, maxMemsize); } protected void waitForTask(Connection c, Task task, long pollInterval, long timeout) throws XenAPIException, XmlRpcException { diff --git a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XcpServerResource.java b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XcpServerResource.java index 7df4c46a63d..bf0a4089713 100644 --- a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XcpServerResource.java +++ b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XcpServerResource.java @@ -140,9 +140,12 @@ public class XcpServerResource extends CitrixResourceBase { */ @Override protected void setMemory(Connection conn, VM vm, long minMemsize, long maxMemsize) throws XmlRpcException, XenAPIException { - vm.setMemoryStaticMin(conn, mem_32m); - vm.setMemoryDynamicMin(conn, minMemsize); - vm.setMemoryDynamicMax(conn, maxMemsize); - vm.setMemoryStaticMax(conn, maxMemsize); + //setMemoryLimits(staticMin, staticMax, dynamicMin, dynamicMax) + if (s_logger.isDebugEnabled()) { + s_logger.debug("Memory Limits for VM [" + vm.getNameLabel(conn) + + "[staticMin:" + mem_32m + ", staticMax:" + maxMemsize + + ", dynamicMin: " + minMemsize + ", dynamicMax:" + maxMemsize+"]]"); + } + vm.setMemoryLimits(conn, mem_32m, maxMemsize, minMemsize, maxMemsize); } } diff --git a/server/src/com/cloud/hypervisor/HypervisorGuruBase.java b/server/src/com/cloud/hypervisor/HypervisorGuruBase.java index ca1644a4281..ea4fcc1e5bf 100644 --- a/server/src/com/cloud/hypervisor/HypervisorGuruBase.java +++ b/server/src/com/cloud/hypervisor/HypervisorGuruBase.java @@ -86,9 +86,9 @@ public abstract class HypervisorGuruBase extends AdapterBase implements Hypervis ServiceOffering offering = vmProfile.getServiceOffering(); VirtualMachine vm = vmProfile.getVirtualMachine(); - Long minMemory = (long) (offering.getRamSize()/vmProfile.getCpuOvercommitRatio()); - int minspeed= (int)(offering.getSpeed()/vmProfile.getMemoryOvercommitRatio()); - int maxspeed = (offering.getSpeed()); + Long minMemory = (long) (offering.getRamSize() / vmProfile.getMemoryOvercommitRatio()); + int minspeed = (int) (offering.getSpeed() / vmProfile.getCpuOvercommitRatio()); + int maxspeed = (offering.getSpeed()); VirtualMachineTO to = new VirtualMachineTO(vm.getId(), vm.getInstanceName(), vm.getType(), offering.getCpu(), minspeed, maxspeed, minMemory * 1024l * 1024l, offering.getRamSize() * 1024l * 1024l, null, null, vm.isHaEnabled(), vm.limitCpuUse(), vm.getVncPassword()); to.setBootArgs(vmProfile.getBootArgs()); From 63e92a4ea6766212d4450cde3cf205d661a6419d Mon Sep 17 00:00:00 2001 From: Isaac Chiang Date: Mon, 20 May 2013 08:54:13 +0800 Subject: [PATCH 021/108] CLOUDSTACK-1871 : domainId parameter to uploadVolume not working 1. Remove duplicated lines for setting domainId. 2. Set domainId with owner's domain if the owner is specified. --- server/src/com/cloud/storage/VolumeManagerImpl.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/com/cloud/storage/VolumeManagerImpl.java b/server/src/com/cloud/storage/VolumeManagerImpl.java index 2f4b2c8d8a6..55e20cf0273 100644 --- a/server/src/com/cloud/storage/VolumeManagerImpl.java +++ b/server/src/com/cloud/storage/VolumeManagerImpl.java @@ -708,19 +708,19 @@ public class VolumeManagerImpl extends ManagerBase implements VolumeManager { VolumeVO volume = new VolumeVO(volumeName, zoneId, -1, -1, -1, new Long(-1), null, null, 0, Volume.Type.DATADISK); + Account owner = (caller.getId() == ownerId) ? caller : _accountMgr + .getActiveAccountById(ownerId); volume.setPoolId(null); volume.setDataCenterId(zoneId); volume.setPodId(null); volume.setAccountId(ownerId); - volume.setDomainId(((caller == null) ? Domain.ROOT_DOMAIN : caller - .getDomainId())); long diskOfferingId = _diskOfferingDao.findByUniqueName( "Cloud.com-Custom").getId(); volume.setDiskOfferingId(diskOfferingId); // volume.setSize(size); volume.setInstanceId(null); volume.setUpdated(new Date()); - volume.setDomainId((caller == null) ? Domain.ROOT_DOMAIN : caller + volume.setDomainId((owner == null) ? Domain.ROOT_DOMAIN : owner .getDomainId()); volume = _volsDao.persist(volume); From e7332b7c0447c8fc9bde157e4f639fde453790cd Mon Sep 17 00:00:00 2001 From: Sanjay Tripathi Date: Fri, 10 May 2013 12:10:56 +0530 Subject: [PATCH 022/108] CLOUDSTACK-1901 : API:MS: DeleteEvents deletes Archived events as well --- engine/schema/src/com/cloud/alert/dao/AlertDaoImpl.java | 8 ++++++++ engine/schema/src/com/cloud/event/dao/EventDaoImpl.java | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/engine/schema/src/com/cloud/alert/dao/AlertDaoImpl.java b/engine/schema/src/com/cloud/alert/dao/AlertDaoImpl.java index 4b9bc6a2988..01a560a129a 100755 --- a/engine/schema/src/com/cloud/alert/dao/AlertDaoImpl.java +++ b/engine/schema/src/com/cloud/alert/dao/AlertDaoImpl.java @@ -43,6 +43,7 @@ public class AlertDaoImpl extends GenericDaoBase implements Alert AlertSearchByIdsAndType.and("type", AlertSearchByIdsAndType.entity().getType(), Op.EQ); AlertSearchByIdsAndType.and("createdDateL", AlertSearchByIdsAndType.entity().getCreatedDate(), Op.LT); AlertSearchByIdsAndType.and("data_center_id", AlertSearchByIdsAndType.entity().getDataCenterId(), Op.EQ); + AlertSearchByIdsAndType.and("archived", AlertSearchByIdsAndType.entity().getArchived(), Op.EQ); AlertSearchByIdsAndType.done(); } @@ -53,6 +54,7 @@ public class AlertDaoImpl extends GenericDaoBase implements Alert sc.addAnd("type", SearchCriteria.Op.EQ, Short.valueOf(type)); sc.addAnd("dataCenterId", SearchCriteria.Op.EQ, Long.valueOf(dataCenterId)); + sc.addAnd("archived", SearchCriteria.Op.EQ, false); if (podId != null) { sc.addAnd("podId", SearchCriteria.Op.EQ, podId); } @@ -74,6 +76,7 @@ public class AlertDaoImpl extends GenericDaoBase implements Alert sc.addAnd("type", SearchCriteria.Op.EQ, Short.valueOf(type)); sc.addAnd("dataCenterId", SearchCriteria.Op.EQ, Long.valueOf(dataCenterId)); + sc.addAnd("archived", SearchCriteria.Op.EQ, false); if (podId != null) { sc.addAnd("podId", SearchCriteria.Op.EQ, podId); } @@ -101,6 +104,8 @@ public class AlertDaoImpl extends GenericDaoBase implements Alert if(olderThan != null) { sc.setParameters("createdDateL", olderThan); } + sc.setParameters("archived", false); + boolean result = true;; List alerts = listBy(sc); if (Ids != null && alerts.size() < Ids.size()) { @@ -135,6 +140,8 @@ public class AlertDaoImpl extends GenericDaoBase implements Alert if(olderThan != null) { sc.setParameters("createdDateL", olderThan); } + sc.setParameters("archived", false); + boolean result = true; List alerts = listBy(sc); if (ids != null && alerts.size() < ids.size()) { @@ -150,6 +157,7 @@ public class AlertDaoImpl extends GenericDaoBase implements Alert if (oldTime == null) return null; SearchCriteria sc = createSearchCriteria(); sc.addAnd("createDate", SearchCriteria.Op.LT, oldTime); + sc.addAnd("archived", SearchCriteria.Op.EQ, false); return listIncludingRemovedBy(sc, null); } diff --git a/engine/schema/src/com/cloud/event/dao/EventDaoImpl.java b/engine/schema/src/com/cloud/event/dao/EventDaoImpl.java index 0d3d38a0204..cefe1078dbd 100644 --- a/engine/schema/src/com/cloud/event/dao/EventDaoImpl.java +++ b/engine/schema/src/com/cloud/event/dao/EventDaoImpl.java @@ -44,6 +44,7 @@ public class EventDaoImpl extends GenericDaoBase implements Event CompletedEventSearch = createSearchBuilder(); CompletedEventSearch.and("state",CompletedEventSearch.entity().getState(),SearchCriteria.Op.EQ); CompletedEventSearch.and("startId", CompletedEventSearch.entity().getStartId(), SearchCriteria.Op.EQ); + CompletedEventSearch.and("archived", CompletedEventSearch.entity().getArchived(), Op.EQ); CompletedEventSearch.done(); ToArchiveOrDeleteEventSearch = createSearchBuilder(); @@ -51,6 +52,7 @@ public class EventDaoImpl extends GenericDaoBase implements Event ToArchiveOrDeleteEventSearch.and("type", ToArchiveOrDeleteEventSearch.entity().getType(), Op.EQ); ToArchiveOrDeleteEventSearch.and("accountIds", ToArchiveOrDeleteEventSearch.entity().getAccountId(), Op.IN); ToArchiveOrDeleteEventSearch.and("createDateL", ToArchiveOrDeleteEventSearch.entity().getCreateDate(), Op.LT); + ToArchiveOrDeleteEventSearch.and("archived", ToArchiveOrDeleteEventSearch.entity().getArchived(), Op.EQ); ToArchiveOrDeleteEventSearch.done(); } @@ -64,6 +66,7 @@ public class EventDaoImpl extends GenericDaoBase implements Event if (oldTime == null) return null; SearchCriteria sc = createSearchCriteria(); sc.addAnd("createDate", SearchCriteria.Op.LT, oldTime); + sc.addAnd("archived", SearchCriteria.Op.EQ, false); return listIncludingRemovedBy(sc, null); } @@ -72,6 +75,7 @@ public class EventDaoImpl extends GenericDaoBase implements Event SearchCriteria sc = CompletedEventSearch.create(); sc.setParameters("state", State.Completed); sc.setParameters("startId", startId); + sc.setParameters("archived", false); return findOneIncludingRemovedBy(sc); } @@ -90,6 +94,7 @@ public class EventDaoImpl extends GenericDaoBase implements Event if (accountIds != null && !accountIds.isEmpty()) { sc.setParameters("accountIds", accountIds.toArray(new Object[accountIds.size()])); } + sc.setParameters("archived", false); return search(sc, null); } From a2fea4d4499a963a7bb5aed73b4ee03016684529 Mon Sep 17 00:00:00 2001 From: Sanjay Tripathi Date: Mon, 13 May 2013 10:54:52 +0530 Subject: [PATCH 023/108] CLOUDSTACK-2297 : Delete Account/Domain is not updating the resources usage of the parent domain --- .../configuration/dao/ResourceCountDao.java | 2 ++ .../configuration/dao/ResourceCountDaoImpl.java | 16 ++++++++++++++++ .../configuration/dao/ResourceLimitDao.java | 2 ++ .../configuration/dao/ResourceLimitDaoImpl.java | 14 ++++++++++++++ .../src/com/cloud/user/AccountManagerImpl.java | 17 +++++++++++++++++ .../src/com/cloud/user/DomainManagerImpl.java | 15 ++++++++++++--- 6 files changed, 63 insertions(+), 3 deletions(-) diff --git a/engine/schema/src/com/cloud/configuration/dao/ResourceCountDao.java b/engine/schema/src/com/cloud/configuration/dao/ResourceCountDao.java index 111bcb122d6..dc5a65c8ef7 100644 --- a/engine/schema/src/com/cloud/configuration/dao/ResourceCountDao.java +++ b/engine/schema/src/com/cloud/configuration/dao/ResourceCountDao.java @@ -55,4 +55,6 @@ public interface ResourceCountDao extends GenericDao { Set listAllRowsToUpdate(long ownerId, ResourceOwnerType ownerType, ResourceType type); Set listRowsToUpdateForDomain(long domainId, ResourceType type); + + long removeEntriesByOwner(long ownerId, ResourceOwnerType ownerType); } diff --git a/engine/schema/src/com/cloud/configuration/dao/ResourceCountDaoImpl.java b/engine/schema/src/com/cloud/configuration/dao/ResourceCountDaoImpl.java index 52bc746fd8e..cfd2137f479 100644 --- a/engine/schema/src/com/cloud/configuration/dao/ResourceCountDaoImpl.java +++ b/engine/schema/src/com/cloud/configuration/dao/ResourceCountDaoImpl.java @@ -219,4 +219,20 @@ public class ResourceCountDaoImpl extends GenericDaoBase return super.persist(resourceCountVO); } + + + @Override + public long removeEntriesByOwner(long ownerId, ResourceOwnerType ownerType) { + SearchCriteria sc = TypeSearch.create(); + + if (ownerType == ResourceOwnerType.Account) { + sc.setParameters("accountId", ownerId); + return remove(sc); + } else if (ownerType == ResourceOwnerType.Domain) { + sc.setParameters("domainId", ownerId); + return remove(sc); + } + return 0; + } + } \ No newline at end of file diff --git a/engine/schema/src/com/cloud/configuration/dao/ResourceLimitDao.java b/engine/schema/src/com/cloud/configuration/dao/ResourceLimitDao.java index 5fd79b37bc4..e47b38340c2 100644 --- a/engine/schema/src/com/cloud/configuration/dao/ResourceLimitDao.java +++ b/engine/schema/src/com/cloud/configuration/dao/ResourceLimitDao.java @@ -32,4 +32,6 @@ public interface ResourceLimitDao extends GenericDao { ResourceCount.ResourceType getLimitType(String type); ResourceLimitVO findByOwnerIdAndType(long ownerId, ResourceOwnerType ownerType, ResourceCount.ResourceType type); + + long removeEntriesByOwner(Long ownerId, ResourceOwnerType ownerType); } diff --git a/engine/schema/src/com/cloud/configuration/dao/ResourceLimitDaoImpl.java b/engine/schema/src/com/cloud/configuration/dao/ResourceLimitDaoImpl.java index d337070e921..bb67f6bbe21 100644 --- a/engine/schema/src/com/cloud/configuration/dao/ResourceLimitDaoImpl.java +++ b/engine/schema/src/com/cloud/configuration/dao/ResourceLimitDaoImpl.java @@ -97,4 +97,18 @@ public class ResourceLimitDaoImpl extends GenericDaoBase return null; } } + + @Override + public long removeEntriesByOwner(Long ownerId, ResourceOwnerType ownerType) { + SearchCriteria sc = IdTypeSearch.create(); + + if (ownerType == ResourceOwnerType.Account) { + sc.setParameters("accountId", ownerId); + return remove(sc); + } else if (ownerType == ResourceOwnerType.Domain) { + sc.setParameters("domainId", ownerId); + return remove(sc); + } + return 0; + } } diff --git a/server/src/com/cloud/user/AccountManagerImpl.java b/server/src/com/cloud/user/AccountManagerImpl.java index 4088f64f58b..aac8d19eb0e 100755 --- a/server/src/com/cloud/user/AccountManagerImpl.java +++ b/server/src/com/cloud/user/AccountManagerImpl.java @@ -54,9 +54,12 @@ import com.cloud.api.query.dao.UserAccountJoinDao; import com.cloud.api.query.vo.ControlledViewEntity; import com.cloud.configuration.Config; import com.cloud.configuration.ConfigurationManager; +import com.cloud.configuration.ResourceCountVO; import com.cloud.configuration.ResourceLimit; +import com.cloud.configuration.Resource.ResourceOwnerType; import com.cloud.configuration.dao.ConfigurationDao; import com.cloud.configuration.dao.ResourceCountDao; +import com.cloud.configuration.dao.ResourceLimitDao; import com.cloud.dc.DataCenterVO; import com.cloud.dc.dao.DataCenterDao; import com.cloud.dc.dao.DataCenterVnetDao; @@ -229,6 +232,10 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M private AccountGuestVlanMapDao _accountGuestVlanMapDao; @Inject private DataCenterVnetDao _dataCenterVnetDao; + @Inject + private ResourceLimitService _resourceLimitMgr; + @Inject + private ResourceLimitDao _resourceLimitDao; private List _userAuthenticators; List _userPasswordEncoders; @@ -714,6 +721,16 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M int vlansReleased = _accountGuestVlanMapDao.removeByAccountId(accountId); s_logger.info("deleteAccount: Released " + vlansReleased + " dedicated guest vlan ranges from account " + accountId); + // Update resource count for this account and for parent domains. + List resourceCounts = _resourceCountDao.listByOwnerId(accountId, ResourceOwnerType.Account); + for (ResourceCountVO resourceCount : resourceCounts) { + _resourceLimitMgr.decrementResourceCount(accountId, resourceCount.getType(), resourceCount.getCount()); + } + + // Delete resource count and resource limits entries set for this account (if there are any). + _resourceCountDao.removeEntriesByOwner(accountId, ResourceOwnerType.Account); + _resourceLimitDao.removeEntriesByOwner(accountId, ResourceOwnerType.Account); + return true; } catch (Exception ex) { s_logger.warn("Failed to cleanup account " + account + " due to ", ex); diff --git a/server/src/com/cloud/user/DomainManagerImpl.java b/server/src/com/cloud/user/DomainManagerImpl.java index dbcbe4ee431..c451041d951 100644 --- a/server/src/com/cloud/user/DomainManagerImpl.java +++ b/server/src/com/cloud/user/DomainManagerImpl.java @@ -16,11 +16,13 @@ // under the License. package com.cloud.user; -import java.util.*; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; import javax.ejb.Local; import javax.inject.Inject; -import javax.naming.ConfigurationException; import org.apache.cloudstack.api.command.admin.domain.ListDomainChildrenCmd; import org.apache.cloudstack.api.command.admin.domain.ListDomainsCmd; @@ -29,8 +31,10 @@ import org.apache.cloudstack.region.RegionManager; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; +import com.cloud.configuration.Resource.ResourceOwnerType; import com.cloud.configuration.ResourceLimit; import com.cloud.configuration.dao.ResourceCountDao; +import com.cloud.configuration.dao.ResourceLimitDao; import com.cloud.domain.Domain; import com.cloud.domain.DomainVO; import com.cloud.domain.dao.DomainDao; @@ -49,7 +53,6 @@ import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.dao.DiskOfferingDao; import com.cloud.user.dao.AccountDao; import com.cloud.utils.Pair; -import com.cloud.utils.component.Manager; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.db.DB; import com.cloud.utils.db.Filter; @@ -82,6 +85,8 @@ public class DomainManagerImpl extends ManagerBase implements DomainManager, Dom private ProjectManager _projectMgr; @Inject private RegionManager _regionMgr; + @Inject + private ResourceLimitDao _resourceLimitDao; @Override public Domain getDomain(long domainId) { @@ -329,6 +334,10 @@ public class DomainManagerImpl extends ManagerBase implements DomainManager, Dom List accountsForCleanup = _accountDao.findCleanupsForRemovedAccounts(domainId); if (accountsForCleanup.isEmpty()) { deleteDomainSuccess = _domainDao.remove(domainId); + + // Delete resource count and resource limits entries set for this domain (if there are any). + _resourceCountDao.removeEntriesByOwner(domainId, ResourceOwnerType.Domain); + _resourceLimitDao.removeEntriesByOwner(domainId, ResourceOwnerType.Domain); } else { s_logger.debug("Can't delete the domain yet because it has " + accountsForCleanup.size() + "accounts that need a cleanup"); } From 8986e16e5f9f59fdf222704ec9716b9b821bdea8 Mon Sep 17 00:00:00 2001 From: Likitha Shetty Date: Mon, 20 May 2013 14:51:20 +0530 Subject: [PATCH 024/108] CLOUDSTACK-2576. AWS API not returning values in CFSimpleXML Object format. PHP SDK calls the CFSimpleXML parser class to parse the response body into CFSimpleXML Object format. In AWSAPI added an XML declaration during serialization of Axis beans to XML output --- awsapi/src/com/cloud/bridge/service/EC2RestServlet.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/awsapi/src/com/cloud/bridge/service/EC2RestServlet.java b/awsapi/src/com/cloud/bridge/service/EC2RestServlet.java index 630e16fb5f3..6dd7a8cbb75 100644 --- a/awsapi/src/com/cloud/bridge/service/EC2RestServlet.java +++ b/awsapi/src/com/cloud/bridge/service/EC2RestServlet.java @@ -2043,8 +2043,9 @@ public class EC2RestServlet extends HttpServlet { throws ADBException, XMLStreamException, IOException { OutputStream os = response.getOutputStream(); response.setStatus(200); - response.setContentType("text/xml; charset=UTF-8"); + response.setContentType("text/xml"); XMLStreamWriter xmlWriter = xmlOutFactory.createXMLStreamWriter( os ); + xmlWriter.writeStartDocument("UTF-8","1.0"); MTOMAwareXMLSerializer MTOMWriter = new MTOMAwareXMLSerializer( xmlWriter ); MTOMWriter.setDefaultNamespace("http://ec2.amazonaws.com/doc/" + wsdlVersion + "/"); EC2Response.serialize( null, factory, MTOMWriter ); From 2778486c34427dbc4e66a7f5db0e3b2364cf9f11 Mon Sep 17 00:00:00 2001 From: suresh sadhu Date: Mon, 20 May 2013 14:48:06 +0530 Subject: [PATCH 025/108] CLOUDSTACK-2287: Tests for the LDAP system API tests for the LDAP config/remove commands Signed-off-by: Prasanna Santhanam --- test/integration/smoke/test_ldap.py | 365 ++++++++++++++++++++++++++++ 1 file changed, 365 insertions(+) create mode 100755 test/integration/smoke/test_ldap.py diff --git a/test/integration/smoke/test_ldap.py b/test/integration/smoke/test_ldap.py new file mode 100755 index 00000000000..1b933dbccf7 --- /dev/null +++ b/test/integration/smoke/test_ldap.py @@ -0,0 +1,365 @@ +# 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. + +""" P1 for LDAP Config +""" + + +#!/usr/bin/env python + +import marvin +from marvin import cloudstackTestCase +from marvin.cloudstackTestCase import * +import unittest +import hashlib +import random +from marvin.cloudstackAPI import * +from marvin.cloudstackAPI import login +from marvin.integration.lib.utils import * +from marvin.integration.lib.base import * +from marvin.integration.lib.common import * +from nose.plugins.attrib import attr +import urllib + + + +class Services: + """Test LDAP Configuration + """ + + def __init__(self): + self.services = { + "account": { + "email": "test@test.com", + "firstname": "test", + "lastname": "t", + "username": "test", + "password": "password", + }, + "ldapCon_1":#valid values&Query filter as email. + { + "ldapHostname": "10.147.38.163", + "port": "389", + "binddn": "CN=test,CN=Users,DC=hyd-qa,DC=com", + "bindpass": "aaaa_1111", + "queryfilter": "(&(mail=%e))", + "searchbase": "CN=Users,DC=hyd-qa,DC=com", + "ldapusername": "test", + "ldappasswd": "aaaa_1111" + }, + "ldapCon_2": ##valid values&Query filter as displayName. + { + "ldapHostname": "10.147.38.163", + "port": "389", + "binddn": "CN=test,CN=Users,DC=hyd-qa,DC=com", + "bindpass": "aaaa_1111", + "queryfilter": "(&(displayName=%u))", + "searchbase": "CN=Users,DC=hyd-qa,DC=com", + "ldapusername": "test", + "ldappasswd": "aaaa_1111" + }, + "ldapCon_3": #Configuration with missing parameters value(queryfilter) + { + "ldapHostname": "10.147.38.163", + "port": "389", + "binddn": "CN=test,CN=Users,DC=hyd-qa,DC=com", + "bindpass": "aaaa_1111", + "queryfilter": "", + "searchbase": "CN=Users,DC=hyd-qa,DC=com", + "ldapusername": "test", + "ldappasswd": "aaaa_1111" + }, + + "ldapCon_4": #invalid configuration-wrong query filter + { + "ldapHostname": "10.147.38.163", + "port": "389", + "binddn": "CN=test,CN=Users,DC=hyd-qa,DC=com", + "bindpass": "aaaa_1111", + "queryfilter": "(&(displayName=%p))", + "searchbase":"CN=Users,DC=hyd-qa,DC=com", + "ldapusername": "test", + "ldappasswd": "aaaa_1111" + }, + "ldapCon_5": #Configuration with invalid ldap credentials + { + "ldapHostname": "10.147.38.163", + "port": "389", + "binddn": "CN=test,CN=Users,DC=hyd-qa,DC=com", + "bindpass": "aaaa_1111", + "queryfilter": "(&(displayName=%u))", + "searchbase": "CN=Users,DC=hyd-qa,DC=com", + "ldapusername": "test", + "ldappasswd": "aaaa" + } + + + + } + + +class TestLdap(cloudstackTestCase): + """ + This test perform registering ldap configuration details in CS and create a user[ldap user] in CS + and validate user credentials against LDAP server:AD + """ + + @classmethod + def setUpClass(cls): + + cls.api_client = super( + TestLdap, + cls + ).getClsTestClient().getApiClient() + cls.services = Services().services + cls.account = cls.services["account"] + cls._cleanup = [] + + + + @classmethod + def tearDownClass(cls): + try: + #Cleanup resources used + #print "tear down class" + cleanup_resources(cls.api_client, cls._cleanup) + + except Exception as tde: + raise Exception("Warning: Exception during cleanup : %s" % tde) + return + + def setUp(self): + + self.apiclient = self.testClient.getApiClient() + + self.acct = createAccount.createAccountCmd() + self.acct.accounttype = 0 #We need a regular user. admins have accounttype=1 + self.acct.firstname = self.services["account"]["firstname"] + self.acct.lastname = self.services["account"]["lastname"] + self.acct.password = self.services["account"]["password"] + self.acct.username = self.services["account"]["username"] + self.acct.email = self.services["account"]["email"] + self.acct.account = self.services["account"]["username"] + self.acct.domainid = 1 + # mapping ldap user by creating same user in cloudstack + + self.acctRes = self.apiclient.createAccount(self.acct) + + + return + + def tearDown(self): + + try: + #Clean up, terminate the created accounts, domains etc + + deleteAcct = deleteAccount.deleteAccountCmd() + deleteAcct.id = self.acctRes.id + + acct_name=self.acctRes.name + + self.apiclient.deleteAccount(deleteAcct) + + self.debug("Deleted the the following account name %s:" %acct_name) + #delete only if ldapconfig registered in CS + if(self.ldapconfRes): + deleteldapconfg=ldapRemove.ldapRemoveCmd() + res=self.apiclient.ldapRemove(deleteldapconfg) + + + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + + def test_01_configLDAP(self): + ''' + This test is to verify ldapConfig API with valid values.(i.e query fileter as email) + ''' + # 1. This test covers ldapConfig & login API with valid ldap credentials.. + # require ldap configuration:ldapCon_1 + + self.debug("start test") + + self.ldapconfRes=self._testldapConfig(self.services["ldapCon_1"]) + + if(self.ldapconfRes==1): + + + self.debug("configure ldap successful") + + #validating the user credentials with ldap Server + loginRes = self.chkLogin(self.services["ldapCon_1"]["ldapusername"], self.services["ldapCon_1"]["ldappasswd"]) + self.assertEquals(loginRes,1,"ldap Authentication failed") + + else: + + self.debug("LDAP Configuration failed with exception") + + self.assertEquals(self.ldapconfRes,1,"ldapConfig API failed") + + + self.debug("end test") + + + def test_02_configLDAP(self): + ''' + This test is to verify ldapConfig API with valid values.(i.e query fileter as displayName) + ''' + + # 1. This test covers ldapConfig & login API with valid ldap credentials. + # 2. require ldap configuration:ldapCon_2 + + self.debug("start test") + self.ldapconfRes=self._testldapConfig(self.services["ldapCon_2"]) + self.assertEquals(self.ldapconfRes,1,"ldapConfig API failed") + if(self.ldapconfRes==1): + self.debug("configure ldap successful") + #validating the user credentials with ldap Server + loginRes = self.chkLogin(self.services["ldapCon_2"]["ldapusername"], self.services["ldapCon_2"]["ldappasswd"]) + self.assertEquals(loginRes,1,"ldap Authentication failed") + else: + self.debug("LDAP Configuration failed with exception") + self.debug("end test") + + + def test_03_configLDAP(self): + + ''' + This test is to verify ldapConfig API with missing config parameters value(i.queryfilter) + ''' + + # 1. Issue ldapConfig API with no ldap config parameter value and check behavior + # 2. require ldap configuration:ldapCon_3 + + self.debug("start test...") + self.ldapconfRes=self._testldapConfig(self.services["ldapCon_3"]) + self.assertEquals(self.ldapconfRes,0,"LDAP configuration successful with invalid value.API failed") + self.debug("end test") + + def test_04_configLDAP(self): + ''' + This test is to verify ldapConfig API with invalid configuration values(by passing wrong query filter) + ''' + # 1. calling ldapConfig API with invalid query filter value and check behavior + # 2. require ldap configuration:ldapCon_4 + + self.debug("start test...") + self.ldapconfRes=self._testldapConfig(self.services["ldapCon_4"]) + self.assertEquals(self.ldapconfRes,0,"API failed") + + + + def test_05_configLDAP(self): + + ''' + This test is to verify login API functionality by passing wrong ldap credentials + ''' + # 1.This script first configure the ldap and validates the user credentials using login API + # 2. require ldap configuration:ldapCon_5 + + + self.debug("start test") + self.ldapconfRes=self._testldapConfig(self.services["ldapCon_5"]) + self.assertEquals(self.ldapconfRes,1,"API failed") + #validating the cloudstack user credentials with ldap Server + loginRes = self.chkLogin(self.services["ldapCon_5"]["ldapusername"], self.services["ldapCon_5"]["ldappasswd"]) + self.assertNotEqual(loginRes,1,"login API failed") + self.debug("end test") + + def test_06_removeLDAP(self): + ''' + This test is to verify ldapRemove API functionality + ''' + # 1. This script fist configures ldap and removes the configured ldap values + # 2. require ldap configuration:ldapCon_1 + + + self.debug("start test") + self.ldapconfRes=self._testldapConfig(self.services["ldapCon_1"]) + if(self.ldapconfRes==1): + self.debug("ldap configured successfully") + deleteldapconfg=ldapRemove.ldapRemoveCmd() + res=self.apiclient.ldapRemove(deleteldapconfg) + self.debug("ldap removed successfully") + self.ldapconfRes=0 + else: + + self.debug("LDAP Configuration failed with exception") + self.assertEquals(self.ldapconfRes,0,"ldapconfig API failed") + self.debug("end test") + + def _testldapConfig(self,ldapSrvD): + + """ + + :param ldapSrvD + + + """ + #This Method takes dictionary as parameter, + # reads the ldap configuration values from the passed dictionary and + # register the ldapconfig detail in cloudstack + # & return true or false based on ldapconfig API response + + self.debug("start ldapconfig test") + #creating the ldapconfig cmd object + lpconfig = ldapConfig.ldapConfigCmd() + #Config the ldap server by assigning the ldapconfig dict variable values to ldapConfig object + lpconfig.hostname = ldapSrvD["ldapHostname"] + lpconfig.port = ldapSrvD["port"] + lpconfig.binddn = ldapSrvD["binddn"] + lpconfig.bindpass = ldapSrvD["bindpass"] + lpconfig.searchbase = ldapSrvD["searchbase"] + lpconfig.queryfilter = ldapSrvD["queryfilter"] + + #end of assigning the variables + + #calling the ldapconfig Api + self.debug("calling ldapconfig API") + try: + lpconfig1 = self.apiclient.ldapConfig(lpconfig) + self.debug("ldapconfig API succesfful") + return 1 + except Exception, e: + self.debug("ldapconfig API failed %s" %e) + return 0 + + def chkLogin(self, username, password): + """ + + :param username: + :param password: + + """ + self.debug("login test") + + try: + login1 = login.loginCmd() + login1.username = username + login1.password = password + loginRes = self.apiclient.login(login1) + self.debug("login response %s" % loginRes) + if loginRes is None: + self.debug("login not successful") + else: + self.debug("login successful") + return 1 + + except Exception, p: + self.debug("login operation failed %s" %p) + self.debug("end of Login") From 8da7ec05f1c7dae0aab827ae0568e120111c6579 Mon Sep 17 00:00:00 2001 From: Prasanna Santhanam Date: Mon, 20 May 2013 17:48:07 +0530 Subject: [PATCH 026/108] CLOUDSTACK-2085: DMC enable guest scaling When dmc is enabled allow cloudstack to scale the VM between dynamic ranges. This requires a further commit where there is enough memory available between dynamic-max and static-max for the VM to scale under memory pressure. See the TODO in Xenserver56FP1Resource.java Signed-off-by: Prasanna Santhanam --- .../xen/resource/CitrixResourceBase.java | 11 +++ .../xen/resource/XcpServerResource.java | 9 ++- .../xen/resource/XenServer56FP1Resource.java | 71 ++++++++++++------- 3 files changed, 64 insertions(+), 27 deletions(-) diff --git a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java index 19444cccfbc..cd4977312ae 100644 --- a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java +++ b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java @@ -3523,6 +3523,17 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe vm.setMemoryLimits(conn, mem_128m, maxMemsize, minMemsize, maxMemsize); } + /** + * When Dynamic Memory Control (DMC) is enabled - + * xen allows scaling the guest memory while the guest is running + * + * By default this is disallowed, override the specific xen resource + * if this is enabled + */ + protected boolean isDmcEnabled(Connection conn, Host host) throws XenAPIException, XmlRpcException { + return false; + } + protected void waitForTask(Connection c, Task task, long pollInterval, long timeout) throws XenAPIException, XmlRpcException { long beginTime = System.currentTimeMillis(); while (task.getStatus(c) == Types.TaskStatusType.PENDING) { diff --git a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XcpServerResource.java b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XcpServerResource.java index bf0a4089713..67e37d52a4a 100644 --- a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XcpServerResource.java +++ b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XcpServerResource.java @@ -24,6 +24,7 @@ import com.cloud.resource.ServerResource; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.script.Script; import com.xensource.xenapi.Connection; +import com.xensource.xenapi.Host; import com.xensource.xenapi.Types.XenAPIException; import com.xensource.xenapi.VM; import org.apache.log4j.Logger; @@ -38,7 +39,6 @@ import java.util.List; public class XcpServerResource extends CitrixResourceBase { private final static Logger s_logger = Logger.getLogger(XcpServerResource.class); private static final long mem_32m = 33554432L; - private String version; public XcpServerResource() { @@ -148,4 +148,11 @@ public class XcpServerResource extends CitrixResourceBase { } vm.setMemoryLimits(conn, mem_32m, maxMemsize, minMemsize, maxMemsize); } + + @Override + protected boolean isDmcEnabled(Connection conn, Host host) { + //Dynamic Memory Control (DMC) is a technology provided by Xen Cloud Platform (XCP), starting from the 0.5 release + //For the supported XCPs dmc is default enabled, XCP 1.0.0, 1.1.0, 1.4.x, 1.5 beta, 1.6.x; + return true; + } } diff --git a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XenServer56FP1Resource.java b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XenServer56FP1Resource.java index ad412c1e3d0..78ad2361b07 100644 --- a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XenServer56FP1Resource.java +++ b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XenServer56FP1Resource.java @@ -47,6 +47,7 @@ import java.util.Set; @Local(value=ServerResource.class) public class XenServer56FP1Resource extends XenServer56Resource { + private static final long mem_128m = 134217728L; private static final Logger s_logger = Logger.getLogger(XenServer56FP1Resource.class); public XenServer56FP1Resource() { @@ -126,41 +127,46 @@ public class XenServer56FP1Resource extends XenServer56Resource { assert templates.size() == 1 : "Should only have 1 template but found " + templates.size(); VM template = templates.iterator().next(); - VM.Record record = template.getRecord(conn); - record.affinity = host; - record.otherConfig.remove("disks"); - record.otherConfig.remove("default_template"); - record.otherConfig.remove("mac_seed"); - record.isATemplate = false; - record.nameLabel = vmSpec.getName(); - record.actionsAfterCrash = Types.OnCrashBehaviour.DESTROY; - record.actionsAfterShutdown = Types.OnNormalExit.DESTROY; - record.memoryDynamicMin = vmSpec.getMinRam(); - record.memoryDynamicMax = vmSpec.getMaxRam(); - Map hostParams = new HashMap(); - hostParams = host.getLicenseParams(conn); - if (hostParams.get("restrict_dmc").equalsIgnoreCase("false")) { - record.memoryStaticMin = vmSpec.getMinRam(); - record.memoryStaticMax = vmSpec.getMaxRam(); + VM.Record vmr = template.getRecord(conn); + vmr.affinity = host; + vmr.otherConfig.remove("disks"); + vmr.otherConfig.remove("default_template"); + vmr.otherConfig.remove("mac_seed"); + vmr.isATemplate = false; + vmr.nameLabel = vmSpec.getName(); + vmr.actionsAfterCrash = Types.OnCrashBehaviour.DESTROY; + vmr.actionsAfterShutdown = Types.OnNormalExit.DESTROY; + + if (isDmcEnabled(conn, host)) { + //scaling is allowed + vmr.memoryStaticMin = mem_128m; //128MB + //TODO: Remove hardcoded 8GB and assign proportionate to ServiceOffering and mem overcommit ratio + vmr.memoryStaticMax = 8589934592L; //8GB + vmr.memoryDynamicMin = vmSpec.getMinRam(); + vmr.memoryDynamicMax = vmSpec.getMaxRam(); } else { - s_logger.warn("Host "+ _host.uuid + " does not support Dynamic Memory Control, so we cannot scale up the vm"); - record.memoryStaticMin = 134217728L; //128MB - record.memoryStaticMax = 8589934592L; //8GB + //scaling disallowed, set static memory target + if (s_logger.isDebugEnabled()) { + s_logger.warn("Host "+ host.getHostname(conn) +" does not support dynamic scaling"); + } + vmr.memoryStaticMin = vmSpec.getMinRam(); + vmr.memoryStaticMax = vmSpec.getMaxRam(); + vmr.memoryDynamicMin = vmSpec.getMinRam(); + vmr.memoryDynamicMax = vmSpec.getMaxRam(); } if (guestOsTypeName.toLowerCase().contains("windows")) { - record.VCPUsMax = (long) vmSpec.getCpus(); + vmr.VCPUsMax = (long) vmSpec.getCpus(); } else { - record.VCPUsMax = 32L; + vmr.VCPUsMax = 32L; } - record.VCPUsAtStartup = (long) vmSpec.getCpus(); - record.consoles.clear(); + vmr.VCPUsAtStartup = (long) vmSpec.getCpus(); + vmr.consoles.clear(); - VM vm = VM.create(conn, record); - VM.Record vmr = vm.getRecord(conn); + VM vm = VM.create(conn, vmr); if (s_logger.isDebugEnabled()) { - s_logger.debug("Created VM " + vmr.uuid + " for " + vmSpec.getName()); + s_logger.debug("Created VM " + vm.getUuid(conn) + " for " + vmSpec.getName()); } Map vcpuParams = new HashMap(); @@ -227,4 +233,17 @@ public class XenServer56FP1Resource extends XenServer56Resource { return vm; } + /** + * When Dynamic Memory Control (DMC) is enabled - + * xen allows scaling the guest memory while the guest is running + * + * This is determined by the 'restrict_dmc' option on the host. + * When false, scaling is allowed hence DMC is enabled + */ + @Override + protected boolean isDmcEnabled(Connection conn, Host host) throws XenAPIException, XmlRpcException { + Map hostParams = new HashMap(); + hostParams = host.getLicenseParams(conn); + return hostParams.get("restrict_dmc").equalsIgnoreCase("false"); + } } From 92e635228731fb72461e757f523cd20fefcd370b Mon Sep 17 00:00:00 2001 From: Prasanna Santhanam Date: Mon, 20 May 2013 17:50:32 +0530 Subject: [PATCH 027/108] Detect the second zone for copyImage operation When copying templates and ISOs detect that a second zone is available. Else skip the test. Signed-off-by: Prasanna Santhanam --- test/integration/smoke/test_iso.py | 10 ++++++---- test/integration/smoke/test_templates.py | 11 +++++++---- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/test/integration/smoke/test_iso.py b/test/integration/smoke/test_iso.py index ad4a8f280d1..c645d3b055d 100644 --- a/test/integration/smoke/test_iso.py +++ b/test/integration/smoke/test_iso.py @@ -202,11 +202,9 @@ class TestISO(cloudstackTestCase): cls.services["sourcezoneid"] = cls.zone.id #populate second zone id for iso copy cmd = listZones.listZonesCmd() - zones = cls.api_client.listZones(cmd) - if not isinstance(zones, list): + cls.zones = cls.api_client.listZones(cmd) + if not isinstance(cls.zones, list): raise Exception("Failed to find zones.") - if len(zones) >= 2: - cls.services["destzoneid"] = zones[1].id #Create an account, ISOs etc. cls.account = Account.create( @@ -484,6 +482,10 @@ class TestISO(cloudstackTestCase): #Validate the following #1. copy ISO should be successful and secondary storage # should contain new copied ISO. + if len(self.zones) <= 1: + self.skipTest("Not enough zones available to perform copy template") + + self.services["destzoneid"] = filter(lambda z: z.id != self.zone.id, self.zones)[0] self.debug("Copy ISO from %s to %s" % ( self.zone.id, diff --git a/test/integration/smoke/test_templates.py b/test/integration/smoke/test_templates.py index 382f56f8980..8b83f5e6e43 100644 --- a/test/integration/smoke/test_templates.py +++ b/test/integration/smoke/test_templates.py @@ -295,11 +295,9 @@ class TestTemplates(cloudstackTestCase): cls.services['mode'] = cls.zone.networktype #populate second zone id for iso copy cmd = listZones.listZonesCmd() - zones = cls.api_client.listZones(cmd) - if not isinstance(zones, list): + cls.zones = cls.api_client.listZones(cmd) + if not isinstance(cls.zones, list): raise Exception("Failed to find zones.") - if len(zones) >= 2: - cls.services["destzoneid"] = zones[1].id cls.disk_offering = DiskOffering.create( cls.api_client, @@ -664,6 +662,11 @@ class TestTemplates(cloudstackTestCase): # 1. copy template should be successful and # secondary storage should contain new copied template. + if len(self.zones) <= 1: + self.skipTest("Not enough zones available to perform copy template") + + self.services["destzoneid"] = filter(lambda z: z.id != self.services["sourcezoneid"], self.zones)[0] + self.debug("Copy template from Zone: %s to %s" % ( self.services["sourcezoneid"], self.services["destzoneid"] From e1ad36bccb70a9f73de1d06b408043a93876195c Mon Sep 17 00:00:00 2001 From: Prasanna Santhanam Date: Mon, 20 May 2013 17:51:18 +0530 Subject: [PATCH 028/108] migrate only when hosts are available Identify the hosts that are suitable for migration before proceeding with migrateVM Signed-off-by: Prasanna Santhanam --- test/integration/smoke/test_vm_life_cycle.py | 131 ++++++++++--------- 1 file changed, 66 insertions(+), 65 deletions(-) diff --git a/test/integration/smoke/test_vm_life_cycle.py b/test/integration/smoke/test_vm_life_cycle.py index 21c26357ab2..3f7c17b9ed6 100644 --- a/test/integration/smoke/test_vm_life_cycle.py +++ b/test/integration/smoke/test_vm_life_cycle.py @@ -135,35 +135,34 @@ class TestDeployVM(cloudstackTestCase): cls.apiclient = super(TestDeployVM, cls).getClsTestClient().getApiClient() # Get Zone, Domain and templates domain = get_domain(cls.apiclient, cls.services) - zone = get_zone(cls.apiclient, cls.services) - cls.services['mode'] = zone.networktype + cls.zone = get_zone(cls.apiclient, cls.services) + cls.services['mode'] = cls.zone.networktype #If local storage is enabled, alter the offerings to use localstorage #this step is needed for devcloud - if zone.localstorageenabled == True: + if cls.zone.localstorageenabled == True: cls.services["service_offerings"]["tiny"]["storagetype"] = 'local' cls.services["service_offerings"]["small"]["storagetype"] = 'local' cls.services["service_offerings"]["medium"]["storagetype"] = 'local' template = get_template( cls.apiclient, - zone.id, + cls.zone.id, cls.services["ostype"] ) # Set Zones and disk offerings - cls.services["small"]["zoneid"] = zone.id + cls.services["small"]["zoneid"] = cls.zone.id cls.services["small"]["template"] = template.id - cls.services["medium"]["zoneid"] = zone.id + cls.services["medium"]["zoneid"] = cls.zone.id cls.services["medium"]["template"] = template.id - cls.services["iso"]["zoneid"] = zone.id + cls.services["iso"]["zoneid"] = cls.zone.id cls.account = Account.create( cls.apiclient, cls.services["account"], domainid=domain.id ) - cls.debug(str("============" )) cls.debug(cls.account.id) cls.service_offering = ServiceOffering.create( @@ -811,68 +810,76 @@ class TestVMLifeCycle(cloudstackTestCase): """Test migrate VM """ # Validate the following - # 1. Should be able to login to the VM. - # 2. listVM command should return this VM.State of this VM - # should be "Running" and the host should be the host - # to which the VM was migrated to + # 1. Environment has enough hosts for migration + # 2. DeployVM on suitable host (with another host in the cluster) + # 3. Migrate the VM and assert migration successful hosts = Host.list( - self.apiclient, - zoneid=self.medium_virtual_machine.zoneid, - type='Routing' - ) - + self.apiclient, + zoneid=self.zone.id, + type='Routing' + ) self.assertEqual( - isinstance(hosts, list), - True, - "Check the number of hosts in the zone" - ) + isinstance(hosts, list), + True, + "Check the number of hosts in the zone" + ) self.assertGreaterEqual( - len(hosts), - 2, - "Atleast 2 hosts should be present in a zone for VM migration" - ) - # Remove the host of current VM from the hosts list - hosts[:] = [host for host in hosts if host.id != self.medium_virtual_machine.hostid] + len(hosts), + 2, + "Atleast 2 hosts should be present for VM migration" + ) - host = hosts[0] + #identify suitable host + clusters = [h.clusterid for h in hosts] + #find hosts withe same clusterid + clusters = [cluster for index, cluster in enumerate(clusters) if clusters.count(cluster) > 1] + + if len(clusters) <= 1: + self.skipTest("Migration needs a cluster with at least two hosts") + + suitable_hosts = [host for host in hosts if host.clusterid == clusters[0]] + target_host = suitable_hosts[0] + migrate_host = suitable_hosts[1] + + #deploy VM on target host + self.vm_to_migrate = VirtualMachine.create( + self.api_client, + self.services["small"], + accountid=self.account.name, + domainid=self.account.domainid, + serviceofferingid=self.small_offering.id, + mode=self.services["mode"], + hostid=target_host.id + ) self.debug("Migrating VM-ID: %s to Host: %s" % ( - self.medium_virtual_machine.id, - host.id + self.vm_to_migrate.id, + migrate_host.id )) - cmd = migrateVirtualMachine.migrateVirtualMachineCmd() - cmd.hostid = host.id - cmd.virtualmachineid = self.medium_virtual_machine.id - self.apiclient.migrateVirtualMachine(cmd) + self.vm_to_migrate.migrate(self.api_client, migrate_host.id) list_vm_response = list_virtual_machines( self.apiclient, - id=self.medium_virtual_machine.id + id=self.vm_to_migrate.id ) - self.assertEqual( - isinstance(list_vm_response, list), - True, - "Check list response returns a valid list" - ) - self.assertNotEqual( list_vm_response, None, - "Check virtual machine is listVirtualMachines" + "Check virtual machine is listed" ) vm_response = list_vm_response[0] self.assertEqual( vm_response.id, - self.medium_virtual_machine.id, + self.vm_to_migrate.id, "Check virtual machine ID of migrated VM" ) self.assertEqual( vm_response.hostid, - host.id, + migrate_host.id, "Check destination hostID of migrated VM" ) return @@ -964,29 +971,25 @@ class TestVMLifeCycle(cloudstackTestCase): try: ssh_client = self.virtual_machine.get_ssh_client() - - cmds = [ - "mkdir -p %s" % self.services["mount_dir"], - "mount -rt iso9660 %s %s" \ - % ( - self.services["diskdevice"], - self.services["mount_dir"] - ), - ] - - for c in cmds: - res = ssh_client.execute(c) - - self.assertEqual(res, [], "Check mount is successful or not") - - c = "fdisk -l|grep %s|head -1" % self.services["diskdevice"] - res = ssh_client.execute(c) - #Disk /dev/xvdd: 4393 MB, 4393723904 bytes - except Exception as e: self.fail("SSH failed for virtual machine: %s - %s" % (self.virtual_machine.ipaddress, e)) + cmds = [ + "mkdir -p %s" % self.services["mount_dir"], + "mount -rt iso9660 %s %s" \ + % ( + self.services["diskdevice"], + self.services["mount_dir"] + ), + ] + for c in cmds: + res = ssh_client.execute(c) + self.assertEqual(res, [], "Check mount is successful or not") + c = "fdisk -l|grep %s|head -1" % self.services["diskdevice"] + res = ssh_client.execute(c) + #Disk /dev/xvdd: 4393 MB, 4393723904 bytes + # Res may contain more than one strings depending on environment # Split strings to form new list which is used for assertion on ISO size result = [] @@ -1015,7 +1018,6 @@ class TestVMLifeCycle(cloudstackTestCase): #Unmount ISO command = "umount %s" % self.services["mount_dir"] ssh_client.execute(command) - except Exception as e: self.fail("SSH failed for virtual machine: %s - %s" % (self.virtual_machine.ipaddress, e)) @@ -1027,7 +1029,6 @@ class TestVMLifeCycle(cloudstackTestCase): try: res = ssh_client.execute(c) - except Exception as e: self.fail("SSH failed for virtual machine: %s - %s" % (self.virtual_machine.ipaddress, e)) From 32e7ba23a3bd514f7b4026b522913def406383c1 Mon Sep 17 00:00:00 2001 From: Prasanna Santhanam Date: Mon, 20 May 2013 17:59:19 +0530 Subject: [PATCH 029/108] LDAP moved to regression suite. LDAP requires a Directory server which isn't available by default in most environments. Moving this to the regression suite so it can be run on demand. Signed-off-by: Prasanna Santhanam --- test/integration/{smoke => component}/test_ldap.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/integration/{smoke => component}/test_ldap.py (100%) diff --git a/test/integration/smoke/test_ldap.py b/test/integration/component/test_ldap.py similarity index 100% rename from test/integration/smoke/test_ldap.py rename to test/integration/component/test_ldap.py From 612afbd17941ee840b1bba3e5951f89cafeea243 Mon Sep 17 00:00:00 2001 From: Kishan Kavala Date: Mon, 20 May 2013 17:58:14 +0530 Subject: [PATCH 030/108] CLOUDSTACK-2553: Made ACL item action case-insensitive --- server/src/com/cloud/network/vpc/NetworkACLServiceImpl.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/server/src/com/cloud/network/vpc/NetworkACLServiceImpl.java b/server/src/com/cloud/network/vpc/NetworkACLServiceImpl.java index 4d5d98192fa..2b02a888de9 100644 --- a/server/src/com/cloud/network/vpc/NetworkACLServiceImpl.java +++ b/server/src/com/cloud/network/vpc/NetworkACLServiceImpl.java @@ -342,9 +342,7 @@ public class NetworkACLServiceImpl extends ManagerBase implements NetworkACLServ //Check ofr valid action Allow/Deny if(action != null){ - try { - NetworkACLItem.Action.valueOf(action); - } catch (IllegalArgumentException ex) { + if(!("Allow".equalsIgnoreCase(action) || "Deny".equalsIgnoreCase(action))){ throw new InvalidParameterValueException("Invalid action. Allowed actions are Allow and Deny"); } } From f5c8e386e5881c1d7aad1f85922752621bb96c1a Mon Sep 17 00:00:00 2001 From: Devdeep Singh Date: Mon, 20 May 2013 18:26:50 +0530 Subject: [PATCH 031/108] CLOUDSTACK-2379: Fix for issue destroy vm causing NPE. Made the following changes. 1. Made a fix to make sure a null object is added to the exception. 2. Also fixed the marvin test cases for the feature. Account cleanup will remove the vms deployed for the account. There is no need to explicitly delete the vms for the account. 3. Fixed the assertion checks for the vm created for an account. If there are multiple vms for an account, the test script needs to compare the ids with the correct instance. --- server/src/com/cloud/vm/UserVmManagerImpl.java | 1 - test/integration/smoke/test_deploy_vm_with_userdata.py | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/server/src/com/cloud/vm/UserVmManagerImpl.java b/server/src/com/cloud/vm/UserVmManagerImpl.java index 05ff6aa74df..86150a2ab9c 100755 --- a/server/src/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/com/cloud/vm/UserVmManagerImpl.java @@ -3228,7 +3228,6 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use if (vm == null || vm.getRemoved() != null) { InvalidParameterValueException ex = new InvalidParameterValueException( "Unable to find a virtual machine with specified vmId"); - ex.addProxyObject(vm, vmId, "vmId"); throw ex; } diff --git a/test/integration/smoke/test_deploy_vm_with_userdata.py b/test/integration/smoke/test_deploy_vm_with_userdata.py index 8ca9bd05a2d..260106cbb0f 100644 --- a/test/integration/smoke/test_deploy_vm_with_userdata.py +++ b/test/integration/smoke/test_deploy_vm_with_userdata.py @@ -105,13 +105,13 @@ class TestDeployVmWithUserData(cloudstackTestCase): vms = list_virtual_machines( self.apiClient, account=self.account.name, - domainid=self.account.domainid + domainid=self.account.domainid, + id=deployVmResponse.id ) self.assert_(len(vms) > 0, "There are no Vms deployed in the account %s" % self.account.name) vm = vms[0] self.assert_(vm.id == str(deployVmResponse.id), "Vm deployed is different from the test") self.assert_(vm.state == "Running", "VM is not in Running state") - self.cleanup.append(deployVmResponse) @attr(tags=["simulator", "devcloud", "basic", "advanced"]) def test_deployvm_userdata(self): @@ -129,13 +129,13 @@ class TestDeployVmWithUserData(cloudstackTestCase): vms = list_virtual_machines( self.apiClient, account=self.account.name, - domainid=self.account.domainid + domainid=self.account.domainid, + id=deployVmResponse.id ) self.assert_(len(vms) > 0, "There are no Vms deployed in the account %s" % self.account.name) vm = vms[0] self.assert_(vm.id == str(deployVmResponse.id), "Vm deployed is different from the test") self.assert_(vm.state == "Running", "VM is not in Running state") - self.cleanup.append(deployVmResponse) @classmethod def tearDownClass(cls): From d6452be8618198454c6f20f4210f6ac18ce9b11d Mon Sep 17 00:00:00 2001 From: Murali Reddy Date: Mon, 20 May 2013 20:05:47 +0530 Subject: [PATCH 032/108] CLOUDSTACK-652: meging 'portable public ip' feature Squashed commit of the following: commit f244f9ce7982db16984dd87c31545f1c0240c704 Merge: 993cbb0 f5c8e38 Author: Murali Reddy Date: Mon May 20 18:54:05 2013 +0530 Merge branch 'master' into portablepublicip Conflicts: server/src/com/cloud/server/ManagementServerImpl.java server/test/org/apache/cloudstack/networkoffering/ChildTestConfiguration.java commit 993cbb0df9fa6e64b96b18ed775b73cdf4a8f5d7 Author: Murali Reddy Date: Mon May 20 18:49:54 2013 +0530 introduce 'transferPortableIP' interface method in network manger. This method will transfer association of portable ip from one network to another network. commit 0c1c2652c1b39e9a81ca35464360e11ed9ef23f1 Merge: a718d35 a29e393 Author: Murali Reddy Date: Fri May 17 02:48:54 2013 +0530 Merge branch 'master' into portablepublicip Conflicts: utils/src/com/cloud/utils/net/NetUtils.java commit a718d353f7acf0328d928673df6f22de1abc0acb Merge: ecca117 c211818 Author: Murali Reddy Date: Mon May 13 21:22:19 2013 +0530 Merge branch 'master' into portablepublicip Conflicts: api/src/org/apache/cloudstack/api/ResponseGenerator.java server/src/com/cloud/api/ApiResponseHelper.java server/src/com/cloud/network/NetworkServiceImpl.java server/src/com/cloud/network/addr/PublicIp.java server/src/com/cloud/server/ManagementServerImpl.java server/test/com/cloud/network/MockNetworkManagerImpl.java server/test/com/cloud/vpc/MockConfigurationManagerImpl.java server/test/com/cloud/vpc/MockNetworkManagerImpl.java setup/db/db/schema-410to420.sql commit ecca117e345224059297f5c1ffa0f442209b3160 Author: Murali Reddy Date: Mon May 13 20:05:29 2013 +0530 added integration tests for testing portable ip ranges commit 895a27c2771dbb497ecc6fe0d212589f012a48d8 Author: Murali Reddy Date: Mon May 13 15:12:19 2013 +0530 - establish model for transferring portable IP association from a network with which it is associated to another network. - enabling static nat api, extended to transfer potrtable IP across the networks if the VM/network is different from the current associate network of the portable ip commit 51509751b290c0e51cbdd104a9aebff189cbe806 Author: Murali Reddy Date: Mon May 13 12:05:33 2013 +0530 seperate out associate/disassociate with guest network operations from alloc and release of portable ip commit bd058f58c2d8d36ec25e31ed9de4cd414e0ca051 Author: Murali Reddy Date: Sun May 12 21:14:48 2013 +0530 enhance disasociateIPAddr API to release protable IP associated with a guest network or VPC commit 27504d9098729e8c3ac3b33f053f2d66ac2c4401 Author: Murali Reddy Date: Sun May 12 16:53:45 2013 +0530 enhance asociateIPAddr API to acquire a protable IP and associate with a guest network or VPC commit f82c6a8431647114462665c1306c6215cb92afd3 Merge: 3dbfb44 0749013 Author: Murali Reddy Date: Sat May 11 23:32:13 2013 +0530 Merge branch 'master' into portablepublicip Conflicts: api/src/com/cloud/network/IpAddress.java api/src/org/apache/cloudstack/api/ResponseGenerator.java client/tomcatconf/commands.properties.in server/src/com/cloud/api/ApiResponseHelper.java server/src/com/cloud/configuration/ConfigurationManagerImpl.java server/src/com/cloud/server/ManagementServerImpl.java server/test/org/apache/cloudstack/affinity/AffinityApiTestConfiguration.java server/test/org/apache/cloudstack/networkoffering/ChildTestConfiguration.java setup/db/db/schema-410to420.sql commit 3dbfb44eb5b888367375a96b8ae0ac9cf54309a6 Author: Murali Reddy Date: Sat May 11 20:33:19 2013 +0530 - add 'portable' boolean as property of IpAddress, persist the property in IPAddressVO, return the property in IpAddressResponse - add ability to request portable IP in associateIpAddress api commit bf3cb274cfeb1ef41c63794ced83c7c6940f34cc Author: Murali Reddy Date: Sat May 11 16:08:40 2013 +0530 add the status of each portable IP (its state, details of associated data center/VPC/guest network etc) in the PortableIpRangeResponse returned by listPortableIpRanges API commit e7b2fb22557cb4ef0ce9c8dde3ed1b9c857038bf Author: Murali Reddy Date: Sat May 11 14:36:01 2013 +0530 Introdcues notion of 'portable IP' pool at region level. Introduces root admin only API's to provision portable ip to a region - createPortableIpRange - deletePortableIpRange - listPortableIpRanges --- api/src/com/cloud/async/AsyncJob.java | 1 + .../configuration/ConfigurationService.java | 13 + api/src/com/cloud/event/EventTypes.java | 5 + api/src/com/cloud/network/IpAddress.java | 4 +- api/src/com/cloud/network/NetworkService.java | 5 + .../apache/cloudstack/api/ApiConstants.java | 2 + .../cloudstack/api/ResponseGenerator.java | 9 +- .../region/CreatePortableIpRangeCmd.java | 156 +++++++++++ .../region/DeletePortableIpRangeCmd.java | 93 +++++++ .../admin/region/ListPortableIpRangesCmd.java | 109 ++++++++ .../user/address/AssociateIPAddrCmd.java | 28 +- .../user/address/DisassociateIPAddrCmd.java | 12 +- .../command/user/nat/EnableStaticNatCmd.java | 6 + .../api/response/IPAddressResponse.java | 7 + .../api/response/PortableIpRangeResponse.java | 93 +++++++ .../api/response/PortableIpResponse.java | 106 +++++++ .../apache/cloudstack/region/PortableIp.java | 58 ++++ .../cloudstack/region/PortableIpRange.java | 38 +++ client/tomcatconf/applicationContext.xml.in | 2 + client/tomcatconf/commands.properties.in | 6 + .../com/cloud/network/dao/IPAddressVO.java | 24 ++ .../src/com/cloud/api/ApiResponseHelper.java | 70 ++++- .../ConfigurationManagerImpl.java | 165 ++++++++++- .../src/com/cloud/network/NetworkManager.java | 14 + .../com/cloud/network/NetworkManagerImpl.java | 262 +++++++++++++++++- .../com/cloud/network/NetworkServiceImpl.java | 105 ++++--- .../src/com/cloud/network/addr/PublicIp.java | 9 + .../cloud/network/rules/RulesManagerImpl.java | 43 +++ .../cloud/server/ManagementServerImpl.java | 86 +++++- .../cloudstack/region/PortableIpDao.java | 39 +++ .../cloudstack/region/PortableIpDaoImpl.java | 131 +++++++++ .../cloudstack/region/PortableIpRangeDao.java | 30 ++ .../region/PortableIpRangeDaoImpl.java | 65 +++++ .../cloudstack/region/PortableIpRangeVO.java | 119 ++++++++ .../cloudstack/region/PortableIpVO.java | 222 +++++++++++++++ .../cloud/network/MockNetworkManagerImpl.java | 40 ++- .../vpc/MockConfigurationManagerImpl.java | 58 ++++ .../com/cloud/vpc/MockNetworkManagerImpl.java | 41 ++- .../ChildTestConfiguration.java | 7 + setup/db/db/schema-410to420.sql | 37 +++ .../smoke/test_portable_publicip.py | 237 ++++++++++++++++ tools/marvin/marvin/integration/lib/base.py | 36 +++ utils/src/com/cloud/utils/net/NetUtils.java | 13 + 43 files changed, 2542 insertions(+), 64 deletions(-) create mode 100644 api/src/org/apache/cloudstack/api/command/admin/region/CreatePortableIpRangeCmd.java create mode 100644 api/src/org/apache/cloudstack/api/command/admin/region/DeletePortableIpRangeCmd.java create mode 100644 api/src/org/apache/cloudstack/api/command/admin/region/ListPortableIpRangesCmd.java create mode 100644 api/src/org/apache/cloudstack/api/response/PortableIpRangeResponse.java create mode 100644 api/src/org/apache/cloudstack/api/response/PortableIpResponse.java create mode 100644 api/src/org/apache/cloudstack/region/PortableIp.java create mode 100644 api/src/org/apache/cloudstack/region/PortableIpRange.java create mode 100755 server/src/org/apache/cloudstack/region/PortableIpDao.java create mode 100755 server/src/org/apache/cloudstack/region/PortableIpDaoImpl.java create mode 100755 server/src/org/apache/cloudstack/region/PortableIpRangeDao.java create mode 100755 server/src/org/apache/cloudstack/region/PortableIpRangeDaoImpl.java create mode 100644 server/src/org/apache/cloudstack/region/PortableIpRangeVO.java create mode 100644 server/src/org/apache/cloudstack/region/PortableIpVO.java create mode 100644 test/integration/smoke/test_portable_publicip.py diff --git a/api/src/com/cloud/async/AsyncJob.java b/api/src/com/cloud/async/AsyncJob.java index ccdc40620b7..47f9b574e23 100644 --- a/api/src/com/cloud/async/AsyncJob.java +++ b/api/src/com/cloud/async/AsyncJob.java @@ -35,6 +35,7 @@ public interface AsyncJob extends Identity, InternalIdentity { Host, StoragePool, IpAddress, + PortableIpAddress, SecurityGroup, PhysicalNetwork, TrafficType, diff --git a/api/src/com/cloud/configuration/ConfigurationService.java b/api/src/com/cloud/configuration/ConfigurationService.java index fdbd9d6bb0b..381fcad95cc 100644 --- a/api/src/com/cloud/configuration/ConfigurationService.java +++ b/api/src/com/cloud/configuration/ConfigurationService.java @@ -39,6 +39,9 @@ import org.apache.cloudstack.api.command.admin.offering.UpdateDiskOfferingCmd; import org.apache.cloudstack.api.command.admin.offering.UpdateServiceOfferingCmd; import org.apache.cloudstack.api.command.admin.pod.DeletePodCmd; import org.apache.cloudstack.api.command.admin.pod.UpdatePodCmd; +import org.apache.cloudstack.api.command.admin.region.CreatePortableIpRangeCmd; +import org.apache.cloudstack.api.command.admin.region.DeletePortableIpRangeCmd; +import org.apache.cloudstack.api.command.admin.region.ListPortableIpRangesCmd; import org.apache.cloudstack.api.command.admin.vlan.CreateVlanIpRangeCmd; import org.apache.cloudstack.api.command.admin.vlan.DedicatePublicIpRangeCmd; import org.apache.cloudstack.api.command.admin.vlan.DeleteVlanIpRangeCmd; @@ -56,6 +59,8 @@ import com.cloud.offering.DiskOffering; import com.cloud.offering.NetworkOffering; import com.cloud.offering.ServiceOffering; import com.cloud.user.Account; +import org.apache.cloudstack.region.PortableIp; +import org.apache.cloudstack.region.PortableIpRange; public interface ConfigurationService { @@ -278,4 +283,12 @@ public interface ConfigurationService { * @return */ boolean isOfferingForVpc(NetworkOffering offering); + + PortableIpRange createPortableIpRange(CreatePortableIpRangeCmd cmd) throws ConcurrentOperationException; + + boolean deletePortableIpRange(DeletePortableIpRangeCmd cmd); + + List listPortableIpRanges(ListPortableIpRangesCmd cmd); + + List listPortableIps(long id); } diff --git a/api/src/com/cloud/event/EventTypes.java b/api/src/com/cloud/event/EventTypes.java index 9c83f13ea2a..fcac8e8e65d 100755 --- a/api/src/com/cloud/event/EventTypes.java +++ b/api/src/com/cloud/event/EventTypes.java @@ -102,6 +102,8 @@ public class EventTypes { // Network Events public static final String EVENT_NET_IP_ASSIGN = "NET.IPASSIGN"; public static final String EVENT_NET_IP_RELEASE = "NET.IPRELEASE"; + public static final String EVENT_PORTABLE_IP_ASSIGN = "PORTABLE.IPASSIGN"; + public static final String EVENT_PORTABLE_IP_RELEASE = "PORTABLEIPRELEASE"; public static final String EVENT_NET_RULE_ADD = "NET.RULEADD"; public static final String EVENT_NET_RULE_DELETE = "NET.RULEDELETE"; public static final String EVENT_NET_RULE_MODIFY = "NET.RULEMODIFY"; @@ -432,6 +434,9 @@ public class EventTypes { public static final String EVENT_DEDICATED_GUEST_VLAN_RANGE_RELEASE = "GUESTVLANRANGE.RELEASE"; + public static final String EVENT_PORTABLE_IP_RANGE_CREATE = "PORTABLE.IP.RANGE.CREATE"; + public static final String EVENT_PORTABLE_IP_RANGE_DELETE = "PORTABLE.IP.RANGE.DELETE"; + static { // TODO: need a way to force author adding event types to declare the entity details as well, with out braking diff --git a/api/src/com/cloud/network/IpAddress.java b/api/src/com/cloud/network/IpAddress.java index c48e8b97ca8..de11a6de79b 100644 --- a/api/src/com/cloud/network/IpAddress.java +++ b/api/src/com/cloud/network/IpAddress.java @@ -81,7 +81,9 @@ public interface IpAddress extends ControlledEntity, Identity, InternalIdentity Long getVpcId(); String getVmIp(); - + + boolean isPortable(); + Long getNetworkId(); } diff --git a/api/src/com/cloud/network/NetworkService.java b/api/src/com/cloud/network/NetworkService.java index 2e50c53d8bb..59702a2864e 100755 --- a/api/src/com/cloud/network/NetworkService.java +++ b/api/src/com/cloud/network/NetworkService.java @@ -52,6 +52,11 @@ public interface NetworkService { boolean releaseIpAddress(long ipAddressId) throws InsufficientAddressCapacityException; + IpAddress allocatePortableIP(Account ipOwner, int regionId, Long zoneId, Long networkId, Long vpcId) throws ResourceAllocationException, + InsufficientAddressCapacityException, ConcurrentOperationException; + + boolean releasePortableIpAddress(long ipAddressId) throws InsufficientAddressCapacityException; + Network createGuestNetwork(CreateNetworkCmd cmd) throws InsufficientCapacityException, ConcurrentOperationException, ResourceAllocationException; diff --git a/api/src/org/apache/cloudstack/api/ApiConstants.java b/api/src/org/apache/cloudstack/api/ApiConstants.java index cf093bf4c7c..1e9435f6a8e 100755 --- a/api/src/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/org/apache/cloudstack/api/ApiConstants.java @@ -114,6 +114,7 @@ public class ApiConstants { public static final String IS_CLEANUP_REQUIRED = "iscleanuprequired"; public static final String IS_EXTRACTABLE = "isextractable"; public static final String IS_FEATURED = "isfeatured"; + public static final String IS_PORTABLE = "isportable"; public static final String IS_PUBLIC = "ispublic"; public static final String IS_PERSISTENT = "ispersistent"; public static final String IS_READY = "isready"; @@ -158,6 +159,7 @@ public class ApiConstants { public static final String POLICY_ID = "policyid"; public static final String PORT = "port"; public static final String PORTAL = "portal"; + public static final String PORTABLE_IP_ADDRESS = "portableipaddress"; public static final String PORT_FORWARDING_SERVICE_ID = "portforwardingserviceid"; public static final String PRIVATE_INTERFACE = "privateinterface"; public static final String PRIVATE_IP = "privateip"; diff --git a/api/src/org/apache/cloudstack/api/ResponseGenerator.java b/api/src/org/apache/cloudstack/api/ResponseGenerator.java index 10bf305cb1c..0732e77a781 100644 --- a/api/src/org/apache/cloudstack/api/ResponseGenerator.java +++ b/api/src/org/apache/cloudstack/api/ResponseGenerator.java @@ -119,6 +119,8 @@ import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.cloudstack.api.response.*; import org.apache.cloudstack.network.lb.ApplicationLoadBalancerRule; import org.apache.cloudstack.region.Region; +import org.apache.cloudstack.region.PortableIp; +import org.apache.cloudstack.region.PortableIpRange; import org.apache.cloudstack.usage.Usage; import com.cloud.async.AsyncJob; @@ -441,7 +443,12 @@ public interface ResponseGenerator { Long getAffinityGroupId(String name, long entityOwnerId); + PortableIpRangeResponse createPortableIPRangeResponse(PortableIpRange range); + + PortableIpResponse createPortableIPResponse(PortableIp portableIp); + InternalLoadBalancerElementResponse createInternalLbElementResponse(VirtualRouterProvider result); - + IsolationMethodResponse createIsolationMethodResponse(IsolationType method); + } diff --git a/api/src/org/apache/cloudstack/api/command/admin/region/CreatePortableIpRangeCmd.java b/api/src/org/apache/cloudstack/api/command/admin/region/CreatePortableIpRangeCmd.java new file mode 100644 index 00000000000..78e4c94ed4c --- /dev/null +++ b/api/src/org/apache/cloudstack/api/command/admin/region/CreatePortableIpRangeCmd.java @@ -0,0 +1,156 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.admin.region; + +import javax.inject.Inject; + +import com.cloud.async.AsyncJob; +import com.cloud.dc.Vlan; +import com.cloud.event.EventTypes; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.ResourceAllocationException; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCreateCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.PortableIpRangeResponse; +import org.apache.cloudstack.api.response.RegionResponse; +import org.apache.cloudstack.api.response.VlanIpRangeResponse; +import org.apache.cloudstack.region.PortableIpRange; +import org.apache.cloudstack.region.Region; +import org.apache.cloudstack.region.RegionService; +import org.apache.log4j.Logger; + +import com.cloud.user.Account; + +@APICommand(name = "createPortableIpRange", responseObject=PortableIpRangeResponse.class, description="adds a range of portable public IP's to a region", since="4.2.0") +public class CreatePortableIpRangeCmd extends BaseAsyncCreateCmd { + + public static final Logger s_logger = Logger.getLogger(CreatePortableIpRangeCmd.class.getName()); + + private static final String s_name = "createportableiprangeresponse"; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name=ApiConstants.REGION_ID, type=CommandType.INTEGER, entityType = RegionResponse.class, required=true, description="Id of the Region") + private Integer regionId; + + @Parameter(name=ApiConstants.START_IP, type=CommandType.STRING, required=true, description="the beginning IP address in the portable IP range") + private String startIp; + + @Parameter(name=ApiConstants.END_IP, type=CommandType.STRING, required=true, description="the ending IP address in the portable IP range") + private String endIp; + + @Parameter(name=ApiConstants.GATEWAY, type=CommandType.STRING, required=true, description="the gateway for the portable IP range") + private String gateway; + + @Parameter(name=ApiConstants.NETMASK, type=CommandType.STRING, required=true, description="the netmask of the portable IP range") + private String netmask; + + @Parameter(name=ApiConstants.VLAN, type=CommandType.STRING, description="VLAN id, if not specified defaulted to untagged") + private String vlan; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Integer getRegionId() { + return regionId; + } + + public String getStartIp() { + return startIp; + } + + public String getEndIp() { + return endIp; + } + + public String getVlan() { + return vlan; + } + + public String getGateway() { + return gateway; + } + + public String getNetmask() { + return netmask; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } + + @Override + public void execute(){ + PortableIpRange portableIpRange = _entityMgr.findById(PortableIpRange.class, getEntityId()); + PortableIpRangeResponse response = null; + if (portableIpRange != null) { + response = _responseGenerator.createPortableIPRangeResponse(portableIpRange); + } + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } + + @Override + public void create() throws ResourceAllocationException { + try { + PortableIpRange portableIpRange = _configService.createPortableIpRange(this); + if (portableIpRange != null) { + this.setEntityId(portableIpRange.getId()); + this.setEntityUuid(portableIpRange.getUuid()); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create portable public IP range"); + } + } catch (ConcurrentOperationException ex) { + s_logger.warn("Exception: ", ex); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage()); + } + } + + @Override + public String getEventType() { + return EventTypes.EVENT_PORTABLE_IP_RANGE_CREATE; + } + + @Override + public String getEventDescription() { + return "creating a portable public ip range in region: " + getRegionId(); + } + + @Override + public AsyncJob.Type getInstanceType() { + return AsyncJob.Type.PortableIpAddress; + } +} diff --git a/api/src/org/apache/cloudstack/api/command/admin/region/DeletePortableIpRangeCmd.java b/api/src/org/apache/cloudstack/api/command/admin/region/DeletePortableIpRangeCmd.java new file mode 100644 index 00000000000..856e8efa210 --- /dev/null +++ b/api/src/org/apache/cloudstack/api/command/admin/region/DeletePortableIpRangeCmd.java @@ -0,0 +1,93 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.admin.region; + +import javax.inject.Inject; + +import com.cloud.async.AsyncJob; +import com.cloud.event.EventTypes; +import org.apache.cloudstack.api.*; +import org.apache.cloudstack.api.response.PortableIpRangeResponse; +import org.apache.cloudstack.api.response.RegionResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.region.Region; +import org.apache.cloudstack.region.RegionService; +import org.apache.log4j.Logger; + +import com.cloud.user.Account; + +@APICommand(name = "deletePortableIpRange", description="deletes a range of portable public IP's associated with a region", responseObject=SuccessResponse.class) +public class DeletePortableIpRangeCmd extends BaseAsyncCmd { + public static final Logger s_logger = Logger.getLogger(DeletePortableIpRangeCmd.class.getName()); + + private static final String s_name = "deleteportablepublicipresponse"; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + @Parameter(name=ApiConstants.ID, type=CommandType.UUID, required=true, entityType = PortableIpRangeResponse.class, description="Id of the portable ip range") + private Long id; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } + + @Override + public void execute(){ + boolean result = _configService.deletePortableIpRange(this); + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete portable ip range"); + } + } + + @Override + public String getEventType() { + return EventTypes.EVENT_PORTABLE_IP_RANGE_DELETE; + } + + @Override + public String getEventDescription() { + return "deleting a portable public ip range"; + } + + @Override + public AsyncJob.Type getInstanceType() { + return AsyncJob.Type.PortableIpAddress; + } +} diff --git a/api/src/org/apache/cloudstack/api/command/admin/region/ListPortableIpRangesCmd.java b/api/src/org/apache/cloudstack/api/command/admin/region/ListPortableIpRangesCmd.java new file mode 100644 index 00000000000..75bcce00acf --- /dev/null +++ b/api/src/org/apache/cloudstack/api/command/admin/region/ListPortableIpRangesCmd.java @@ -0,0 +1,109 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.admin.region; + +import javax.inject.Inject; + +import org.apache.cloudstack.api.*; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.PortableIpRangeResponse; +import org.apache.cloudstack.api.response.PortableIpResponse; +import org.apache.cloudstack.api.response.RegionResponse; +import org.apache.cloudstack.region.PortableIp; +import org.apache.cloudstack.region.PortableIpRange; +import org.apache.cloudstack.region.Region; +import org.apache.cloudstack.region.RegionService; +import org.apache.log4j.Logger; + +import com.cloud.user.Account; + +import java.util.ArrayList; +import java.util.List; + +@APICommand(name = "listPortableIpRanges", description="list portable IP ranges", responseObject=PortableIpRangeResponse.class) +public class ListPortableIpRangesCmd extends BaseListCmd { + + public static final Logger s_logger = Logger.getLogger(ListPortableIpRangesCmd.class.getName()); + + private static final String s_name = "listportableipresponse"; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name=ApiConstants.REGION_ID, type=CommandType.INTEGER, required=false, description="Id of a Region") + private Integer regionId; + + @Parameter(name=ApiConstants.ID, type=CommandType.UUID, required=false, entityType = PortableIpRangeResponse.class, description="Id of the portable ip range") + private Long id; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Integer getRegionIdId() { + return regionId; + } + + public Long getPortableIpRangeId() { + return id; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } + + @Override + public void execute(){ + ListResponse response = new ListResponse(); + List responses = new ArrayList(); + List portableIpRanges = new ArrayList(); + + portableIpRanges = _configService.listPortableIpRanges(this); + if (portableIpRanges != null && !portableIpRanges.isEmpty()) { + for (PortableIpRange range : portableIpRanges) { + PortableIpRangeResponse rangeResponse = _responseGenerator.createPortableIPRangeResponse(range); + + List portableIps = _configService.listPortableIps(range.getId()); + if (portableIps != null && !portableIps.isEmpty()) { + List portableIpResponses = new ArrayList(); + for (PortableIp portableIP: portableIps) { + PortableIpResponse portableIpresponse = _responseGenerator.createPortableIPResponse(portableIP); + portableIpResponses.add(portableIpresponse); + } + rangeResponse.setPortableIpResponses(portableIpResponses); + } + + rangeResponse.setObjectName("portableiprange"); + responses.add(rangeResponse); + } + } + response.setResponses(responses, portableIpRanges.size()); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } +} diff --git a/api/src/org/apache/cloudstack/api/command/user/address/AssociateIPAddrCmd.java b/api/src/org/apache/cloudstack/api/command/user/address/AssociateIPAddrCmd.java index 28fbae4437a..f37e82060fb 100644 --- a/api/src/org/apache/cloudstack/api/command/user/address/AssociateIPAddrCmd.java +++ b/api/src/org/apache/cloudstack/api/command/user/address/AssociateIPAddrCmd.java @@ -66,6 +66,14 @@ public class AssociateIPAddrCmd extends BaseAsyncCreateCmd { "be associated with") private Long vpcId; + @Parameter(name=ApiConstants.IS_PORTABLE, type = BaseCmd.CommandType.BOOLEAN, description = "should be set to true " + + "if public IP is required to be transferable across zones, if not specified defaults to false") + private Boolean isPortable; + + @Parameter(name=ApiConstants.REGION_ID, type=CommandType.INTEGER, entityType = RegionResponse.class, + required=false, description="region ID from where portable ip is to be associated.") + private Integer regionId; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -107,6 +115,18 @@ public class AssociateIPAddrCmd extends BaseAsyncCreateCmd { return vpcId; } + public boolean isPortable() { + if (isPortable == null) { + return false; + } else { + return isPortable; + } + } + + public Integer getRegionId() { + return regionId; + } + public Long getNetworkId() { if (vpcId != null) { return null; @@ -196,7 +216,13 @@ public class AssociateIPAddrCmd extends BaseAsyncCreateCmd { @Override public void create() throws ResourceAllocationException{ try { - IpAddress ip = _networkService.allocateIP(_accountService.getAccount(getEntityOwnerId()), getZoneId(), getNetworkId()); + IpAddress ip = null; + + if (!isPortable()) { + ip = _networkService.allocateIP(_accountService.getAccount(getEntityOwnerId()), getZoneId(), getNetworkId()); + } else { + ip = _networkService.allocatePortableIP(_accountService.getAccount(getEntityOwnerId()), 1, getZoneId(), getNetworkId(), getVpcId()); + } if (ip != null) { this.setEntityId(ip.getId()); diff --git a/api/src/org/apache/cloudstack/api/command/user/address/DisassociateIPAddrCmd.java b/api/src/org/apache/cloudstack/api/command/user/address/DisassociateIPAddrCmd.java index 827111902ff..8f78fe3a959 100644 --- a/api/src/org/apache/cloudstack/api/command/user/address/DisassociateIPAddrCmd.java +++ b/api/src/org/apache/cloudstack/api/command/user/address/DisassociateIPAddrCmd.java @@ -74,7 +74,12 @@ public class DisassociateIPAddrCmd extends BaseAsyncCmd { @Override public void execute() throws InsufficientAddressCapacityException{ UserContext.current().setEventDetails("Ip Id: " + getIpAddressId()); - boolean result = _networkService.releaseIpAddress(getIpAddressId()); + boolean result = false; + if (!isPortable(id)) { + _networkService.releaseIpAddress(getIpAddressId()); + } else { + _networkService.releaseIpAddress(getIpAddressId()); + } if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); this.setResponseObject(response); @@ -139,4 +144,9 @@ public class DisassociateIPAddrCmd extends BaseAsyncCmd { public Long getInstanceId() { return getIpAddressId(); } + + private boolean isPortable(long id) { + IpAddress ip = getIpAddress(id); + return ip.isPortable(); + } } diff --git a/api/src/org/apache/cloudstack/api/command/user/nat/EnableStaticNatCmd.java b/api/src/org/apache/cloudstack/api/command/user/nat/EnableStaticNatCmd.java index 902dbae00aa..2e2e1507229 100644 --- a/api/src/org/apache/cloudstack/api/command/user/nat/EnableStaticNatCmd.java +++ b/api/src/org/apache/cloudstack/api/command/user/nat/EnableStaticNatCmd.java @@ -91,6 +91,12 @@ public class EnableStaticNatCmd extends BaseCmd{ } else { ntwkId = networkId; } + + // in case of portable public IP, network ID passed takes precedence + if (ip.isPortable() && networkId != null ) { + ntwkId = networkId; + } + if (ntwkId == null) { throw new InvalidParameterValueException("Unable to enable static nat for the ipAddress id=" + ipAddressId + " as ip is not associated with any network and no networkId is passed in"); diff --git a/api/src/org/apache/cloudstack/api/response/IPAddressResponse.java b/api/src/org/apache/cloudstack/api/response/IPAddressResponse.java index cede84f931e..e3d5cc6a101 100644 --- a/api/src/org/apache/cloudstack/api/response/IPAddressResponse.java +++ b/api/src/org/apache/cloudstack/api/response/IPAddressResponse.java @@ -115,6 +115,9 @@ public class IPAddressResponse extends BaseResponse implements ControlledEntityR @SerializedName(ApiConstants.TAGS) @Param(description="the list of resource tags associated with ip address", responseObject = ResourceTagResponse.class) private List tags; + @SerializedName(ApiConstants.IS_PORTABLE) @Param(description = "is public IP portable across the zones") + private Boolean isPortable; + /* @SerializedName(ApiConstants.JOB_ID) @Param(description="shows the current pending asynchronous job ID. This tag is not returned if no current pending jobs are acting on the volume") private IdentityProxy jobId = new IdentityProxy("async_job"); @@ -247,4 +250,8 @@ public class IPAddressResponse extends BaseResponse implements ControlledEntityR public void setAssociatedNetworkName(String associatedNetworkName) { this.associatedNetworkName = associatedNetworkName; } + + public void setPortable(Boolean portable) { + this.isPortable = portable; + } } diff --git a/api/src/org/apache/cloudstack/api/response/PortableIpRangeResponse.java b/api/src/org/apache/cloudstack/api/response/PortableIpRangeResponse.java new file mode 100644 index 00000000000..e7a15b8f606 --- /dev/null +++ b/api/src/org/apache/cloudstack/api/response/PortableIpRangeResponse.java @@ -0,0 +1,93 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.response; + +import java.util.Date; +import java.util.List; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.EntityReference; + +import com.cloud.network.IpAddress; +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; +import org.apache.cloudstack.region.PortableIpRange; + +@EntityReference(value=PortableIpRange.class) +public class PortableIpRangeResponse extends BaseResponse { + + @SerializedName(ApiConstants.ID) + @Param(description = "portable IP range ID") + private String id; + + @SerializedName(ApiConstants.REGION_ID) + @Param(description = "Region Id in which portable ip range is provisioned") + private Integer regionId; + + @SerializedName(ApiConstants.GATEWAY) @Param(description="the gateway of the VLAN IP range") + private String gateway; + + @SerializedName(ApiConstants.NETMASK) @Param(description="the netmask of the VLAN IP range") + private String netmask; + + @SerializedName(ApiConstants.VLAN) @Param(description="the ID or VID of the VLAN.") + private String vlan; + + @SerializedName(ApiConstants.START_IP) @Param(description="the start ip of the portable IP range") + private String startIp; + + @SerializedName(ApiConstants.END_IP) @Param(description="the end ip of the portable IP range") + private String endIp; + + @SerializedName(ApiConstants.PORTABLE_IP_ADDRESS) + @Param(description="List of portable IP and association with zone/network/vpc details that are part of GSLB rule", responseObject = PortableIpResponse.class) + private List portableIpResponses; + + public void setId(String id) { + this.id = id; + } + + public void setRegionId(int regionId) { + this.regionId = regionId; + } + + public void setStartIp(String startIp) { + this.startIp = startIp; + } + + public void setEndIp(String endIp) { + this.endIp = endIp; + } + + public void setNetmask(String netmask) { + this.netmask = netmask; + } + + public void setGateway(String gateway) { + this.gateway = gateway; + } + + public void setVlan(String vlan) { + this.vlan = vlan; + } + + public void setPortableIpResponses(List portableIpResponses) { + this.portableIpResponses = portableIpResponses; + } +} diff --git a/api/src/org/apache/cloudstack/api/response/PortableIpResponse.java b/api/src/org/apache/cloudstack/api/response/PortableIpResponse.java new file mode 100644 index 00000000000..0ccbcc3e5f6 --- /dev/null +++ b/api/src/org/apache/cloudstack/api/response/PortableIpResponse.java @@ -0,0 +1,106 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.response; + +import java.util.Date; +import java.util.List; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.EntityReference; + +import com.cloud.network.IpAddress; +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; +import org.apache.cloudstack.region.PortableIp; +import org.apache.cloudstack.region.PortableIpRange; + +@EntityReference(value=PortableIp.class) +public class PortableIpResponse extends BaseResponse { + + @SerializedName(ApiConstants.REGION_ID) + @Param(description = "Region Id in which global load balancer is created") + private Integer regionId; + + @SerializedName(ApiConstants.IP_ADDRESS) @Param(description="public IP address") + private String ipAddress; + + @SerializedName(ApiConstants.ZONE_ID) @Param(description="the ID of the zone the public IP address belongs to") + private String zoneId; + + @SerializedName(ApiConstants.NETWORK_ID) @Param(description="the ID of the Network where ip belongs to") + private String networkId; + + @SerializedName(ApiConstants.VPC_ID) @Param(description="VPC the ip belongs to") + private String vpcId; + + @SerializedName(ApiConstants.PHYSICAL_NETWORK_ID) @Param(description="the physical network this belongs to") + private String physicalNetworkId; + + @SerializedName(ApiConstants.ACCOUNT_ID) @Param(description="the account ID the portable IP address is associated with") + private String accountId; + + @SerializedName(ApiConstants.DOMAIN_ID) @Param(description="the domain ID the portable IP address is associated with") + private String domainId; + + @SerializedName("allocated") @Param(description="date the portal IP address was acquired") + private Date allocated; + + @SerializedName(ApiConstants.STATE) @Param(description="State of the ip address. Can be: Allocatin, Allocated and Releasing") + private String state; + + public void setRegionId(Integer regionId) { + this.regionId = regionId; + } + + public void setAddress(String ipAddress) { + this.ipAddress = ipAddress; + } + + public void setAssociatedDataCenterId(String zoneId) { + this.zoneId = zoneId; + } + + public void setAssociatedWithNetworkId(String networkId) { + this.networkId = networkId; + } + + public void setAssociatedWithVpcId(String vpcId) { + this.vpcId = vpcId; + } + + public void setPhysicalNetworkId(String physicalNetworkId) { + this.physicalNetworkId = physicalNetworkId; + } + + public void setAllocatedToAccountId(String accountId) { + this.accountId = accountId; + } + + public void setAllocatedInDomainId(String domainId) { + this.domainId = domainId; + } + + public void setAllocatedTime(Date allocatedTimetime) { + this.allocated = allocatedTimetime; + } + + public void setState(String state) { + this.state = state; + } +} diff --git a/api/src/org/apache/cloudstack/region/PortableIp.java b/api/src/org/apache/cloudstack/region/PortableIp.java new file mode 100644 index 00000000000..b95071e2456 --- /dev/null +++ b/api/src/org/apache/cloudstack/region/PortableIp.java @@ -0,0 +1,58 @@ +// 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.region; + +import java.util.Date; + +import com.cloud.utils.net.Ip; +import org.apache.cloudstack.api.InternalIdentity; + +public interface PortableIp extends InternalIdentity { + + enum State { + Allocating, // The IP Address is being propagated to other network elements and is not ready for use yet. + Allocated, // The IP address is in used. + Releasing, // The IP address is being released for other network elements and is not ready for allocation. + Free // The IP address is ready to be allocated. + } + + Long getAllocatedToAccountId(); + + Long getAllocatedInDomainId(); + + Date getAllocatedTime(); + + State getState(); + + int getRegionId(); + + Long getAssociatedDataCenterId(); + + Long getAssociatedWithNetworkId(); + + Long getAssociatedWithVpcId(); + + Long getPhysicalNetworkId(); + + String getAddress(); + + String getVlan(); + + String getNetmask(); + + String getGateway(); +} diff --git a/api/src/org/apache/cloudstack/region/PortableIpRange.java b/api/src/org/apache/cloudstack/region/PortableIpRange.java new file mode 100644 index 00000000000..413a540f92a --- /dev/null +++ b/api/src/org/apache/cloudstack/region/PortableIpRange.java @@ -0,0 +1,38 @@ +// 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.region; + +import org.apache.cloudstack.acl.InfrastructureEntity; +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + +public interface PortableIpRange extends InfrastructureEntity, InternalIdentity, Identity { + + public final static String UNTAGGED = "untagged"; + + public String getVlanTag(); + + public String getGateway(); + + public String getNetmask(); + + public int getRegionId(); + + public String getIpRange(); + +} diff --git a/client/tomcatconf/applicationContext.xml.in b/client/tomcatconf/applicationContext.xml.in index b500fde8549..edf83a94ae6 100644 --- a/client/tomcatconf/applicationContext.xml.in +++ b/client/tomcatconf/applicationContext.xml.in @@ -271,6 +271,8 @@ + + diff --git a/client/tomcatconf/commands.properties.in b/client/tomcatconf/commands.properties.in index 68a7511560b..fd5479f44b4 100644 --- a/client/tomcatconf/commands.properties.in +++ b/client/tomcatconf/commands.properties.in @@ -628,6 +628,11 @@ addCiscoAsa1000vResource=1 deleteCiscoAsa1000vResource=1 listCiscoAsa1000vResources=1 +#### portable public IP commands +createPortableIpRange=1 +deletePortableIpRange=1 +listPortableIpRanges=1 + #### Internal LB VM commands stopInternalLoadBalancerVM=1 startInternalLoadBalancerVM=1 @@ -635,3 +640,4 @@ listInternalLoadBalancerVMs=1 ### Network Isolation methods listing listNetworkIsolationMethods=1 + diff --git a/engine/schema/src/com/cloud/network/dao/IPAddressVO.java b/engine/schema/src/com/cloud/network/dao/IPAddressVO.java index ae27e95ce4b..5eb2500e0a6 100644 --- a/engine/schema/src/com/cloud/network/dao/IPAddressVO.java +++ b/engine/schema/src/com/cloud/network/dao/IPAddressVO.java @@ -111,6 +111,8 @@ public class IPAddressVO implements IpAddress { @Column(name="dnat_vmip") private String vmIp; + @Column(name="is_portable") + private boolean portable = false; protected IPAddressVO() { this.uuid = UUID.randomUUID().toString(); @@ -134,6 +136,19 @@ public class IPAddressVO implements IpAddress { this.uuid = UUID.randomUUID().toString(); } + public IPAddressVO(Ip address, long dataCenterId, Long networkId, Long vpcId, long physicalNetworkId, long sourceNetworkId, + long vlanDbId, boolean portable) { + this.address = address; + this.dataCenterId = dataCenterId; + this.associatedWithNetworkId = networkId; + this.vpcId = vpcId; + this.physicalNetworkId = physicalNetworkId; + this.sourceNetworkId = sourceNetworkId; + this.vlanId = vlanDbId; + this.portable = portable; + this.uuid = UUID.randomUUID().toString(); + } + public long getMacAddress() { return macAddress; } @@ -283,6 +298,15 @@ public class IPAddressVO implements IpAddress { this.system = isSystem; } + @Override + public boolean isPortable() { + return portable; + } + + public void setPortable(boolean portable) { + this.portable = portable; + } + @Override public Long getVpcId() { return vpcId; diff --git a/server/src/com/cloud/api/ApiResponseHelper.java b/server/src/com/cloud/api/ApiResponseHelper.java index 49e36dc47db..fc1c6a0be63 100755 --- a/server/src/com/cloud/api/ApiResponseHelper.java +++ b/server/src/com/cloud/api/ApiResponseHelper.java @@ -49,6 +49,8 @@ import org.apache.cloudstack.api.ApiConstants.VMDetails; import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.ResponseGenerator; import org.apache.cloudstack.api.command.user.job.QueryAsyncJobResultCmd; +import org.apache.cloudstack.region.PortableIp; +import org.apache.cloudstack.region.PortableIpRange; import org.apache.cloudstack.api.response.AccountResponse; import org.apache.cloudstack.api.response.ApplicationLoadBalancerInstanceResponse; import org.apache.cloudstack.api.response.ApplicationLoadBalancerResponse; @@ -98,6 +100,8 @@ import org.apache.cloudstack.api.response.NicResponse; import org.apache.cloudstack.api.response.NicSecondaryIpResponse; import org.apache.cloudstack.api.response.PhysicalNetworkResponse; import org.apache.cloudstack.api.response.PodResponse; +import org.apache.cloudstack.api.response.PortableIpRangeResponse; +import org.apache.cloudstack.api.response.PortableIpResponse; import org.apache.cloudstack.api.response.PrivateGatewayResponse; import org.apache.cloudstack.api.response.ProjectAccountResponse; import org.apache.cloudstack.api.response.ProjectInvitationResponse; @@ -722,6 +726,8 @@ public class ApiResponseHelper implements ResponseGenerator { } } + ipResponse.setPortable(ipAddr.isPortable()); + //set tag information List tags = ApiDBUtils.listByResourceTypeAndId(TaggedResourceType.PublicIpAddress, ipAddr.getId()); List tagResponses = new ArrayList(); @@ -3824,6 +3830,69 @@ public class ApiResponseHelper implements ResponseGenerator { } } + @Override + public PortableIpRangeResponse createPortableIPRangeResponse(PortableIpRange ipRange) { + PortableIpRangeResponse response = new PortableIpRangeResponse(); + response.setId(ipRange.getUuid()); + String ipRangeStr = ipRange.getIpRange(); + if (ipRangeStr != null) { + String[] range = ipRangeStr.split("-"); + response.setStartIp(range[0]); + response.setEndIp(range[1]); + } + response.setVlan(ipRange.getVlanTag()); + response.setGateway(ipRange.getGateway()); + response.setNetmask(ipRange.getNetmask()); + response.setRegionId(ipRange.getRegionId()); + return response; + } + + @Override + public PortableIpResponse createPortableIPResponse(PortableIp portableIp) { + PortableIpResponse response = new PortableIpResponse(); + response.setAddress(portableIp.getAddress()); + Long accountId = portableIp.getAllocatedInDomainId(); + if (accountId != null) { + Account account = ApiDBUtils.findAccountById(accountId); + response.setAllocatedToAccountId(account.getAccountName()); + Domain domain = ApiDBUtils.findDomainById(account.getDomainId()); + response.setAllocatedInDomainId(domain.getUuid()); + } + + response.setAllocatedTime(portableIp.getAllocatedTime()); + + if (portableIp.getAssociatedDataCenterId() != null) { + DataCenter zone = ApiDBUtils.findZoneById(portableIp.getAssociatedDataCenterId()); + if (zone != null) { + response.setAssociatedDataCenterId(zone.getUuid()); + } + } + + if (portableIp.getPhysicalNetworkId() != null) { + PhysicalNetwork pnw = ApiDBUtils.findPhysicalNetworkById(portableIp.getPhysicalNetworkId()); + if (pnw != null) { + response.setPhysicalNetworkId(pnw.getUuid()); + } + } + + if (portableIp.getAssociatedWithNetworkId() != null) { + Network ntwk = ApiDBUtils.findNetworkById(portableIp.getAssociatedWithNetworkId()); + if (ntwk != null) { + response.setAssociatedWithNetworkId(ntwk.getUuid()); + } + } + + if (portableIp.getAssociatedWithVpcId() != null) { + Vpc vpc = ApiDBUtils.findVpcById(portableIp.getAssociatedWithVpcId()); + if (vpc != null) { + response.setAssociatedWithVpcId(vpc.getUuid()); + } + } + + response.setState(portableIp.getState().name()); + + return response; + } @Override public InternalLoadBalancerElementResponse createInternalLbElementResponse(VirtualRouterProvider result) { @@ -3842,7 +3911,6 @@ public class ApiResponseHelper implements ResponseGenerator { return response; } - @Override public IsolationMethodResponse createIsolationMethodResponse(IsolationType method) { IsolationMethodResponse response = new IsolationMethodResponse(); diff --git a/server/src/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/com/cloud/configuration/ConfigurationManagerImpl.java index 52d617646af..47c54822adb 100755 --- a/server/src/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/com/cloud/configuration/ConfigurationManagerImpl.java @@ -43,6 +43,7 @@ import com.cloud.dc.*; import com.cloud.dc.dao.*; import com.cloud.user.*; import com.cloud.event.UsageEventUtils; +import com.cloud.utils.db.*; import org.apache.cloudstack.acl.SecurityChecker; import org.apache.cloudstack.api.ApiConstants.LDAPParams; import org.apache.cloudstack.api.command.admin.config.UpdateCfgCmd; @@ -59,6 +60,9 @@ import org.apache.cloudstack.api.command.admin.offering.UpdateDiskOfferingCmd; import org.apache.cloudstack.api.command.admin.offering.UpdateServiceOfferingCmd; import org.apache.cloudstack.api.command.admin.pod.DeletePodCmd; import org.apache.cloudstack.api.command.admin.pod.UpdatePodCmd; +import org.apache.cloudstack.api.command.admin.region.CreatePortableIpRangeCmd; +import org.apache.cloudstack.api.command.admin.region.DeletePortableIpRangeCmd; +import org.apache.cloudstack.api.command.admin.region.ListPortableIpRangesCmd; import org.apache.cloudstack.api.command.admin.vlan.CreateVlanIpRangeCmd; import org.apache.cloudstack.api.command.admin.vlan.DedicatePublicIpRangeCmd; import org.apache.cloudstack.api.command.admin.vlan.DeleteVlanIpRangeCmd; @@ -67,6 +71,8 @@ import org.apache.cloudstack.api.command.admin.zone.CreateZoneCmd; import org.apache.cloudstack.api.command.admin.zone.DeleteZoneCmd; import org.apache.cloudstack.api.command.admin.zone.UpdateZoneCmd; import org.apache.cloudstack.api.command.user.network.ListNetworkOfferingsCmd; +import org.apache.cloudstack.region.*; +import org.apache.cloudstack.region.dao.RegionDao; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO; import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao; @@ -186,10 +192,6 @@ import com.cloud.utils.NumbersUtil; import com.cloud.utils.StringUtils; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.crypt.DBEncryptionUtil; -import com.cloud.utils.db.DB; -import com.cloud.utils.db.Filter; -import com.cloud.utils.db.SearchCriteria; -import com.cloud.utils.db.Transaction; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.net.NetUtils; import com.cloud.vm.NicIpAlias; @@ -332,6 +334,12 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati @Inject VpcManager _vpcMgr; @Inject + PortableIpRangeDao _portableIpRangeDao; + @Inject + RegionDao _regionDao; + @Inject + PortableIpDao _portableIpDao; + @Inject ConfigurationServer _configServer; @Inject DcDetailsDao _dcDetailsDao; @@ -4718,4 +4726,153 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati return null; } + + @Override + @DB + @ActionEvent(eventType = EventTypes.EVENT_PORTABLE_IP_RANGE_CREATE, eventDescription = "creating portable ip range", async = false) + public PortableIpRange createPortableIpRange(CreatePortableIpRangeCmd cmd) throws ConcurrentOperationException { + Integer regionId = cmd.getRegionId(); + String startIP = cmd.getStartIp(); + String endIP = cmd.getEndIp(); + String gateway = cmd.getGateway(); + String netmask = cmd.getNetmask(); + Long userId = UserContext.current().getCallerUserId(); + String vlanId = cmd.getVlan(); + + Region region = _regionDao.findById(regionId); + if (region == null) { + throw new InvalidParameterValueException("Invalid region ID: " + regionId); + } + + if (!NetUtils.isValidIp(startIP) || !NetUtils.isValidIp(endIP) || !NetUtils.validIpRange(startIP, endIP)) { + throw new InvalidParameterValueException("Invalid portable ip range: " + startIP + "-" + endIP); + } + + if (!NetUtils.sameSubnet(startIP, gateway, netmask)) { + throw new InvalidParameterValueException("Please ensure that your start IP is in the same subnet as " + + "your portable IP range's gateway and as per the IP range's netmask."); + } + + if (!NetUtils.sameSubnet(endIP, gateway, netmask)) { + throw new InvalidParameterValueException("Please ensure that your end IP is in the same subnet as " + + "your portable IP range's gateway and as per the IP range's netmask."); + } + + if (checkOverlapPortableIpRange(regionId, startIP, endIP)) { + throw new InvalidParameterValueException("Ip range: " + startIP + "-" + endIP + " overlaps with a portable" + + " IP range already configured in the region " + regionId); + } + + if (vlanId == null) { + vlanId = Vlan.UNTAGGED; + } else { + if (!NetUtils.isValidVlan(vlanId)) { + throw new InvalidParameterValueException("Invalid vlan id " + vlanId); + } + } + GlobalLock portableIpLock = GlobalLock.getInternLock("PortablePublicIpRange"); + portableIpLock.lock(5); + Transaction txn = Transaction.currentTxn(); + txn.start(); + + PortableIpRangeVO portableIpRange = new PortableIpRangeVO(regionId, vlanId, gateway, netmask, startIP, endIP); + portableIpRange = _portableIpRangeDao.persist(portableIpRange); + + long startIpLong = NetUtils.ip2Long(startIP); + long endIpLong = NetUtils.ip2Long(endIP); + while(startIpLong <= endIpLong) { + PortableIpVO portableIP = new PortableIpVO(regionId, portableIpRange.getId(), vlanId, + gateway, netmask,NetUtils.long2Ip(startIpLong)); + _portableIpDao.persist(portableIP); + startIpLong++; + } + + txn.commit(); + portableIpLock.unlock(); + return portableIpRange; + } + + @Override + @DB + @ActionEvent(eventType = EventTypes.EVENT_PORTABLE_IP_RANGE_DELETE, eventDescription = "deleting portable ip range", async = false) + public boolean deletePortableIpRange(DeletePortableIpRangeCmd cmd) { + long rangeId = cmd.getId(); + PortableIpRangeVO portableIpRange = _portableIpRangeDao.findById(rangeId); + if (portableIpRange == null) { + throw new InvalidParameterValueException("Please specify a valid portable IP range id."); + } + + List fullIpRange = _portableIpDao.listByRangeId(portableIpRange.getId()); + List freeIpRange = _portableIpDao.listByRangeIdAndState(portableIpRange.getId(), PortableIp.State.Free); + + if (fullIpRange != null && freeIpRange != null) { + if (fullIpRange.size() == freeIpRange.size()) { + _portableIpRangeDao.expunge(portableIpRange.getId()); + return true; + } else { + throw new InvalidParameterValueException("Can't delete portable IP range as there are IP's assigned."); + } + } + + return false; + } + + @Override + public List listPortableIpRanges(ListPortableIpRangesCmd cmd) { + Integer regionId = cmd.getRegionIdId(); + Long rangeId = cmd.getPortableIpRangeId(); + + List ranges = new ArrayList(); + if (regionId != null) { + Region region = _regionDao.findById(regionId); + if (region == null) { + throw new InvalidParameterValueException("Invalid region ID: " + regionId); + } + return _portableIpRangeDao.listByRegionId(regionId); + } + + if (rangeId != null) { + PortableIpRangeVO range = _portableIpRangeDao.findById(rangeId); + if (range == null) { + throw new InvalidParameterValueException("Invalid portable IP range ID: " + regionId); + } + ranges.add(range); + return ranges; + } + + return _portableIpRangeDao.listAll(); + } + + @Override + public List listPortableIps(long id) { + + PortableIpRangeVO portableIpRange = _portableIpRangeDao.findById(id); + if (portableIpRange == null) { + throw new InvalidParameterValueException("Please specify a valid portable IP range id."); + } + + return _portableIpDao.listByRangeId(portableIpRange.getId()); + } + + private boolean checkOverlapPortableIpRange(int regionId, String newStartIpStr, String newEndIpStr) { + long newStartIp = NetUtils.ip2Long(newStartIpStr); + long newEndIp = NetUtils.ip2Long(newEndIpStr); + + List existingPortableIPRanges = _portableIpRangeDao.listByRegionId(regionId); + for (PortableIpRangeVO portableIpRange : existingPortableIPRanges) { + String ipRangeStr = portableIpRange.getIpRange(); + String[] range = ipRangeStr.split("-"); + long startip = NetUtils.ip2Long(range[0]); + long endIp = NetUtils.ip2Long(range[1]); + + if ((newStartIp >= startip && newStartIp <= endIp) || (newEndIp >= startip && newEndIp <= endIp)) { + return true; + } + + if ((startip >= newStartIp && startip <= newEndIp) || (endIp >= newStartIp && endIp <= newEndIp)) { + return true; + } + } + return false; + } } diff --git a/server/src/com/cloud/network/NetworkManager.java b/server/src/com/cloud/network/NetworkManager.java index 08198ee40e6..1e4fb8437ce 100755 --- a/server/src/com/cloud/network/NetworkManager.java +++ b/server/src/com/cloud/network/NetworkManager.java @@ -59,6 +59,7 @@ import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachine.Type; import com.cloud.vm.VirtualMachineProfile; +import org.apache.cloudstack.region.PortableIp; /** * NetworkManager manages the network for the different end users. @@ -240,6 +241,16 @@ public interface NetworkManager { IPAddressVO associateIPToGuestNetwork(long ipAddrId, long networkId, boolean releaseOnFailure) throws ResourceAllocationException, ResourceUnavailableException, InsufficientAddressCapacityException, ConcurrentOperationException; + IPAddressVO associatePortableIPToGuestNetwork(long ipAddrId, long networkId, boolean releaseOnFailure) throws ResourceAllocationException, ResourceUnavailableException, + InsufficientAddressCapacityException, ConcurrentOperationException; + + IPAddressVO disassociatePortableIPToGuestNetwork(long ipAddrId, long networkId) throws ResourceAllocationException, ResourceUnavailableException, + InsufficientAddressCapacityException, ConcurrentOperationException; + + boolean isPortableIpTransferableFromNetwork(long ipAddrId, long networkId); + + void transferPortableIP(long ipAddrId, long currentNetworkId, long newNetworkId) throws ResourceAllocationException, ResourceUnavailableException, + InsufficientAddressCapacityException, ConcurrentOperationException;; /** * @param network @@ -325,6 +336,9 @@ public interface NetworkManager { DataCenter zone) throws ConcurrentOperationException, ResourceAllocationException, InsufficientAddressCapacityException; + IpAddress allocatePortableIp(Account ipOwner, Account caller, long dcId, Long networkId, Long vpcID) + throws ConcurrentOperationException, ResourceAllocationException, InsufficientAddressCapacityException; + Map finalizeServicesAndProvidersForNetwork(NetworkOffering offering, Long physicalNetworkId); diff --git a/server/src/com/cloud/network/NetworkManagerImpl.java b/server/src/com/cloud/network/NetworkManagerImpl.java index 40fc3d30154..b3df0da8ebe 100755 --- a/server/src/com/cloud/network/NetworkManagerImpl.java +++ b/server/src/com/cloud/network/NetworkManagerImpl.java @@ -105,6 +105,10 @@ import com.cloud.vm.VirtualMachine.Type; import com.cloud.vm.dao.*; import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.acl.SecurityChecker.AccessType; +import org.apache.cloudstack.region.PortableIp; +import org.apache.cloudstack.region.PortableIpDao; +import org.apache.cloudstack.region.PortableIpVO; +import org.apache.cloudstack.region.Region; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; @@ -251,6 +255,8 @@ public class NetworkManagerImpl extends ManagerBase implements NetworkManager, L UserIpv6AddressDao _ipv6Dao; @Inject Ipv6AddressManager _ipv6Mgr; + @Inject + PortableIpDao _portableIpDao; protected StateMachine2 _stateMachine; private final HashMap _systemNetworks = new HashMap(5); @@ -702,6 +708,62 @@ public class NetworkManagerImpl extends ManagerBase implements NetworkManager, L return ip; } + @Override + @DB + public IpAddress allocatePortableIp(Account ipOwner, Account caller, long dcId, Long networkId, Long vpcID) + throws ConcurrentOperationException, ResourceAllocationException, InsufficientAddressCapacityException { + + Transaction txn = Transaction.currentTxn(); + GlobalLock portableIpLock = GlobalLock.getInternLock("PortablePublicIpRange"); + PortableIpVO allocatedPortableIp; + IPAddressVO ipaddr; + + try { + portableIpLock.lock(5); + + txn.start(); + //TODO: get the region ID corresponding to running management server + List portableIpVOs = _portableIpDao.listByRegionIdAndState(1, PortableIp.State.Free); + if (portableIpVOs == null || portableIpVOs.isEmpty()) { + InsufficientAddressCapacityException ex = new InsufficientAddressCapacityException + ("Unable to find available portable IP addresses", Region.class, new Long(1)); + throw ex; + } + + // allocate first portable IP to the user + allocatedPortableIp = portableIpVOs.get(0); + allocatedPortableIp.setAllocatedTime(new Date()); + allocatedPortableIp.setAllocatedToAccountId(ipOwner.getAccountId()); + allocatedPortableIp.setAllocatedInDomainId(ipOwner.getDomainId()); + allocatedPortableIp.setState(PortableIp.State.Allocated); + _portableIpDao.update(allocatedPortableIp.getId(), allocatedPortableIp); + + // provision portable IP range VLAN + long physicalNetworkId = _networkModel.getDefaultPhysicalNetworkByZoneAndTrafficType(dcId, TrafficType.Public).getId(); + Network network = _networkModel.getNetwork(physicalNetworkId); + String range = allocatedPortableIp.getAddress() + "-" + allocatedPortableIp.getAddress(); + VlanVO vlan = new VlanVO(VlanType.VirtualNetwork, allocatedPortableIp.getVlan(), allocatedPortableIp.getGateway(), + allocatedPortableIp.getNetmask(), dcId, range, network.getId(), network.getId(), null, null, null); + vlan = _vlanDao.persist(vlan); + + // provision the portable IP in to user_ip_address table + ipaddr = new IPAddressVO(new Ip(allocatedPortableIp.getAddress()), dcId, networkId, vpcID, network.getId(), + network.getId(), vlan.getId(), true); + ipaddr.setState(State.Allocated); + ipaddr.setAllocatedTime(new Date()); + ipaddr.setAllocatedInDomainId(ipOwner.getDomainId()); + ipaddr.setAllocatedToAccountId(ipOwner.getId()); + ipaddr= _ipAddressDao.persist(ipaddr); + + txn.commit(); + + } finally { + portableIpLock.unlock(); + } + + return ipaddr; + } + protected IPAddressVO getExistingSourceNatInNetwork(long ownerId, Long networkId) { List addrs = _networkModel.listPublicIpsAssignedToGuestNtwk(ownerId, networkId, true); @@ -741,12 +803,14 @@ public class NetworkManagerImpl extends ManagerBase implements NetworkManager, L } DataCenter zone = _configMgr.getZone(network.getDataCenterId()); - if (network.getGuestType() == Network.GuestType.Shared && zone.getNetworkType() == NetworkType.Advanced) { - if (isSharedNetworkOfferingWithServices(network.getNetworkOfferingId())) { - _accountMgr.checkAccess(UserContext.current().getCaller(), AccessType.UseNetwork, false, network); - } else { - throw new InvalidParameterValueException("IP can be associated with guest network of 'shared' type only if " + - "network services Source Nat, Static Nat, Port Forwarding, Load balancing, firewall are enabled in the network"); + if (zone.getNetworkType() == NetworkType.Advanced) { + if (network.getGuestType() == Network.GuestType.Shared) { + if (isSharedNetworkOfferingWithServices(network.getNetworkOfferingId())) { + _accountMgr.checkAccess(UserContext.current().getCaller(), AccessType.UseNetwork, false, network); + } else { + throw new InvalidParameterValueException("IP can be associated with guest network of 'shared' type only if " + + "network services Source Nat, Static Nat, Port Forwarding, Load balancing, firewall are enabled in the network"); + } } } else { _accountMgr.checkAccess(caller, null, true, ipToAssoc); @@ -844,6 +908,162 @@ public class NetworkManagerImpl extends ManagerBase implements NetworkManager, L } } + @Override + public IPAddressVO associatePortableIPToGuestNetwork(long ipAddrId, long networkId, boolean releaseOnFailure) throws ResourceAllocationException, ResourceUnavailableException, + InsufficientAddressCapacityException, ConcurrentOperationException { + return associateIPToGuestNetwork(ipAddrId, networkId, releaseOnFailure); + } + + @DB + @Override + public IPAddressVO disassociatePortableIPToGuestNetwork(long ipId, long networkId) + throws ResourceAllocationException, ResourceUnavailableException, + InsufficientAddressCapacityException, ConcurrentOperationException { + + Account caller = UserContext.current().getCaller(); + Account owner = null; + + Network network = _networksDao.findById(networkId); + if (network == null) { + throw new InvalidParameterValueException("Invalid network id is given"); + } + + IPAddressVO ipToAssoc = _ipAddressDao.findById(ipId); + if (ipToAssoc != null) { + + if (ipToAssoc.getAssociatedWithNetworkId() == null) { + throw new InvalidParameterValueException("IP " + ipToAssoc + " is not associated with any network"); + } + + if (ipToAssoc.getAssociatedWithNetworkId() != network.getId()) { + throw new InvalidParameterValueException("IP " + ipToAssoc + " is not associated with network id" + networkId); + } + + DataCenter zone = _configMgr.getZone(network.getDataCenterId()); + if (zone.getNetworkType() == NetworkType.Advanced) { + if (network.getGuestType() == Network.GuestType.Shared) { + assert (isSharedNetworkOfferingWithServices(network.getNetworkOfferingId())); + _accountMgr.checkAccess(UserContext.current().getCaller(), AccessType.UseNetwork, false, network); + } + } else { + _accountMgr.checkAccess(caller, null, true, ipToAssoc); + } + owner = _accountMgr.getAccount(ipToAssoc.getAllocatedToAccountId()); + } else { + s_logger.debug("Unable to find ip address by id: " + ipId); + return null; + } + + DataCenter zone = _configMgr.getZone(network.getDataCenterId()); + + // Check that network belongs to IP owner - skip this check + // - if zone is basic zone as there is just one guest network, + // - if shared network in Advanced zone + // - and it belongs to the system + if (network.getAccountId() != owner.getId()) { + if (zone.getNetworkType() != NetworkType.Basic && !(zone.getNetworkType() == NetworkType.Advanced && network.getGuestType() == Network.GuestType.Shared)) { + throw new InvalidParameterValueException("The owner of the network is not the same as owner of the IP"); + } + } + + // Check if IP has any services (rules) associated in the network + List ipList = new ArrayList(); + PublicIp publicIp = PublicIp.createFromAddrAndVlan(ipToAssoc, _vlanDao.findById(ipToAssoc.getVlanId())); + ipList.add(publicIp); + Map> ipToServices = _networkModel.getIpToServices(ipList, false, true); + if (ipToServices != null & !ipToServices.isEmpty()) { + Set services = ipToServices.get(publicIp); + if (services != null && !services.isEmpty()) { + throw new InvalidParameterValueException("IP " + ipToAssoc + " has services and rules associated in the network " + networkId); + } + } + + IPAddressVO ip = _ipAddressDao.findById(ipId); + ip.setAssociatedWithNetworkId(null); + _ipAddressDao.update(ipId, ip); + + try { + boolean success = applyIpAssociations(network, false); + if (success) { + s_logger.debug("Successfully associated ip address " + ip.getAddress().addr() + " to network " + network); + } else { + s_logger.warn("Failed to associate ip address " + ip.getAddress().addr() + " to network " + network); + } + return ip; + } finally { + + } + } + + @Override + public boolean isPortableIpTransferableFromNetwork(long ipAddrId, long networkId) { + Network network = _networksDao.findById(networkId); + if (network == null) { + throw new InvalidParameterValueException("Invalid network id is given"); + } + + IPAddressVO ip = _ipAddressDao.findById(ipAddrId); + if (ip == null) { + throw new InvalidParameterValueException("Invalid network id is given"); + } + + // Check if IP has any services (rules) associated in the network + List ipList = new ArrayList(); + PublicIp publicIp = PublicIp.createFromAddrAndVlan(ip, _vlanDao.findById(ip.getVlanId())); + ipList.add(publicIp); + Map> ipToServices = _networkModel.getIpToServices(ipList, false, true); + if (ipToServices != null & !ipToServices.isEmpty()) { + Set ipServices = ipToServices.get(publicIp); + if (ipServices != null && !ipServices.isEmpty()) { + return false; + } + } + + return true; + } + + @DB + @Override + public void transferPortableIP(long ipAddrId, long currentNetworkId, long newNetworkId) throws ResourceAllocationException, ResourceUnavailableException, + InsufficientAddressCapacityException, ConcurrentOperationException { + + Network srcNetwork = _networksDao.findById(currentNetworkId); + if (srcNetwork == null) { + throw new InvalidParameterValueException("Invalid source network id " + currentNetworkId +" is given"); + } + + Network dstNetwork = _networksDao.findById(newNetworkId); + if (dstNetwork == null) { + throw new InvalidParameterValueException("Invalid source network id " + newNetworkId +" is given"); + } + + IPAddressVO ip = _ipAddressDao.findById(ipAddrId); + if (ip == null) { + throw new InvalidParameterValueException("Invalid portable ip address id is given"); + } + + Transaction txn = Transaction.currentTxn(); + txn.start(); + + assert(isPortableIpTransferableFromNetwork(ipAddrId, currentNetworkId)); + + if (srcNetwork.getVpcId() != null) { + _vpcMgr.unassignIPFromVpcNetwork(ipAddrId, currentNetworkId); + } else { + disassociatePortableIPToGuestNetwork(ipAddrId, currentNetworkId); + } + + associatePortableIPToGuestNetwork(ipAddrId, newNetworkId, false); + + if (dstNetwork.getVpcId() != null) { + ip.setVpcId(dstNetwork.getVpcId()); + } else { + ip.setVpcId(null); + } + + _ipAddressDao.update(ipAddrId, ip); + txn.commit(); + } @Override @DB @@ -884,12 +1104,42 @@ public class NetworkManagerImpl extends ManagerBase implements NetworkManager, L } if (success) { + if (ip.isPortable()) { + releasePortableIpAddress(addrId); + } s_logger.debug("Released a public ip id=" + addrId); } return success; } + @DB + private void releasePortableIpAddress(long addrId) { + Transaction txn = Transaction.currentTxn(); + GlobalLock portableIpLock = GlobalLock.getInternLock("PortablePublicIpRange"); + + txn.start(); + try { + portableIpLock.lock(5); + IPAddressVO ip = _ipAddressDao.findById(addrId); + + // unassign portable IP + PortableIpVO portableIp = _portableIpDao.findByIpAddress(ip.getAddress().addr()); + _portableIpDao.unassignIpAddress(portableIp.getId()); + + // removed the provisioned vlan + VlanVO vlan = _vlanDao.findById(ip.getVlanId()); + _vlanDao.expunge(vlan.getId()); + + // remove the provisioned public ip address + _ipAddressDao.expunge(ip.getId()); + + txn.commit(); + } finally { + portableIpLock.releaseRef(); + } + } + @Override @DB public boolean configure(final String name, final Map params) throws ConfigurationException { diff --git a/server/src/com/cloud/network/NetworkServiceImpl.java b/server/src/com/cloud/network/NetworkServiceImpl.java index ed9a8c4ece7..1533ca9bc4f 100755 --- a/server/src/com/cloud/network/NetworkServiceImpl.java +++ b/server/src/com/cloud/network/NetworkServiceImpl.java @@ -39,6 +39,7 @@ import javax.ejb.Local; import javax.inject.Inject; import javax.naming.ConfigurationException; +import com.cloud.network.vpc.dao.VpcDao; import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.acl.SecurityChecker; import org.apache.cloudstack.acl.SecurityChecker.AccessType; @@ -184,32 +185,6 @@ import com.cloud.vm.dao.NicSecondaryIpDao; import com.cloud.vm.dao.NicSecondaryIpVO; import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.VMInstanceDao; -import com.cloud.vm.*; -import com.cloud.vm.dao.*; -import org.apache.cloudstack.acl.ControlledEntity.ACLType; -import org.apache.cloudstack.acl.SecurityChecker; -import org.apache.cloudstack.acl.SecurityChecker.AccessType; -import org.apache.cloudstack.api.command.admin.network.DedicateGuestVlanRangeCmd; -import org.apache.cloudstack.api.command.admin.network.ListDedicatedGuestVlanRangesCmd; -import org.apache.cloudstack.api.command.admin.usage.ListTrafficTypeImplementorsCmd; -import org.apache.cloudstack.api.command.user.network.CreateNetworkCmd; -import org.apache.cloudstack.api.command.user.network.ListNetworksCmd; -import org.apache.cloudstack.api.command.user.network.RestartNetworkCmd; -import org.apache.cloudstack.api.command.user.vm.ListNicsCmd; -import org.apache.log4j.Logger; -import org.springframework.stereotype.Component; - -import javax.ejb.Local; -import javax.inject.Inject; -import javax.naming.ConfigurationException; -import java.net.Inet6Address; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.security.InvalidParameterException; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.*; /** @@ -315,6 +290,8 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService { @Inject AccountGuestVlanMapDao _accountGuestVlanMapDao; @Inject + VpcDao _vpcDao; + @Inject NetworkACLDao _networkACLDao; int _cidrLimit; @@ -527,22 +504,23 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService { public IpAddress allocateIP(Account ipOwner, long zoneId, Long networkId) throws ResourceAllocationException, InsufficientAddressCapacityException, ConcurrentOperationException { + Account caller = UserContext.current().getCaller(); + long callerUserId = UserContext.current().getCallerUserId(); + DataCenter zone = _configMgr.getZone(zoneId); + if (networkId != null) { Network network = _networksDao.findById(networkId); if (network == null) { throw new InvalidParameterValueException("Invalid network id is given"); } + if (network.getGuestType() == Network.GuestType.Shared) { - DataCenter zone = _configMgr.getZone(zoneId); if (zone == null) { throw new InvalidParameterValueException("Invalid zone Id is given"); } - // if shared network in the advanced zone, then check the caller against the network for 'AccessType.UseNetwork' if (zone.getNetworkType() == NetworkType.Advanced) { if (isSharedNetworkOfferingWithServices(network.getNetworkOfferingId())) { - Account caller = UserContext.current().getCaller(); - long callerUserId = UserContext.current().getCallerUserId(); _accountMgr.checkAccess(caller, AccessType.UseNetwork, false, network); if (s_logger.isDebugEnabled()) { s_logger.debug("Associate IP address called by the user " + callerUserId + " account " + ipOwner.getId()); @@ -554,20 +532,67 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService { } } } + } else { + _accountMgr.checkAccess(caller, null, false, ipOwner); } - return allocateIP(ipOwner, false, zoneId); + return _networkMgr.allocateIp(ipOwner, false, caller, callerUserId, zone); } - public IpAddress allocateIP(Account ipOwner, boolean isSystem, long zoneId) + @Override + @ActionEvent(eventType = EventTypes.EVENT_PORTABLE_IP_ASSIGN, eventDescription = "allocating portable public Ip", create = true) + public IpAddress allocatePortableIP(Account ipOwner, int regionId, Long zoneId, Long networkId, Long vpcId) throws ResourceAllocationException, InsufficientAddressCapacityException, ConcurrentOperationException { Account caller = UserContext.current().getCaller(); - // check permissions - _accountMgr.checkAccess(caller, null, false, ipOwner); long callerUserId = UserContext.current().getCallerUserId(); DataCenter zone = _configMgr.getZone(zoneId); - return _networkMgr.allocateIp(ipOwner, isSystem, caller, callerUserId, zone); + if ((networkId == null && vpcId == null) && (networkId != null && vpcId != null)) { + throw new InvalidParameterValueException("One of Network id or VPC is should be passed"); + } + + if (networkId != null) { + Network network = _networksDao.findById(networkId); + if (network == null) { + throw new InvalidParameterValueException("Invalid network id is given"); + } + + if (network.getGuestType() == Network.GuestType.Shared) { + if (zone == null) { + throw new InvalidParameterValueException("Invalid zone Id is given"); + } + // if shared network in the advanced zone, then check the caller against the network for 'AccessType.UseNetwork' + if (zone.getNetworkType() == NetworkType.Advanced) { + if (isSharedNetworkOfferingWithServices(network.getNetworkOfferingId())) { + _accountMgr.checkAccess(caller, AccessType.UseNetwork, false, network); + if (s_logger.isDebugEnabled()) { + s_logger.debug("Associate IP address called by the user " + callerUserId + " account " + ipOwner.getId()); + } + return _networkMgr.allocatePortableIp(ipOwner, caller, zoneId, networkId, null); + } else { + throw new InvalidParameterValueException("Associate IP address can only be called on the shared networks in the advanced zone" + + " with Firewall/Source Nat/Static Nat/Port Forwarding/Load balancing services enabled"); + } + } + } + } + + if (vpcId != null) { + Vpc vpc = _vpcDao.findById(vpcId); + if (vpc != null) { + throw new InvalidParameterValueException("Invalid vpc id is given"); + } + } + + _accountMgr.checkAccess(caller, null, false, ipOwner); + + return _networkMgr.allocatePortableIp(ipOwner, caller, zoneId, null, null); + } + + @Override + @ActionEvent(eventType = EventTypes.EVENT_PORTABLE_IP_RELEASE, eventDescription = "disassociating portable Ip", async = true) + public boolean releasePortableIpAddress(long ipAddressId) throws InsufficientAddressCapacityException { + return releaseIpAddressInternal(ipAddressId); } @Override @@ -810,9 +835,13 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService { } @Override - @DB @ActionEvent(eventType = EventTypes.EVENT_NET_IP_RELEASE, eventDescription = "disassociating Ip", async = true) public boolean releaseIpAddress(long ipAddressId) throws InsufficientAddressCapacityException { + return releaseIpAddressInternal(ipAddressId); + } + + @DB + private boolean releaseIpAddressInternal(long ipAddressId) throws InsufficientAddressCapacityException { Long userId = UserContext.current().getCallerUserId(); Account caller = UserContext.current().getCaller(); @@ -851,6 +880,9 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService { boolean success = _networkMgr.disassociatePublicIpAddress(ipAddressId, userId, caller); if (success) { + if (!ipVO.isPortable()) { + return success; + } Long networkId = ipVO.getAssociatedWithNetworkId(); if (networkId != null) { Network guestNetwork = getNetwork(networkId); @@ -867,7 +899,6 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService { return success; } - @Override @DB public Network getNetwork(long id) { diff --git a/server/src/com/cloud/network/addr/PublicIp.java b/server/src/com/cloud/network/addr/PublicIp.java index c753b4927c8..b18c6912003 100644 --- a/server/src/com/cloud/network/addr/PublicIp.java +++ b/server/src/com/cloud/network/addr/PublicIp.java @@ -220,6 +220,15 @@ public class PublicIp implements PublicIpAddress { return _addr.getVmIp(); } + @Override + public boolean isPortable() { + return _addr.isPortable(); + } + + public void setPortable(boolean portable) { + _addr.setPortable(portable); + } + public Long getIpMacAddress() { return _addr.getMacAddress(); } diff --git a/server/src/com/cloud/network/rules/RulesManagerImpl.java b/server/src/com/cloud/network/rules/RulesManagerImpl.java index c9b47b44bab..883455377f4 100755 --- a/server/src/com/cloud/network/rules/RulesManagerImpl.java +++ b/server/src/com/cloud/network/rules/RulesManagerImpl.java @@ -491,6 +491,49 @@ public class RulesManagerImpl extends ManagerBase implements RulesManager, Rules return false; } performedIpAssoc = true; + } else if (ipAddress.isPortable()) { + s_logger.info("Portable IP " + ipAddress.getUuid() + " is not associated with the network, so" + + "associate IP with the network " + networkId); + try { + // check if StaticNat service is enabled in the network + _networkModel.checkIpForService(ipAddress, Service.StaticNat, networkId); + + // associate portable IP to vpc, if network is part of VPC + if (network.getVpcId() != null) { + _vpcMgr.associateIPToVpc(ipId, network.getVpcId()); + } + + // associate portable IP with guest network + _networkMgr.associatePortableIPToGuestNetwork(ipId, networkId, false); + } catch (Exception e) { + s_logger.warn("Failed to associate portable id=" + ipId + " to network id=" + networkId + " as " + + "a part of enable static nat"); + return false; + } + performedIpAssoc = true; + } + } else if (ipAddress.getAssociatedWithNetworkId() != networkId) { + if (ipAddress.isPortable()) { + // check if destination network has StaticNat service enabled + _networkModel.checkIpForService(ipAddress, Service.StaticNat, networkId); + + // check if portable IP can be transferred across the networks + if (_networkMgr.isPortableIpTransferableFromNetwork(ipId, ipAddress.getAssociatedWithNetworkId() )) { + try { + _networkMgr.transferPortableIP(ipId, ipAddress.getAssociatedWithNetworkId(), networkId); + } catch (Exception e) { + s_logger.warn("Failed to associate portable id=" + ipId + " to network id=" + networkId + " as " + + "a part of enable static nat"); + return false; + } + } else { + throw new InvalidParameterValueException("Portable IP: " + ipId + " has associated services" + + "in network " + ipAddress.getAssociatedWithNetworkId() + " so can not be transferred to " + + " network " + networkId); + } + } else { + throw new InvalidParameterValueException("Invalid network Id=" + networkId + ". IP is associated with" + + " a different network than passed network id"); } } else { _networkModel.checkIpForService(ipAddress, Service.StaticNat, null); diff --git a/server/src/com/cloud/server/ManagementServerImpl.java b/server/src/com/cloud/server/ManagementServerImpl.java index 8b3eea4a1f2..8323af84981 100755 --- a/server/src/com/cloud/server/ManagementServerImpl.java +++ b/server/src/com/cloud/server/ManagementServerImpl.java @@ -47,6 +47,14 @@ import com.cloud.exception.*; import com.cloud.vm.*; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.SecurityChecker.AccessType; +import org.apache.cloudstack.api.ApiConstants; + +import com.cloud.event.ActionEventUtils; +import org.apache.cloudstack.api.BaseUpdateTemplateOrIsoCmd; +import org.apache.cloudstack.api.command.admin.region.*; +import org.apache.cloudstack.api.response.ExtractResponse; +import org.apache.commons.codec.binary.Base64; +import org.apache.log4j.Logger; import org.apache.cloudstack.affinity.AffinityGroupProcessor; import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; import org.apache.cloudstack.api.ApiConstants; @@ -124,9 +132,6 @@ import org.apache.cloudstack.api.command.admin.pod.CreatePodCmd; import org.apache.cloudstack.api.command.admin.pod.DeletePodCmd; import org.apache.cloudstack.api.command.admin.pod.ListPodsByCmd; import org.apache.cloudstack.api.command.admin.pod.UpdatePodCmd; -import org.apache.cloudstack.api.command.admin.region.AddRegionCmd; -import org.apache.cloudstack.api.command.admin.region.RemoveRegionCmd; -import org.apache.cloudstack.api.command.admin.region.UpdateRegionCmd; import org.apache.cloudstack.api.command.admin.resource.ArchiveAlertsCmd; import org.apache.cloudstack.api.command.admin.resource.DeleteAlertsCmd; import org.apache.cloudstack.api.command.admin.resource.ListAlertsCmd; @@ -571,8 +576,78 @@ import com.cloud.vm.dao.VMInstanceDao; import edu.emory.mathcs.backport.java.util.Arrays; import edu.emory.mathcs.backport.java.util.Collections; -import org.apache.cloudstack.api.command.admin.config.ListDeploymentPlannersCmd; +import org.apache.cloudstack.acl.ControlledEntity; +import org.apache.cloudstack.api.command.admin.autoscale.CreateCounterCmd; +import org.apache.cloudstack.api.command.admin.autoscale.DeleteCounterCmd; +import org.apache.cloudstack.api.command.admin.cluster.AddClusterCmd; +import org.apache.cloudstack.api.command.admin.cluster.DeleteClusterCmd; +import org.apache.cloudstack.api.command.admin.cluster.ListClustersCmd; +import org.apache.cloudstack.api.command.admin.cluster.UpdateClusterCmd; +import org.apache.cloudstack.api.command.admin.config.ListCfgsByCmd; +import org.apache.cloudstack.api.command.admin.config.ListHypervisorCapabilitiesCmd; +import org.apache.cloudstack.api.command.admin.config.UpdateCfgCmd; +import org.apache.cloudstack.api.command.admin.config.UpdateHypervisorCapabilitiesCmd; +import org.apache.cloudstack.api.command.admin.ldap.LDAPConfigCmd; +import org.apache.cloudstack.api.command.admin.ldap.LDAPRemoveCmd; +import org.apache.cloudstack.api.command.admin.pod.CreatePodCmd; +import org.apache.cloudstack.api.command.admin.pod.DeletePodCmd; +import org.apache.cloudstack.api.command.admin.pod.ListPodsByCmd; +import org.apache.cloudstack.api.command.admin.pod.UpdatePodCmd; +import org.apache.cloudstack.api.command.admin.swift.AddSwiftCmd; +import org.apache.cloudstack.api.command.admin.swift.ListSwiftsCmd; +import org.apache.cloudstack.api.command.admin.template.PrepareTemplateCmd; +import org.apache.cloudstack.api.command.admin.vlan.CreateVlanIpRangeCmd; +import org.apache.cloudstack.api.command.admin.vlan.DeleteVlanIpRangeCmd; +import org.apache.cloudstack.api.command.admin.vlan.ListVlanIpRangesCmd; +import org.apache.cloudstack.api.command.admin.vm.AssignVMCmd; +import org.apache.cloudstack.api.command.admin.vm.MigrateVMCmd; +import org.apache.cloudstack.api.command.admin.vm.MigrateVirtualMachineWithVolumeCmd; +import org.apache.cloudstack.api.command.admin.vm.RecoverVMCmd; +import org.apache.cloudstack.api.command.admin.zone.CreateZoneCmd; +import org.apache.cloudstack.api.command.admin.zone.DeleteZoneCmd; +import org.apache.cloudstack.api.command.admin.zone.MarkDefaultZoneForAccountCmd; +import org.apache.cloudstack.api.command.admin.zone.UpdateZoneCmd; +import org.apache.cloudstack.api.command.user.account.AddAccountToProjectCmd; +import org.apache.cloudstack.api.command.user.account.DeleteAccountFromProjectCmd; +import org.apache.cloudstack.api.command.user.account.ListAccountsCmd; +import org.apache.cloudstack.api.command.user.account.ListProjectAccountsCmd; +import org.apache.cloudstack.api.command.user.address.AssociateIPAddrCmd; +import org.apache.cloudstack.api.command.user.address.DisassociateIPAddrCmd; +import org.apache.cloudstack.api.command.user.address.ListPublicIpAddressesCmd; +import org.apache.cloudstack.api.command.user.config.ListCapabilitiesCmd; +import org.apache.cloudstack.api.command.user.event.ArchiveEventsCmd; +import org.apache.cloudstack.api.command.user.event.DeleteEventsCmd; +import org.apache.cloudstack.api.command.user.event.ListEventTypesCmd; +import org.apache.cloudstack.api.command.user.event.ListEventsCmd; +import org.apache.cloudstack.api.command.user.guest.ListGuestOsCategoriesCmd; +import org.apache.cloudstack.api.command.user.guest.ListGuestOsCmd; +import org.apache.cloudstack.api.command.user.job.ListAsyncJobsCmd; +import org.apache.cloudstack.api.command.user.job.QueryAsyncJobResultCmd; +import org.apache.cloudstack.api.command.user.offering.ListDiskOfferingsCmd; +import org.apache.cloudstack.api.command.user.offering.ListServiceOfferingsCmd; +import org.apache.cloudstack.api.command.user.region.ListRegionsCmd; +import org.apache.cloudstack.api.command.user.region.ha.gslb.*; +import org.apache.cloudstack.api.command.user.ssh.CreateSSHKeyPairCmd; +import org.apache.cloudstack.api.command.user.ssh.DeleteSSHKeyPairCmd; +import org.apache.cloudstack.api.command.user.ssh.ListSSHKeyPairsCmd; +import org.apache.cloudstack.api.command.user.ssh.RegisterSSHKeyPairCmd; +import org.apache.cloudstack.api.command.user.tag.CreateTagsCmd; +import org.apache.cloudstack.api.command.user.tag.DeleteTagsCmd; +import org.apache.cloudstack.api.command.user.tag.ListTagsCmd; +import org.apache.cloudstack.api.command.user.vmgroup.CreateVMGroupCmd; +import org.apache.cloudstack.api.command.user.vmgroup.DeleteVMGroupCmd; +import org.apache.cloudstack.api.command.user.vmgroup.ListVMGroupsCmd; +import org.apache.cloudstack.api.command.user.vmgroup.UpdateVMGroupCmd; +import org.apache.cloudstack.api.command.user.vmsnapshot.CreateVMSnapshotCmd; +import org.apache.cloudstack.api.command.user.vmsnapshot.DeleteVMSnapshotCmd; +import org.apache.cloudstack.api.command.user.vmsnapshot.ListVMSnapshotCmd; +import org.apache.cloudstack.api.command.user.zone.ListZonesByCmd; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; +import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.cloudstack.api.command.admin.config.ListDeploymentPlannersCmd; public class ManagementServerImpl extends ManagerBase implements ManagementServer { public static final Logger s_logger = Logger.getLogger(ManagementServerImpl.class.getName()); @@ -2897,6 +2972,9 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe cmdList.add(ListAffinityGroupsCmd.class); cmdList.add(UpdateVMAffinityGroupCmd.class); cmdList.add(ListAffinityGroupTypesCmd.class); + cmdList.add(CreatePortableIpRangeCmd.class); + cmdList.add(DeletePortableIpRangeCmd.class); + cmdList.add(ListPortableIpRangesCmd.class); cmdList.add(ListDeploymentPlannersCmd.class); cmdList.add(ReleaseHostReservationCmd.class); cmdList.add(ScaleSystemVMCmd.class); diff --git a/server/src/org/apache/cloudstack/region/PortableIpDao.java b/server/src/org/apache/cloudstack/region/PortableIpDao.java new file mode 100755 index 00000000000..9f5341fb6ec --- /dev/null +++ b/server/src/org/apache/cloudstack/region/PortableIpDao.java @@ -0,0 +1,39 @@ +// 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.region; + +import java.util.List; + +import com.cloud.dc.Vlan; +import com.cloud.dc.Vlan.VlanType; +import com.cloud.dc.VlanVO; +import com.cloud.utils.db.GenericDao; + +public interface PortableIpDao extends GenericDao { + + List listByRegionId(int regionId); + + List listByRangeId(long rangeId); + + List listByRangeIdAndState(long rangeId, PortableIp.State state); + + List listByRegionIdAndState(int regionId, PortableIp.State state); + + PortableIpVO findByIpAddress(String ipAddress); + + void unassignIpAddress(long ipAddressId); +} diff --git a/server/src/org/apache/cloudstack/region/PortableIpDaoImpl.java b/server/src/org/apache/cloudstack/region/PortableIpDaoImpl.java new file mode 100755 index 00000000000..488761bc35f --- /dev/null +++ b/server/src/org/apache/cloudstack/region/PortableIpDaoImpl.java @@ -0,0 +1,131 @@ +// 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.region; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.ejb.Local; +import javax.inject.Inject; +import javax.naming.ConfigurationException; + +import org.springframework.stereotype.Component; + +import com.cloud.dc.AccountVlanMapVO; +import com.cloud.dc.PodVlanMapVO; +import com.cloud.dc.Vlan; +import com.cloud.dc.Vlan.VlanType; +import com.cloud.dc.VlanVO; +import com.cloud.network.dao.IPAddressDao; +import com.cloud.utils.Pair; +import com.cloud.utils.db.DB; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.JoinBuilder; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.exception.CloudRuntimeException; + +@Component +@Local(value={PortableIpDao.class}) +public class PortableIpDaoImpl extends GenericDaoBase implements PortableIpDao { + + private final SearchBuilder listByRegionIDSearch; + private final SearchBuilder listByRangeIDSearch; + private final SearchBuilder listByRangeIDAndStateSearch; + private final SearchBuilder listByRegionIDAndStateSearch; + private final SearchBuilder findByIpAddressSearch; + + public PortableIpDaoImpl() { + listByRegionIDSearch = createSearchBuilder(); + listByRegionIDSearch.and("regionId", listByRegionIDSearch.entity().getRegionId(), SearchCriteria.Op.EQ); + listByRegionIDSearch.done(); + + listByRangeIDSearch = createSearchBuilder(); + listByRangeIDSearch.and("rangeId", listByRangeIDSearch.entity().getRangeId(), SearchCriteria.Op.EQ); + listByRangeIDSearch.done(); + + listByRangeIDAndStateSearch = createSearchBuilder(); + listByRangeIDAndStateSearch.and("rangeId", listByRangeIDAndStateSearch.entity().getRangeId(), SearchCriteria.Op.EQ); + listByRangeIDAndStateSearch.and("state", listByRangeIDAndStateSearch.entity().getState(), SearchCriteria.Op.EQ); + listByRangeIDAndStateSearch.done(); + + listByRegionIDAndStateSearch = createSearchBuilder(); + listByRegionIDAndStateSearch.and("regionId", listByRegionIDAndStateSearch.entity().getRangeId(), SearchCriteria.Op.EQ); + listByRegionIDAndStateSearch.and("state", listByRegionIDAndStateSearch.entity().getState(), SearchCriteria.Op.EQ); + listByRegionIDAndStateSearch.done(); + + findByIpAddressSearch = createSearchBuilder(); + findByIpAddressSearch.and("address", findByIpAddressSearch.entity().getAddress(), SearchCriteria.Op.EQ); + findByIpAddressSearch.done(); + } + + @Override + public List listByRegionId(int regionIdId) { + SearchCriteria sc = listByRegionIDSearch.create(); + sc.setParameters("regionId", regionIdId); + return listBy(sc); + } + + @Override + public List listByRangeId(long rangeId) { + SearchCriteria sc = listByRangeIDSearch.create(); + sc.setParameters("rangeId", rangeId); + return listBy(sc); + } + + @Override + public List listByRangeIdAndState(long rangeId, PortableIp.State state) { + SearchCriteria sc = listByRangeIDAndStateSearch.create(); + sc.setParameters("rangeId", rangeId); + sc.setParameters("state", state); + return listBy(sc); + } + + @Override + public List listByRegionIdAndState(int regionId, PortableIp.State state) { + SearchCriteria sc = listByRegionIDAndStateSearch.create(); + sc.setParameters("regionId", regionId); + sc.setParameters("state", state); + return listBy(sc); + } + + @Override + public PortableIpVO findByIpAddress(String ipAddress) { + SearchCriteria sc = findByIpAddressSearch.create(); + sc.setParameters("address", ipAddress); + return findOneBy(sc); + } + + @Override + public void unassignIpAddress(long ipAddressId) { + PortableIpVO address = createForUpdate(); + address.setAllocatedToAccountId(null); + address.setAllocatedInDomainId(null); + address.setAllocatedTime(null); + address.setState(PortableIp.State.Free); + address.setAssociatedWithNetworkId(null); + address.setAssociatedDataCenterId(null); + address.setAssociatedWithVpcId(null); + address.setPhysicalNetworkId(null); + update(ipAddressId, address); + } +} diff --git a/server/src/org/apache/cloudstack/region/PortableIpRangeDao.java b/server/src/org/apache/cloudstack/region/PortableIpRangeDao.java new file mode 100755 index 00000000000..85da6c0a87e --- /dev/null +++ b/server/src/org/apache/cloudstack/region/PortableIpRangeDao.java @@ -0,0 +1,30 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.region; + +import java.util.List; + +import com.cloud.dc.Vlan; +import com.cloud.dc.Vlan.VlanType; +import com.cloud.dc.VlanVO; +import com.cloud.utils.db.GenericDao; + +public interface PortableIpRangeDao extends GenericDao { + + List listByRegionId(int regionId); + +} diff --git a/server/src/org/apache/cloudstack/region/PortableIpRangeDaoImpl.java b/server/src/org/apache/cloudstack/region/PortableIpRangeDaoImpl.java new file mode 100755 index 00000000000..496c9e6da40 --- /dev/null +++ b/server/src/org/apache/cloudstack/region/PortableIpRangeDaoImpl.java @@ -0,0 +1,65 @@ +// 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.region; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.ejb.Local; +import javax.inject.Inject; +import javax.naming.ConfigurationException; + +import org.springframework.stereotype.Component; + +import com.cloud.dc.AccountVlanMapVO; +import com.cloud.dc.PodVlanMapVO; +import com.cloud.dc.Vlan; +import com.cloud.dc.Vlan.VlanType; +import com.cloud.dc.VlanVO; +import com.cloud.network.dao.IPAddressDao; +import com.cloud.utils.Pair; +import com.cloud.utils.db.DB; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.JoinBuilder; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.exception.CloudRuntimeException; + +@Component +@Local(value={PortableIpRangeDao.class}) +public class PortableIpRangeDaoImpl extends GenericDaoBase implements PortableIpRangeDao { + + private final SearchBuilder listByRegionIDSearch; + + public PortableIpRangeDaoImpl() { + listByRegionIDSearch = createSearchBuilder(); + listByRegionIDSearch.and("regionId", listByRegionIDSearch.entity().getRegionId(), SearchCriteria.Op.EQ); + listByRegionIDSearch.done(); + } + + @Override + public List listByRegionId(int regionIdId) { + SearchCriteria sc = listByRegionIDSearch.create(); + sc.setParameters("regionId", regionIdId); + return listBy(sc); + } +} diff --git a/server/src/org/apache/cloudstack/region/PortableIpRangeVO.java b/server/src/org/apache/cloudstack/region/PortableIpRangeVO.java new file mode 100644 index 00000000000..933fcc3d1d7 --- /dev/null +++ b/server/src/org/apache/cloudstack/region/PortableIpRangeVO.java @@ -0,0 +1,119 @@ +// 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.region; + +import java.util.UUID; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + +@Entity +@Table(name="portable_ip_range") +public class PortableIpRangeVO implements PortableIpRange { + + @Id + @GeneratedValue(strategy=GenerationType.IDENTITY) + @Column(name="id") + Long id; + + @Column(name="uuid") + String uuid; + + @Column(name="region_id") + int regionId; + + @Column(name="vlan_id") + String vlan; + + @Column(name="gateway") + String gateway; + + @Column(name="netmask") + String netmask; + + @Column(name="start_ip") + String startIp; + + @Column(name="end_ip") + String endIp; + + public PortableIpRangeVO() { + this.uuid = UUID.randomUUID().toString(); + } + + public PortableIpRangeVO(int regionId, String vlan, String gateway, String netmask, String startIp, String endIp) { + this.uuid = UUID.randomUUID().toString(); + this.regionId =regionId; + this.vlan = vlan; + this.gateway = gateway; + this.netmask = netmask; + this.startIp = startIp; + this.endIp = endIp; + } + + @Override + public long getId() { + return id; + } + + @Override + public String getUuid() { + return this.uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + @Override + public String getVlanTag() { + return vlan; + } + + public void setVlanTag(String vlan) { + this.vlan = vlan; + } + + @Override + public String getGateway() { + return gateway; + } + + @Override + public String getNetmask() { + return netmask; + } + + @Override + public int getRegionId() { + return regionId; + } + + @Override + public String getIpRange() { + return startIp + "-" + endIp; + } +} diff --git a/server/src/org/apache/cloudstack/region/PortableIpVO.java b/server/src/org/apache/cloudstack/region/PortableIpVO.java new file mode 100644 index 00000000000..9a630094001 --- /dev/null +++ b/server/src/org/apache/cloudstack/region/PortableIpVO.java @@ -0,0 +1,222 @@ +// 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.region; + +import java.util.Date; +import java.util.UUID; + +import javax.persistence.*; + +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + +@Entity +@Table(name="portable_ip_address") +public class PortableIpVO implements PortableIp { + + @Id + @GeneratedValue(strategy=GenerationType.IDENTITY) + @Column(name="id") + Long id; + + @Column(name="region_id") + int regionId; + + @Column(name="allocated") + @Temporal(value=TemporalType.TIMESTAMP) + private Date allocatedTime; + + @Column(name="account_id") + private Long allocatedToAccountId = null; + + @Column(name="domain_id") + private Long allocatedInDomainId = null; + + @Column(name="state") + private State state; + + @Column(name="vlan") + String vlan; + + @Column(name="gateway") + String gateway; + + @Column(name="netmask") + String netmask; + + @Column(name="portable_ip_address") + String address; + + @Column(name="portable_ip_range_id") + private long rangeId; + + @Column(name="physical_network_id") + private Long physicalNetworkId; + + @Column(name="data_center_id") + private Long dataCenterId; + + @Column(name="network_id") + private Long networkId; + + @Column(name="vpc_id") + private Long vpcId; + + public PortableIpVO() { + + } + + public PortableIpVO(int regionId, Long rangeId, String vlan, String gateway, String netmask, String address) { + this.regionId =regionId; + this.vlan = vlan; + this.gateway = gateway; + this.netmask = netmask; + this.address = address; + state = State.Free; + this.rangeId = rangeId; + } + + @Override + public long getId() { + return id; + } + + @Override + public Long getAllocatedToAccountId() { + return allocatedToAccountId; + } + + public void setAllocatedToAccountId(Long accountId) { + this.allocatedToAccountId = accountId; + } + + @Override + public Long getAllocatedInDomainId() { + return allocatedInDomainId; + } + + public void setAllocatedInDomainId(Long domainId) { + this.allocatedInDomainId = domainId; + } + + @Override + public Date getAllocatedTime() { + return allocatedTime; + } + + public void setAllocatedTime(Date date) { + this.allocatedTime = date; + } + + @Override + public State getState() { + return state; + } + + public void setState(State state) { + this.state = state; + } + + @Override + public int getRegionId() { + return regionId; + } + + public void setRegionId(int regionId) { + this.regionId = regionId; + } + + @Override + public Long getAssociatedDataCenterId() { + return dataCenterId; + } + + public void setAssociatedDataCenterId(Long datacenterId) { + this.dataCenterId = datacenterId; + } + + @Override + public Long getAssociatedWithNetworkId() { + return networkId; + } + + public void setAssociatedWithNetworkId(Long networkId) { + this.networkId = networkId; + } + + @Override + public Long getAssociatedWithVpcId() { + return vpcId; + } + + public void setAssociatedWithVpcId(Long vpcId) { + this.vpcId = vpcId; + } + + @Override + public Long getPhysicalNetworkId() { + return physicalNetworkId; + } + + public void setPhysicalNetworkId(Long physicalNetworkId) { + this.physicalNetworkId = physicalNetworkId; + } + + @Override + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + @Override + public String getVlan() { + return vlan; + } + + public void setVlan(String vlan) { + this.vlan = vlan; + } + + @Override + public String getNetmask() { + return netmask; + } + + public void setNetmask(String netmask) { + this.netmask = netmask; + } + + @Override + public String getGateway() { + return gateway; + } + + public void setGateay(String gateway) { + this.gateway = gateway; + } + + Long getRangeId() { + return rangeId; + } + + public void setRangeId(Long rangeId) { + this.rangeId = rangeId; + } +} diff --git a/server/test/com/cloud/network/MockNetworkManagerImpl.java b/server/test/com/cloud/network/MockNetworkManagerImpl.java index 87431ab54ab..1ac014d502a 100755 --- a/server/test/com/cloud/network/MockNetworkManagerImpl.java +++ b/server/test/com/cloud/network/MockNetworkManagerImpl.java @@ -75,7 +75,6 @@ import com.cloud.vm.VirtualMachine; import com.cloud.vm.*; import com.cloud.vm.VirtualMachine.Type; import com.cloud.vm.VirtualMachineProfile; -import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.api.command.admin.network.DedicateGuestVlanRangeCmd; import org.apache.cloudstack.api.command.admin.network.ListDedicatedGuestVlanRangesCmd; import org.apache.cloudstack.api.command.admin.usage.ListTrafficTypeImplementorsCmd; @@ -89,7 +88,6 @@ import java.util.List; import java.util.Map; import java.util.Set; - @Component @Local(value = { NetworkManager.class, NetworkService.class }) public class MockNetworkManagerImpl extends ManagerBase implements NetworkManager, NetworkService { @@ -106,6 +104,27 @@ public class MockNetworkManagerImpl extends ManagerBase implements NetworkManage return null; } + @Override + public IPAddressVO associatePortableIPToGuestNetwork(long ipAddrId, long networkId, boolean releaseOnFailure) throws ResourceAllocationException, ResourceUnavailableException { + return null;// TODO Auto-generated method stub + } + + @Override + public IPAddressVO disassociatePortableIPToGuestNetwork(long ipAddrId, long networkId) throws ResourceAllocationException, ResourceUnavailableException, InsufficientAddressCapacityException, ConcurrentOperationException { + return null; // TODO Auto-generated method stub + } + + @Override + public boolean isPortableIpTransferableFromNetwork(long ipAddrId, long networkId) { + return false; + } + + @Override + public void transferPortableIP(long ipAddrId, long currentNetworkId, long newNetworkId) throws ResourceAllocationException, ResourceUnavailableException, + InsufficientAddressCapacityException, ConcurrentOperationException { + + } + @Override public boolean releaseIpAddress(long ipAddressId) { // TODO Auto-generated method stub @@ -861,6 +880,23 @@ public class MockNetworkManagerImpl extends ManagerBase implements NetworkManage return null; } + @Override + public IpAddress allocatePortableIp(Account ipOwner, Account caller, long dcId, Long networkId, Long vpcID) + throws ConcurrentOperationException, ResourceAllocationException, InsufficientAddressCapacityException { + return null;// TODO Auto-generated method stub + } + + @Override + public IpAddress allocatePortableIP(Account ipOwner, int regionId, Long zoneId, Long networkId, Long vpcId) throws ResourceAllocationException, + InsufficientAddressCapacityException, ConcurrentOperationException { + return null; + } + + @Override + public boolean releasePortableIpAddress(long ipAddressId) throws InsufficientAddressCapacityException { + return false;// TODO Auto-generated method stub + } + @Override public boolean isSecondaryIpSetForNic(long nicId) { // TODO Auto-generated method stub diff --git a/server/test/com/cloud/vpc/MockConfigurationManagerImpl.java b/server/test/com/cloud/vpc/MockConfigurationManagerImpl.java index 4fb182aae14..21b3590282d 100755 --- a/server/test/com/cloud/vpc/MockConfigurationManagerImpl.java +++ b/server/test/com/cloud/vpc/MockConfigurationManagerImpl.java @@ -16,6 +16,44 @@ // under the License. package com.cloud.vpc; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.ejb.Local; +import javax.inject.Inject; +import javax.naming.ConfigurationException; +import javax.naming.NamingException; + +import org.apache.cloudstack.api.command.admin.config.UpdateCfgCmd; +import org.apache.cloudstack.api.command.admin.ldap.LDAPConfigCmd; +import org.apache.cloudstack.api.command.admin.ldap.LDAPRemoveCmd; +import org.apache.cloudstack.api.command.admin.network.CreateNetworkOfferingCmd; +import org.apache.cloudstack.api.command.admin.network.DeleteNetworkOfferingCmd; +import org.apache.cloudstack.api.command.admin.network.UpdateNetworkOfferingCmd; +import org.apache.cloudstack.api.command.admin.offering.CreateDiskOfferingCmd; +import org.apache.cloudstack.api.command.admin.offering.CreateServiceOfferingCmd; +import org.apache.cloudstack.api.command.admin.offering.DeleteDiskOfferingCmd; +import org.apache.cloudstack.api.command.admin.offering.DeleteServiceOfferingCmd; +import org.apache.cloudstack.api.command.admin.offering.UpdateDiskOfferingCmd; +import org.apache.cloudstack.api.command.admin.offering.UpdateServiceOfferingCmd; +import org.apache.cloudstack.api.command.admin.pod.DeletePodCmd; +import org.apache.cloudstack.api.command.admin.pod.UpdatePodCmd; +import org.apache.cloudstack.api.command.admin.region.CreatePortableIpRangeCmd; +import org.apache.cloudstack.api.command.admin.region.DeletePortableIpRangeCmd; +import org.apache.cloudstack.api.command.admin.region.ListPortableIpRangesCmd; +import org.apache.cloudstack.api.command.admin.vlan.CreateVlanIpRangeCmd; +import org.apache.cloudstack.api.command.admin.vlan.DedicatePublicIpRangeCmd; +import org.apache.cloudstack.api.command.admin.vlan.DeleteVlanIpRangeCmd; +import org.apache.cloudstack.api.command.admin.vlan.ReleasePublicIpRangeCmd; +import org.apache.cloudstack.api.command.admin.zone.CreateZoneCmd; +import org.apache.cloudstack.api.command.admin.zone.DeleteZoneCmd; +import org.apache.cloudstack.api.command.admin.zone.UpdateZoneCmd; +import org.apache.cloudstack.api.command.user.network.ListNetworkOfferingsCmd; +import org.apache.cloudstack.region.PortableIp; +import org.apache.cloudstack.region.PortableIpRange; +import org.springframework.stereotype.Component; + import com.cloud.configuration.Configuration; import com.cloud.configuration.ConfigurationManager; import com.cloud.configuration.ConfigurationService; @@ -381,6 +419,26 @@ public class MockConfigurationManagerImpl extends ManagerBase implements Configu return false; } + @Override + public PortableIpRange createPortableIpRange(CreatePortableIpRangeCmd cmd) throws ConcurrentOperationException { + return null;// TODO Auto-generated method stub + } + + @Override + public boolean deletePortableIpRange(DeletePortableIpRangeCmd cmd) { + return false;// TODO Auto-generated method stub + } + + @Override + public List listPortableIpRanges(ListPortableIpRangesCmd cmd) { + return null;// TODO Auto-generated method stub + } + + @Override + public List listPortableIps(long id) { + return null;// TODO Auto-generated method stub + } + /* (non-Javadoc) * @see com.cloud.utils.component.Manager#configure(java.lang.String, java.util.Map) */ diff --git a/server/test/com/cloud/vpc/MockNetworkManagerImpl.java b/server/test/com/cloud/vpc/MockNetworkManagerImpl.java index 3e6a08bdbf3..62599b8d504 100644 --- a/server/test/com/cloud/vpc/MockNetworkManagerImpl.java +++ b/server/test/com/cloud/vpc/MockNetworkManagerImpl.java @@ -63,7 +63,6 @@ import com.cloud.network.PhysicalNetworkServiceProvider; import com.cloud.network.PhysicalNetworkTrafficType; import com.cloud.network.PublicIpAddress; import com.cloud.network.addr.PublicIp; -import com.cloud.network.dao.AccountGuestVlanMapVO; import com.cloud.network.dao.IPAddressVO; import com.cloud.network.dao.NetworkServiceMapDao; import com.cloud.network.dao.NetworkVO; @@ -198,13 +197,36 @@ public class MockNetworkManagerImpl extends ManagerBase implements NetworkManage return null; } + @Override + public IpAddress allocatePortableIp(Account ipOwner, Account caller, long dcId, Long networkId, Long vpcID) + throws ConcurrentOperationException, ResourceAllocationException, InsufficientAddressCapacityException { + return null;// TODO Auto-generated method stub + } + @Override + public IpAddress allocatePortableIP(Account ipOwner, int regionId, Long zoneId, Long networkId, Long vpcId) throws ResourceAllocationException, + InsufficientAddressCapacityException, ConcurrentOperationException { + return null; + } + @Override + public boolean releasePortableIpAddress(long ipAddressId) throws InsufficientAddressCapacityException { + return false;// TODO Auto-generated method stub + } + @Override + public boolean isPortableIpTransferableFromNetwork(long ipAddrId, long networkId) { + return false; + } + + @Override + public void transferPortableIP(long ipAddrId, long currentNetworkId, long newNetworkId) throws ResourceAllocationException, ResourceUnavailableException, + InsufficientAddressCapacityException, ConcurrentOperationException { + } /* (non-Javadoc) - * @see com.cloud.network.NetworkService#releaseIpAddress(long) - */ + * @see com.cloud.network.NetworkService#releaseIpAddress(long) + */ @Override public boolean releaseIpAddress(long ipAddressId) throws InsufficientAddressCapacityException { // TODO Auto-generated method stub @@ -1112,13 +1134,20 @@ public class MockNetworkManagerImpl extends ManagerBase implements NetworkManage return null; } + @Override + public IPAddressVO associatePortableIPToGuestNetwork(long ipAddrId, long networkId, boolean releaseOnFailure) throws ResourceAllocationException, ResourceUnavailableException { + return null;// TODO Auto-generated method stub + } - + @Override + public IPAddressVO disassociatePortableIPToGuestNetwork(long ipAddrId, long networkId) throws ResourceAllocationException, ResourceUnavailableException, InsufficientAddressCapacityException, ConcurrentOperationException { + return null;// TODO Auto-generated method stub + } /* (non-Javadoc) - * @see com.cloud.network.NetworkManager#setupDns(com.cloud.network.Network, com.cloud.network.Network.Provider) - */ + * @see com.cloud.network.NetworkManager#setupDns(com.cloud.network.Network, com.cloud.network.Network.Provider) + */ @Override public boolean setupDns(Network network, Provider provider) { // TODO Auto-generated method stub diff --git a/server/test/org/apache/cloudstack/networkoffering/ChildTestConfiguration.java b/server/test/org/apache/cloudstack/networkoffering/ChildTestConfiguration.java index a8256990973..f862a2a4760 100644 --- a/server/test/org/apache/cloudstack/networkoffering/ChildTestConfiguration.java +++ b/server/test/org/apache/cloudstack/networkoffering/ChildTestConfiguration.java @@ -20,6 +20,9 @@ package org.apache.cloudstack.networkoffering; import java.io.IOException; import org.apache.cloudstack.acl.SecurityChecker; +import org.apache.cloudstack.region.PortableIpDaoImpl; +import org.apache.cloudstack.region.dao.RegionDaoImpl; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDaoImpl; import org.apache.cloudstack.test.utils.SpringUtils; import org.mockito.Mockito; @@ -113,6 +116,7 @@ import com.cloud.vm.dao.NicDaoImpl; import com.cloud.vm.dao.NicSecondaryIpDaoImpl; import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.VMInstanceDaoImpl; +import org.apache.cloudstack.region.PortableIpRangeDaoImpl; @Configuration @ComponentScan(basePackageClasses={ @@ -162,6 +166,9 @@ import com.cloud.vm.dao.VMInstanceDaoImpl; NetworkServiceMapDaoImpl.class, PrimaryDataStoreDaoImpl.class, StoragePoolDetailsDaoImpl.class, + PortableIpRangeDaoImpl.class, + RegionDaoImpl.class, + PortableIpDaoImpl.class, AccountGuestVlanMapDaoImpl.class }, includeFilters={@Filter(value=ChildTestConfiguration.Library.class, type=FilterType.CUSTOM)}, diff --git a/setup/db/db/schema-410to420.sql b/setup/db/db/schema-410to420.sql index c088ac1c2f0..7b5e9cb5f29 100644 --- a/setup/db/db/schema-410to420.sql +++ b/setup/db/db/schema-410to420.sql @@ -1267,6 +1267,43 @@ INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'Netwo alter table `cloud_usage`.`usage_network_offering` add column nic_id bigint(20) unsigned NOT NULL; + +CREATE TABLE `cloud`.`portable_ip_range` ( + `id` bigint unsigned NOT NULL UNIQUE AUTO_INCREMENT, + `uuid` varchar(40), + `region_id` int unsigned NOT NULL, + `vlan_id` varchar(255), + `gateway` varchar(255), + `netmask` varchar(255), + `start_ip` varchar(255), + `end_ip` varchar(255), + PRIMARY KEY (`id`), + CONSTRAINT `fk_portableip__region_id` FOREIGN KEY (`region_id`) REFERENCES `region`(`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE `cloud`.`portable_ip_address` ( + `id` bigint unsigned NOT NULL UNIQUE AUTO_INCREMENT, + `account_id` bigint unsigned NULL, + `domain_id` bigint unsigned NULL, + `allocated` datetime NULL COMMENT 'Date portable ip was allocated', + `state` char(32) NOT NULL default 'Free' COMMENT 'state of the portable ip address', + `region_id` int unsigned NOT NULL, + `vlan` varchar(255), + `gateway` varchar(255), + `netmask` varchar(255), + `portable_ip_address` varchar(255), + `portable_ip_range_id` bigint unsigned NOT NULL, + `data_center_id` bigint unsigned NULL COMMENT 'zone to which portable IP is associated', + `physical_network_id` bigint unsigned NULL COMMENT 'physical network id in the zone to which portable IP is associated', + `network_id` bigint unsigned NULL COMMENT 'guest network to which portable ip address is associated with', + `vpc_id` bigint unsigned COMMENT 'vpc to which portable ip address is associated with', + PRIMARY KEY (`id`), + CONSTRAINT `fk_portable_ip_address__portable_ip_range_id` FOREIGN KEY (`portable_ip_range_id`) REFERENCES `portable_ip_range`(`id`) ON DELETE CASCADE, + CONSTRAINT `fk_portable_ip_address__region_id` FOREIGN KEY (`region_id`) REFERENCES `region`(`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +ALTER TABLE `cloud`.`user_ip_address` ADD COLUMN is_portable int(1) unsigned NOT NULL default '0'; + DROP VIEW IF EXISTS `cloud`.`disk_offering_view`; CREATE VIEW `cloud`.`disk_offering_view` AS select diff --git a/test/integration/smoke/test_portable_publicip.py b/test/integration/smoke/test_portable_publicip.py new file mode 100644 index 00000000000..101747d958e --- /dev/null +++ b/test/integration/smoke/test_portable_publicip.py @@ -0,0 +1,237 @@ +#!/usr/bin/env python +# 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. + +from marvin.cloudstackTestCase import * +from marvin.cloudstackAPI import * +from marvin.integration.lib.utils import * +from marvin.integration.lib.base import * +from marvin.integration.lib.common import * +from marvin import remoteSSHClient +from nose.plugins.attrib import attr + +class Services: + """Test Data + """ + + def __init__(self): + self.services = { + "domain": { + "name": "Domain", + }, + "account": { + "email": "test@test.com", + "firstname": "Test", + "lastname": "User", + "username": "test", + # Random characters are appended for unique + # username + "password": "password", + }, + "service_offering": { + "name": "Tiny Instance", + "displaytext": "Tiny Instance", + "cpunumber": 1, + "cpuspeed": 100, + # in MHz + "memory": 128, + # In MBs + }, + "network_offering": { + "name": 'Test Network offering', + "displaytext": 'Test Network offering', + "guestiptype": 'Isolated', + "supportedservices": 'Dhcp,Dns,SourceNat,PortForwarding', + "traffictype": 'GUEST', + "availability": 'Optional', + "serviceProviderList" : { + "Dhcp": 'VirtualRouter', + "Dns": 'VirtualRouter', + "SourceNat": 'VirtualRouter', + "PortForwarding": 'VirtualRouter', + }, + }, + "network": { + "name": "Test Network", + "displaytext": "Test Network", + }, + "ostype": 'CentOS 5.3 (64-bit)', + "gateway" : "10.1.1.1", + "netmask" : "255.255.255.0", + "startip" : "10.1.1.10", + "endip" : "10.1.1.20", + "regionid" : "1", + "vlan" :"10", + "isportable" : "true", + "virtual_machine" : { + "affinity": { + "name": "webvms", + "type": "host anti-affinity", + }, + "hypervisor" : "XenServer", + } + } + +class TestPortablePublicIPRange(cloudstackTestCase): + + """ + This test validates functionality where + - admin can provision a portable public ip range + - list provisioned portable public ip range + - delete provisioned portable public ip range + """ + @classmethod + def setUpClass(cls): + cls.api_client = super(TestPortablePublicIPRange, cls).getClsTestClient().getApiClient() + cls.services = Services().services + # Get Zone, Domain + cls.domain = get_domain(cls.api_client, cls.services) + cls.zone = get_zone(cls.api_client, cls.services) + + # Create Account + cls.account = Account.create( + cls.api_client, + cls.services["account"], + domainid=cls.domain.id + ) + cls._cleanup = [ + cls.account, + ] + return + + @classmethod + def tearDownClass(cls): + try: + # Cleanup resources used + cleanup_resources(cls.api_client, cls._cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + return + + def tearDown(self): + try: + # Clean up + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + @attr(tags = ["simulator", "basic", "advanced", "portablepublicip"]) + def test_createPortablePublicIPRange(self): + """ + """ + self.debug("attempting to create a portable Public IP range") + self.portable_ip_range = PortablePublicIpRange.create( + self.api_client, + self.services + ) + self.debug("attempting to verify portable Public IP range is created") + list_portbale_ip_range_response = PortablePublicIpRange.list( + self.apiclient, + id=self.portable_ip_range.id + ) + self.portable_ip_range.delete(self.apiclient) + return + + +class TestPortablePublicIPAcquire(cloudstackTestCase): + + """ + This test validates functionality where + - admin has provisioned a portable public ip range + - user can acquire portable ip from the provisioned ip range + """ + @classmethod + def setUpClass(cls): + cls.api_client = super(TestPortablePublicIPAcquire, cls).getClsTestClient().getApiClient() + cls.services = Services().services + # Get Zone, Domain + cls.domain = get_domain(cls.api_client, cls.services) + cls.zone = get_zone(cls.api_client, cls.services) + # Create Account + cls.account = Account.create( + cls.api_client, + cls.services["account"], + domainid=cls.domain.id + ) + cls.services["network"]["zoneid"] = cls.zone.id + + cls.network_offering = NetworkOffering.create( + cls.api_client, + cls.services["network_offering"], + ) + # Enable Network offering + cls.network_offering.update(cls.api_client, state='Enabled') + + cls.services["network"]["networkoffering"] = cls.network_offering.id + cls.account_network = Network.create( + cls.api_client, + cls.services["network"], + cls.account.name, + cls.account.domainid + ) + cls._cleanup = [ + cls.account_network, + cls.network_offering, + cls.account + ] + + return + + @classmethod + def tearDownClass(cls): + try: + # Cleanup resources used + cleanup_resources(cls.api_client, cls._cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + return + + def tearDown(self): + try: + # Clean up + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + @attr(tags = ["simulator", "basic", "advanced", "portablepublicip"]) + def test_createPortablePublicIPAcquire(self): + """ + """ + self.debug("attempting to create a portable Public IP range") + self.portable_ip_range = PortablePublicIpRange.create( + self.api_client, + self.services + ) + + ip_address = PublicIPAddress.create(self.api_client, self.account.name, self.zone.id, self.account.domainid) + + self.portable_ip_range.delete(self.apiclient) + return \ No newline at end of file diff --git a/tools/marvin/marvin/integration/lib/base.py b/tools/marvin/marvin/integration/lib/base.py index f3a96bd9bec..ec1c34e12c7 100755 --- a/tools/marvin/marvin/integration/lib/base.py +++ b/tools/marvin/marvin/integration/lib/base.py @@ -2131,6 +2131,42 @@ class PublicIpRange: cmd.id = self.vlan.id return apiclient.releasePublicIpRange(cmd) + +class PortablePublicIpRange: + """Manage portable public Ip Range""" + + def __init__(self, items): + self.__dict__.update(items) + + @classmethod + def create(cls, apiclient, services): + """Create portable public Ip Range""" + + cmd = createPortableIpRange.createPortableIpRangeCmd() + cmd.gateway = services["gateway"] + cmd.netmask = services["netmask"] + cmd.startip = services["startip"] + cmd.endip = services["endip"] + cmd.regionid = services["regionid"] + cmd.vlan = services["vlan"] + + return PortablePublicIpRange(apiclient.createVlanIpRange(cmd).__dict__) + + def delete(self, apiclient): + """Delete portable IpRange""" + + cmd = deletePortableIpRange.deletePortableIpRangeCmd() + cmd.id = self.id + apiclient.deletePortableIpRange(cmd) + + @classmethod + def list(cls, apiclient, **kwargs): + """Lists all portable public IP ranges.""" + + cmd = listPortableIpRanges.listPortableIpRangesCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.listPortableIpRanges(cmd)) + class SecondaryStorage: """Manage Secondary storage""" diff --git a/utils/src/com/cloud/utils/net/NetUtils.java b/utils/src/com/cloud/utils/net/NetUtils.java index 9551c262e54..37dcef382aa 100755 --- a/utils/src/com/cloud/utils/net/NetUtils.java +++ b/utils/src/com/cloud/utils/net/NetUtils.java @@ -1296,6 +1296,18 @@ public class NetUtils { return resultIp; } + public static boolean isValidVlan(String vlan) { + try { + int vnet = Integer.parseInt(vlan); + if (vnet < 0 || vnet > 4096) { + return false; + } + return true; + } catch (NumberFormatException e) { + return false; + } + } + public static URI generateUriForPvlan(String primaryVlan, String isolatedPvlan) { return URI.create("pvlan://" + primaryVlan + "-i" + isolatedPvlan); } @@ -1320,4 +1332,5 @@ public class NetUtils { } return null; } + } From aa60105a84e1260ec9687759a069a0c9cf0c46bf Mon Sep 17 00:00:00 2001 From: Sanjeev Neelarapu Date: Mon, 20 May 2013 20:36:00 +0530 Subject: [PATCH 033/108] CLOUDSTACK-2543: [Multiple_IP_Ranges] Failed to create IP alias on router vm createipAlias.sh/deleteipAlias.sh won't be copied to XenServer host. The directory of the scripts should be ".." rather "../../.." in all the xenserver patches file. Corrected the path to ".." because the scripts are located at scripts/vm/hypervisor/xenserver/xenserver56/patch --- scripts/vm/hypervisor/xenserver/xcposs/patch | 2 ++ scripts/vm/hypervisor/xenserver/xcpserver/patch | 4 ++-- scripts/vm/hypervisor/xenserver/xenserver56/patch | 4 ++-- scripts/vm/hypervisor/xenserver/xenserver56fp1/patch | 4 ++-- scripts/vm/hypervisor/xenserver/xenserver60/patch | 4 ++-- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/scripts/vm/hypervisor/xenserver/xcposs/patch b/scripts/vm/hypervisor/xenserver/xcposs/patch index 6dc3baae555..4d07c76a68f 100644 --- a/scripts/vm/hypervisor/xenserver/xcposs/patch +++ b/scripts/vm/hypervisor/xenserver/xcposs/patch @@ -65,3 +65,5 @@ getRouterStatus.sh=../../../../network/domr/,0755,/usr/lib/xcp/bin bumpUpPriority.sh=../../../../network/domr/,0755,/usr/lib/xcp/bin getDomRVersion.sh=../../../../network/domr/,0755,/usr/lib/xcp/bin router_proxy.sh=../../../../network/domr/,0755,/usr/lib/xcp/bin +createipAlias.sh=..,0755,/usr/lib/xcp/bin +deleteipAlias.sh=..,0755,/usr/lib/xcp/bin diff --git a/scripts/vm/hypervisor/xenserver/xcpserver/patch b/scripts/vm/hypervisor/xenserver/xcpserver/patch index a275df4a48b..7e92d5aa4ec 100644 --- a/scripts/vm/hypervisor/xenserver/xcpserver/patch +++ b/scripts/vm/hypervisor/xenserver/xcpserver/patch @@ -40,8 +40,8 @@ make_migratable.sh=..,0755,/opt/xensource/bin setup_iscsi.sh=..,0755,/opt/xensource/bin pingtest.sh=../../..,0755,/opt/xensource/bin dhcp_entry.sh=../../../../network/domr/,0755,/opt/xensource/bin -createipAlias.sh=../../..,0755,/opt/xensource/bin -deleteipAlias.sh=../../..,0755,/opt/xensource/bin +createipAlias.sh=..,0755,/opt/xensource/bin +deleteipAlias.sh=..,0755,/opt/xensource/bin router_proxy.sh=../../../../network/domr/,0755,/opt/xensource/bin vm_data.sh=../../../../network/domr/,0755,/opt/xensource/bin save_password_to_domr.sh=../../../../network/domr/,0755,/opt/xensource/bin diff --git a/scripts/vm/hypervisor/xenserver/xenserver56/patch b/scripts/vm/hypervisor/xenserver/xenserver56/patch index 5c4673df247..8abd6b2c850 100644 --- a/scripts/vm/hypervisor/xenserver/xenserver56/patch +++ b/scripts/vm/hypervisor/xenserver/xenserver56/patch @@ -38,8 +38,8 @@ make_migratable.sh=..,0755,/opt/xensource/bin setup_iscsi.sh=..,0755,/opt/xensource/bin cloud-setup-bonding.sh=..,0755,/opt/xensource/bin pingtest.sh=../../..,0755,/opt/xensource/bin -createipAlias.sh=../../..,0755,/opt/xensource/bin -deleteipAlias.sh=../../..,0755,/opt/xensource/bin +createipAlias.sh=..,0755,/opt/xensource/bin +deleteipAlias.sh=..,0755,/opt/xensource/bin dhcp_entry.sh=../../../../network/domr/,0755,/opt/xensource/bin vm_data.sh=../../../../network/domr/,0755,/opt/xensource/bin save_password_to_domr.sh=../../../../network/domr/,0755,/opt/xensource/bin diff --git a/scripts/vm/hypervisor/xenserver/xenserver56fp1/patch b/scripts/vm/hypervisor/xenserver/xenserver56fp1/patch index c7c58b98374..901f6de3643 100644 --- a/scripts/vm/hypervisor/xenserver/xenserver56fp1/patch +++ b/scripts/vm/hypervisor/xenserver/xenserver56fp1/patch @@ -37,8 +37,8 @@ setupxenserver.sh=..,0755,/opt/xensource/bin make_migratable.sh=..,0755,/opt/xensource/bin setup_iscsi.sh=..,0755,/opt/xensource/bin pingtest.sh=../../..,0755,/opt/xensource/bin -createipAlias.sh=../../..,0755,/opt/xensource/bin -deleteipAlias.sh=../../..,0755,/opt/xensource/bin +createipAlias.sh=..,0755,/opt/xensource/bin +deleteipAlias.sh=..,0755,/opt/xensource/bin dhcp_entry.sh=../../../../network/domr/,0755,/opt/xensource/bin vm_data.sh=../../../../network/domr/,0755,/opt/xensource/bin save_password_to_domr.sh=../../../../network/domr/,0755,/opt/xensource/bin diff --git a/scripts/vm/hypervisor/xenserver/xenserver60/patch b/scripts/vm/hypervisor/xenserver/xenserver60/patch index 26205f2e7e6..d7da3747183 100644 --- a/scripts/vm/hypervisor/xenserver/xenserver60/patch +++ b/scripts/vm/hypervisor/xenserver/xenserver60/patch @@ -40,8 +40,8 @@ id_rsa.cloud=../../../systemvm,0600,/root/.ssh network_info.sh=..,0755,/opt/xensource/bin setupxenserver.sh=..,0755,/opt/xensource/bin make_migratable.sh=..,0755,/opt/xensource/bin -createipAlias.sh=../../..,0755,/opt/xensource/bin -deleteipAlias.sh=../../..,0755,/opt/xensource/bin +createipAlias.sh=..,0755,/opt/xensource/bin +deleteipAlias.sh=..,0755,/opt/xensource/bin setup_iscsi.sh=..,0755,/opt/xensource/bin pingtest.sh=../../..,0755,/opt/xensource/bin dhcp_entry.sh=../../../../network/domr/,0755,/opt/xensource/bin From 145a116c9bbe69e3c5cb026ce9d659f449137d30 Mon Sep 17 00:00:00 2001 From: Prasanna Santhanam Date: Mon, 20 May 2013 22:05:07 +0530 Subject: [PATCH 034/108] Fixing TestVMLifeCycle class Missed the reference to zone listed in setUpClass() Signed-off-by: Prasanna Santhanam --- test/integration/smoke/test_vm_life_cycle.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/integration/smoke/test_vm_life_cycle.py b/test/integration/smoke/test_vm_life_cycle.py index 3f7c17b9ed6..d52ed9b8df6 100644 --- a/test/integration/smoke/test_vm_life_cycle.py +++ b/test/integration/smoke/test_vm_life_cycle.py @@ -292,28 +292,28 @@ class TestVMLifeCycle(cloudstackTestCase): # Get Zone, Domain and templates domain = get_domain(cls.api_client, cls.services) - zone = get_zone(cls.api_client, cls.services) - cls.services['mode'] = zone.networktype + cls.zone = get_zone(cls.api_client, cls.services) + cls.services['mode'] = cls.zone.networktype #if local storage is enabled, alter the offerings to use localstorage #this step is needed for devcloud - if zone.localstorageenabled == True: + if cls.zone.localstorageenabled == True: cls.services["service_offerings"]["tiny"]["storagetype"] = 'local' cls.services["service_offerings"]["small"]["storagetype"] = 'local' cls.services["service_offerings"]["medium"]["storagetype"] = 'local' template = get_template( cls.api_client, - zone.id, + cls.zone.id, cls.services["ostype"] ) # Set Zones and disk offerings - cls.services["small"]["zoneid"] = zone.id + cls.services["small"]["zoneid"] = cls.zone.id cls.services["small"]["template"] = template.id - cls.services["medium"]["zoneid"] = zone.id + cls.services["medium"]["zoneid"] = cls.zone.id cls.services["medium"]["template"] = template.id - cls.services["iso"]["zoneid"] = zone.id + cls.services["iso"]["zoneid"] = cls.zone.id # Create VMs, NAT Rules etc cls.account = Account.create( From 54ac779b8f3a520a391902ae4163b922e2078061 Mon Sep 17 00:00:00 2001 From: Prasanna Santhanam Date: Mon, 20 May 2013 22:59:52 +0530 Subject: [PATCH 035/108] Fix all occurrences of account.account Fixes the dereference of account objects with account.account. This is to conform to recent library changes in Marvin Signed-off-by: Prasanna Santhanam --- test/integration/component/test_accounts.py | 2 +- .../component/test_high_availability.py | 28 ++-- .../component/test_host_high_availability.py | 14 +- test/integration/component/test_ldap.py | 0 test/integration/component/test_projects.py | 34 ++-- .../component/test_redundant_router.py | 120 +++++++------- test/integration/component/test_stopped_vm.py | 70 ++++---- test/integration/component/test_tags.py | 110 ++++++------- test/integration/component/test_vpc.py | 124 +++++++------- .../component/test_vpc_host_maintenance.py | 44 ++--- .../integration/component/test_vpc_network.py | 106 ++++++------ .../component/test_vpc_network_lbrules.py | 18 +- .../component/test_vpc_network_pfrules.py | 18 +- .../test_vpc_network_staticnatrule.py | 18 +- .../component/test_vpc_offerings.py | 52 +++--- .../integration/component/test_vpc_routers.py | 56 +++---- .../component/test_vpc_vm_life_cycle.py | 98 +++++------ .../component/test_vpc_vms_deployment.py | 154 +++++++++--------- test/integration/component/test_vpn_users.py | 4 +- 19 files changed, 535 insertions(+), 535 deletions(-) mode change 100755 => 100644 test/integration/component/test_ldap.py diff --git a/test/integration/component/test_accounts.py b/test/integration/component/test_accounts.py index b2038a9bd3b..a25e63688f3 100644 --- a/test/integration/component/test_accounts.py +++ b/test/integration/component/test_accounts.py @@ -561,7 +561,7 @@ class TestNonRootAdminsPrivileges(cloudstackTestCase): self.apiclient, self.services["account"] ) - self.debug("Created account: %s" % account_1.account.name) + self.debug("Created account: %s" % account_1.name) self.cleanup.append(account_1) account_2 = Account.create( self.apiclient, diff --git a/test/integration/component/test_high_availability.py b/test/integration/component/test_high_availability.py index 12753c1707f..cd2dfcea559 100644 --- a/test/integration/component/test_high_availability.py +++ b/test/integration/component/test_high_availability.py @@ -220,7 +220,7 @@ class TestHighAvailability(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id ) vms = VirtualMachine.list( @@ -248,7 +248,7 @@ class TestHighAvailability(cloudstackTestCase): networks = Network.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -264,7 +264,7 @@ class TestHighAvailability(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network.id ) @@ -372,7 +372,7 @@ class TestHighAvailability(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id ) vms = VirtualMachine.list( @@ -424,7 +424,7 @@ class TestHighAvailability(cloudstackTestCase): vms = VirtualMachine.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -464,7 +464,7 @@ class TestHighAvailability(cloudstackTestCase): vms = VirtualMachine.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -513,7 +513,7 @@ class TestHighAvailability(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id ) vms = VirtualMachine.list( @@ -615,7 +615,7 @@ class TestHighAvailability(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id ) vms = VirtualMachine.list( @@ -643,7 +643,7 @@ class TestHighAvailability(cloudstackTestCase): networks = Network.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -659,7 +659,7 @@ class TestHighAvailability(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network.id ) @@ -833,7 +833,7 @@ class TestHighAvailability(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id ) vms = VirtualMachine.list( @@ -952,7 +952,7 @@ class TestHighAvailability(cloudstackTestCase): vms = VirtualMachine.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -990,7 +990,7 @@ class TestHighAvailability(cloudstackTestCase): vms = VirtualMachine.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -1037,7 +1037,7 @@ class TestHighAvailability(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id ) vms = VirtualMachine.list( diff --git a/test/integration/component/test_host_high_availability.py b/test/integration/component/test_host_high_availability.py index 7a3f62a520f..8c66d175dd7 100644 --- a/test/integration/component/test_host_high_availability.py +++ b/test/integration/component/test_host_high_availability.py @@ -190,7 +190,7 @@ class TestHostHighAvailability(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering_with_ha.id ) vms = VirtualMachine.list( @@ -235,7 +235,7 @@ class TestHostHighAvailability(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering_with_ha.id ) @@ -289,7 +289,7 @@ class TestHostHighAvailability(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering_without_ha.id ) @@ -358,7 +358,7 @@ class TestHostHighAvailability(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering_with_ha.id ) @@ -464,7 +464,7 @@ class TestHostHighAvailability(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering_with_ha.id ) @@ -573,7 +573,7 @@ class TestHostHighAvailability(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering_with_ha.id ) @@ -705,7 +705,7 @@ class TestHostHighAvailability(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering_without_ha.id ) diff --git a/test/integration/component/test_ldap.py b/test/integration/component/test_ldap.py old mode 100755 new mode 100644 diff --git a/test/integration/component/test_projects.py b/test/integration/component/test_projects.py index f013e99a0dd..e1975cf5295 100644 --- a/test/integration/component/test_projects.py +++ b/test/integration/component/test_projects.py @@ -782,8 +782,8 @@ class TestProjectOwners(cloudstackTestCase): project = Project.create( self.apiclient, self.services["project"], - account=self.admin.account.name, - domainid=self.admin.account.domainid + account=self.admin.name, + domainid=self.admin.domainid ) self.cleanup.append(project) # Cleanup created project at end of test @@ -815,20 +815,20 @@ class TestProjectOwners(cloudstackTestCase): "Check project name from list response" ) self.debug("Adding %s user to project: %s" % ( - self.new_admin.account.name, + self.new_admin.name, project.name )) # Add user to the project project.addAccount( self.apiclient, - self.new_admin.account.name, + self.new_admin.name, ) # listProjectAccount to verify the user is added to project or not accounts_reponse = Project.listAccounts( self.apiclient, projectid=project.id, - account=self.new_admin.account.name, + account=self.new_admin.name, ) self.debug(accounts_reponse) self.assertEqual( @@ -853,14 +853,14 @@ class TestProjectOwners(cloudstackTestCase): # Update the project with new admin project.update( self.apiclient, - account=self.new_admin.account.name + account=self.new_admin.name ) # listProjectAccount to verify the user is new admin of the project accounts_reponse = Project.listAccounts( self.apiclient, projectid=project.id, - account=self.new_admin.account.name, + account=self.new_admin.name, ) self.debug(accounts_reponse) self.assertEqual( @@ -886,7 +886,7 @@ class TestProjectOwners(cloudstackTestCase): accounts_reponse = Project.listAccounts( self.apiclient, projectid=project.id, - account=self.admin.account.name, + account=self.admin.name, ) self.debug(accounts_reponse) self.assertEqual( @@ -923,8 +923,8 @@ class TestProjectOwners(cloudstackTestCase): project = Project.create( self.apiclient, self.services["project"], - account=self.admin.account.name, - domainid=self.admin.account.domainid + account=self.admin.name, + domainid=self.admin.domainid ) # Cleanup created project at end of test self.cleanup.append(project) @@ -965,20 +965,20 @@ class TestProjectOwners(cloudstackTestCase): "Check project name from list response" ) self.debug("Adding %s user to project: %s" % ( - self.new_admin.account.name, + self.new_admin.name, project.name )) # Add user to the project project.addAccount( self.apiclient, - self.new_admin.account.name, + self.new_admin.name, ) # listProjectAccount to verify the user is added to project or not accounts_reponse = Project.listAccounts( self.apiclient, projectid=project.id, - account=self.new_admin.account.name, + account=self.new_admin.name, ) self.debug(accounts_reponse) self.assertEqual( @@ -1000,18 +1000,18 @@ class TestProjectOwners(cloudstackTestCase): "Newly added user is not added as a regular user" ) self.debug("Updating project with new Admin: %s" % - self.new_admin.account.name) + self.new_admin.name) # Update the project with new admin project.update( self.apiclient, - account=self.new_admin.account.name + account=self.new_admin.name ) # listProjectAccount to verify the user is new admin of the project accounts_reponse = Project.listAccounts( self.apiclient, projectid=project.id, - account=self.new_admin.account.name, + account=self.new_admin.name, ) self.assertEqual( isinstance(accounts_reponse, list), @@ -1106,7 +1106,7 @@ class TestProjectOwners(cloudstackTestCase): accounts_reponse = Project.listAccounts( self.apiclient, projectid=project.id, - account=self.new_admin.account.name, + account=self.new_admin.name, ) self.assertEqual( isinstance(accounts_reponse, list), diff --git a/test/integration/component/test_redundant_router.py b/test/integration/component/test_redundant_router.py index 8885241ef7b..a87818a4ccb 100644 --- a/test/integration/component/test_redundant_router.py +++ b/test/integration/component/test_redundant_router.py @@ -331,7 +331,7 @@ class TestCreateRvRNetwork(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id ) @@ -375,7 +375,7 @@ class TestCreateRvRNetwork(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network.id)] ) @@ -538,7 +538,7 @@ class TestCreateRvRNetworkNonDefaultGuestCidr(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id, guestcidr=' 192.168.2.0/23' @@ -593,7 +593,7 @@ class TestCreateRvRNetworkNonDefaultGuestCidr(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network.id)] ) @@ -760,7 +760,7 @@ class TestRVRInternals(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id ) @@ -804,7 +804,7 @@ class TestRVRInternals(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network.id)] ) @@ -1019,7 +1019,7 @@ class TestRedundancy(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id ) @@ -1031,7 +1031,7 @@ class TestRedundancy(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(self.network.id)] ) @@ -1572,7 +1572,7 @@ class TestRedundancy(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(self.network.id)] ) @@ -1712,7 +1712,7 @@ class TestApplyAndDeleteNetworkRulesOnRvR(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id ) @@ -1756,7 +1756,7 @@ class TestApplyAndDeleteNetworkRulesOnRvR(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network.id)] ) @@ -1801,7 +1801,7 @@ class TestApplyAndDeleteNetworkRulesOnRvR(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network.id ) self.debug("Associated %s with network %s" % ( @@ -1870,7 +1870,7 @@ class TestApplyAndDeleteNetworkRulesOnRvR(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network.id ) self.debug("Associated %s with network %s" % ( @@ -1902,7 +1902,7 @@ class TestApplyAndDeleteNetworkRulesOnRvR(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network.id ) self.debug("Associated %s with network %s" % ( @@ -2037,7 +2037,7 @@ class TestEnableVPNOverRvR(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id ) @@ -2081,7 +2081,7 @@ class TestEnableVPNOverRvR(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network.id)] ) @@ -2126,7 +2126,7 @@ class TestEnableVPNOverRvR(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network.id ) self.debug("Associated %s with network %s" % ( @@ -2142,7 +2142,7 @@ class TestEnableVPNOverRvR(cloudstackTestCase): self.apiclient, publicipid=public_ip.ipaddress.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) except Exception as e: self.fail("Failed to create VPN for account: %s - %s" % ( @@ -2154,7 +2154,7 @@ class TestEnableVPNOverRvR(cloudstackTestCase): username="root", password="password", account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) except Exception as e: self.fail("Failed to create VPN user: %s" % e) @@ -2163,7 +2163,7 @@ class TestEnableVPNOverRvR(cloudstackTestCase): remote_vpns = Vpn.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, publicipid=public_ip.ipaddress.id, listall=True ) @@ -2184,7 +2184,7 @@ class TestEnableVPNOverRvR(cloudstackTestCase): remote_vpns = Vpn.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, publicipid=public_ip.ipaddress.id, listall=True ) @@ -2306,7 +2306,7 @@ class TestNetworkRulesMasterDownDeleteNetworkRules(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id ) @@ -2350,7 +2350,7 @@ class TestNetworkRulesMasterDownDeleteNetworkRules(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network.id)] ) @@ -2412,7 +2412,7 @@ class TestNetworkRulesMasterDownDeleteNetworkRules(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network.id ) self.debug("Associated %s with network %s" % ( @@ -2481,7 +2481,7 @@ class TestNetworkRulesMasterDownDeleteNetworkRules(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network.id ) self.debug("Associated %s with network %s" % ( @@ -2513,7 +2513,7 @@ class TestNetworkRulesMasterDownDeleteNetworkRules(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network.id ) self.debug("Associated %s with network %s" % ( @@ -2694,7 +2694,7 @@ class TestApplyDeleteNetworkRulesRebootRouter(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id ) @@ -2738,7 +2738,7 @@ class TestApplyDeleteNetworkRulesRebootRouter(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network.id)] ) @@ -2790,7 +2790,7 @@ class TestApplyDeleteNetworkRulesRebootRouter(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network.id ) self.debug("Associated %s with network %s" % ( @@ -2851,7 +2851,7 @@ class TestApplyDeleteNetworkRulesRebootRouter(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network.id ) self.debug("Associated %s with network %s" % ( @@ -2872,7 +2872,7 @@ class TestApplyDeleteNetworkRulesRebootRouter(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network.id ) self.debug("Associated %s with network %s" % ( @@ -3058,7 +3058,7 @@ class TestRestartRvRNetworkWithoutCleanup(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id ) @@ -3102,7 +3102,7 @@ class TestRestartRvRNetworkWithoutCleanup(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network.id)] ) @@ -3279,7 +3279,7 @@ class TestRestartRvRNetworkWithCleanup(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id ) @@ -3323,7 +3323,7 @@ class TestRestartRvRNetworkWithCleanup(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network.id)] ) @@ -3500,7 +3500,7 @@ class TestDeleteRvRNetwork(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id ) @@ -3544,7 +3544,7 @@ class TestDeleteRvRNetwork(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network.id)] ) @@ -3719,7 +3719,7 @@ class TestNetworkGCRvR(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id ) @@ -3763,7 +3763,7 @@ class TestNetworkGCRvR(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network.id)] ) @@ -3999,7 +3999,7 @@ class TestApplyRulesRestartRvRNetwork(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id ) @@ -4043,7 +4043,7 @@ class TestApplyRulesRestartRvRNetwork(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network.id)] ) @@ -4095,7 +4095,7 @@ class TestApplyRulesRestartRvRNetwork(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network.id ) self.debug("Associated %s with network %s" % ( @@ -4156,7 +4156,7 @@ class TestApplyRulesRestartRvRNetwork(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network.id ) self.debug("Associated %s with network %s" % ( @@ -4177,7 +4177,7 @@ class TestApplyRulesRestartRvRNetwork(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network.id ) self.debug("Associated %s with network %s" % ( @@ -4439,7 +4439,7 @@ class TestUpgradeDowngradeRVR(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=network_off_vr.id, zoneid=self.zone.id ) @@ -4471,7 +4471,7 @@ class TestUpgradeDowngradeRVR(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network.id)] ) @@ -4500,7 +4500,7 @@ class TestUpgradeDowngradeRVR(cloudstackTestCase): routers = Router.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -4528,7 +4528,7 @@ class TestUpgradeDowngradeRVR(cloudstackTestCase): routers = Router.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -4574,7 +4574,7 @@ class TestUpgradeDowngradeRVR(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id ) @@ -4606,7 +4606,7 @@ class TestUpgradeDowngradeRVR(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network.id)] ) @@ -4635,7 +4635,7 @@ class TestUpgradeDowngradeRVR(cloudstackTestCase): routers = Router.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -4676,7 +4676,7 @@ class TestUpgradeDowngradeRVR(cloudstackTestCase): routers = Router.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -4808,7 +4808,7 @@ class TestRVRWithDiffEnvs(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id ) @@ -4852,7 +4852,7 @@ class TestRVRWithDiffEnvs(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network.id)] ) @@ -4972,7 +4972,7 @@ class TestRVRWithDiffEnvs(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id ) @@ -5016,7 +5016,7 @@ class TestRVRWithDiffEnvs(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network.id)] ) @@ -5208,7 +5208,7 @@ class TestRVRWithDiffEnvs(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id ) @@ -5265,7 +5265,7 @@ class TestRVRWithDiffEnvs(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network.id)], hostid=host.id @@ -5449,7 +5449,7 @@ class TestRVRWithDiffEnvs(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id ) @@ -5506,7 +5506,7 @@ class TestRVRWithDiffEnvs(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network.id)], hostid=host.id diff --git a/test/integration/component/test_stopped_vm.py b/test/integration/component/test_stopped_vm.py index 10e3d4d0b83..f1096919824 100644 --- a/test/integration/component/test_stopped_vm.py +++ b/test/integration/component/test_stopped_vm.py @@ -188,7 +188,7 @@ class TestDeployVM(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, diskofferingid=self.disk_offering.id, mode=self.zone.networktype @@ -242,7 +242,7 @@ class TestDeployVM(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, startvm=True, diskofferingid=self.disk_offering.id, @@ -299,7 +299,7 @@ class TestDeployVM(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, startvm=False, diskofferingid=self.disk_offering.id, @@ -332,7 +332,7 @@ class TestDeployVM(cloudstackTestCase): routers = Router.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -389,7 +389,7 @@ class TestDeployVM(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, startvm=False, diskofferingid=self.disk_offering.id, @@ -426,7 +426,7 @@ class TestDeployVM(cloudstackTestCase): self.services["volume"], zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, diskofferingid=self.disk_offering.id ) self.debug("Created volume in account: %s" % self.account.name) @@ -457,7 +457,7 @@ class TestDeployVM(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, startvm=False, diskofferingid=self.disk_offering.id, @@ -494,7 +494,7 @@ class TestDeployVM(cloudstackTestCase): self.services["volume"], zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, diskofferingid=self.disk_offering.id ) self.debug("Created volume in account: %s" % self.account.name) @@ -570,7 +570,7 @@ class TestDeployVM(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, startvm=False, diskofferingid=self.disk_offering.id, @@ -607,7 +607,7 @@ class TestDeployVM(cloudstackTestCase): self.services["volume"], zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, diskofferingid=self.disk_offering.id ) self.debug("Created volume in account: %s" % self.account.name) @@ -653,7 +653,7 @@ class TestDeployVM(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, startvm=False, diskofferingid=self.disk_offering.id, @@ -689,7 +689,7 @@ class TestDeployVM(cloudstackTestCase): self.apiclient, self.services["iso"], account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.debug("Successfully created ISO with ID: %s" % iso.id) @@ -746,7 +746,7 @@ class TestDeployVM(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, startvm=False, ) @@ -782,7 +782,7 @@ class TestDeployVM(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, diskofferingid=self.disk_offering.id ) @@ -819,7 +819,7 @@ class TestDeployVM(cloudstackTestCase): self.apiclient, type='DATADISK', account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -943,7 +943,7 @@ class TestDeployHaEnabledVM(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, diskofferingid=self.disk_offering.id, startvm=False @@ -990,7 +990,7 @@ class TestDeployHaEnabledVM(cloudstackTestCase): self.apiclient, self.services["iso"], account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) try: # Dowanload the ISO @@ -1007,7 +1007,7 @@ class TestDeployHaEnabledVM(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, templateid=self.iso.id, serviceofferingid=self.service_offering.id, diskofferingid=self.disk_offering.id, @@ -1057,7 +1057,7 @@ class TestDeployHaEnabledVM(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, diskofferingid=self.disk_offering.id, startvm=False @@ -1175,7 +1175,7 @@ class TestRouterStateAfterDeploy(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, diskofferingid=self.disk_offering.id, startvm=False @@ -1210,7 +1210,7 @@ class TestRouterStateAfterDeploy(cloudstackTestCase): routers = Router.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -1225,7 +1225,7 @@ class TestRouterStateAfterDeploy(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, diskofferingid=self.disk_offering.id, startvm=True @@ -1260,7 +1260,7 @@ class TestRouterStateAfterDeploy(cloudstackTestCase): routers = Router.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -1295,7 +1295,7 @@ class TestRouterStateAfterDeploy(cloudstackTestCase): routers = Router.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertNotEqual( @@ -1397,7 +1397,7 @@ class TestDeployVMBasicZone(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, startvm=True, diskofferingid=self.disk_offering.id, @@ -1455,7 +1455,7 @@ class TestDeployVMBasicZone(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, startvm=False, mode=self.zone.networktype @@ -1570,7 +1570,7 @@ class TestDeployVMFromTemplate(cloudstackTestCase): self.services["template"], zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) try: self.template.download(self.apiclient) @@ -1606,7 +1606,7 @@ class TestDeployVMFromTemplate(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, templateid=self.template.id, startvm=False, @@ -1744,7 +1744,7 @@ class TestVMAccountLimit(cloudstackTestCase): self.apiclient, 0, # Instance account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, max=1 ) self.debug( @@ -1756,7 +1756,7 @@ class TestVMAccountLimit(cloudstackTestCase): self.services["virtual_machine"], templateid=self.template.id, accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, startvm=False ) @@ -1775,7 +1775,7 @@ class TestVMAccountLimit(cloudstackTestCase): self.services["virtual_machine"], templateid=self.template.id, accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, startvm=False ) @@ -1861,7 +1861,7 @@ class TestUploadAttachVolume(cloudstackTestCase): self.services["volume"], zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.debug("Uploading the volume: %s" % volume.name) volume.wait_for_upload(self.apiclient) @@ -1878,7 +1878,7 @@ class TestUploadAttachVolume(cloudstackTestCase): self.services["virtual_machine"], templateid=self.template.id, accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, startvm=False ) @@ -1999,7 +1999,7 @@ class TestDeployOnSpecificHost(cloudstackTestCase): self.services["virtual_machine"], templateid=self.template.id, accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, hostid=host.id ) @@ -2013,7 +2013,7 @@ class TestDeployOnSpecificHost(cloudstackTestCase): id=vm.id, listall=True, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.assertEqual( diff --git a/test/integration/component/test_tags.py b/test/integration/component/test_tags.py index ab5ab310094..12a586313f1 100644 --- a/test/integration/component/test_tags.py +++ b/test/integration/component/test_tags.py @@ -224,7 +224,7 @@ class TestResourceTags(cloudstackTestCase): cls.api_client, cls.services["virtual_machine"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, serviceofferingid=cls.service_offering.id, mode=cls.zone.networktype ) @@ -280,7 +280,7 @@ class TestResourceTags(cloudstackTestCase): networks = Network.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -297,7 +297,7 @@ class TestResourceTags(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network.id ) self.cleanup.append(public_ip) @@ -346,7 +346,7 @@ class TestResourceTags(cloudstackTestCase): resourceType='LoadBalancer', key='LB', account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, value=40 ) @@ -393,7 +393,7 @@ class TestResourceTags(cloudstackTestCase): resourceType='LoadBalancer', key='LB', account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.assertEqual( @@ -423,7 +423,7 @@ class TestResourceTags(cloudstackTestCase): networks = Network.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -440,7 +440,7 @@ class TestResourceTags(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network.id ) self.cleanup.append(public_ip) @@ -484,7 +484,7 @@ class TestResourceTags(cloudstackTestCase): listall=True, resourceType='portForwardingRule', account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, key='PF', value=40 ) @@ -529,7 +529,7 @@ class TestResourceTags(cloudstackTestCase): listall=True, resourceType='portForwardingRule', account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, key='PF', value=40 ) @@ -560,7 +560,7 @@ class TestResourceTags(cloudstackTestCase): networks = Network.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -577,7 +577,7 @@ class TestResourceTags(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network.id ) self.cleanup.append(public_ip) @@ -626,7 +626,7 @@ class TestResourceTags(cloudstackTestCase): listall=True, resourceType='FirewallRule', account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, key='FW', value='40' ) @@ -671,7 +671,7 @@ class TestResourceTags(cloudstackTestCase): listall=True, resourceType='FirewallRule', account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, key='FW', value='40' ) @@ -704,7 +704,7 @@ class TestResourceTags(cloudstackTestCase): networks = Network.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -721,7 +721,7 @@ class TestResourceTags(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network.id ) self.cleanup.append(public_ip) @@ -754,7 +754,7 @@ class TestResourceTags(cloudstackTestCase): self.apiclient, public_ip.ipaddress.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) except Exception as e: @@ -792,7 +792,7 @@ class TestResourceTags(cloudstackTestCase): listall=True, resourceType='VPN', account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, key='protocol', value='L2TP' ) @@ -823,7 +823,7 @@ class TestResourceTags(cloudstackTestCase): listall=True, resourceType='VPN', account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, key='protocol', value='L2TP' ) @@ -863,7 +863,7 @@ class TestResourceTags(cloudstackTestCase): listall=True, resourceType='userVM', account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, key='region', value='India' ) @@ -907,7 +907,7 @@ class TestResourceTags(cloudstackTestCase): listall=True, resourceType='userVM', account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, key='region', value='India' ) @@ -978,7 +978,7 @@ class TestResourceTags(cloudstackTestCase): listall=True, resourceType='Template', account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, key='OS', value='CentOS' ) @@ -1019,7 +1019,7 @@ class TestResourceTags(cloudstackTestCase): listall=True, resourceType='Template', account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, key='OS', value='CentOS' ) @@ -1042,7 +1042,7 @@ class TestResourceTags(cloudstackTestCase): self.apiclient, self.services["iso"], account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.debug("ISO created with ID: %s" % iso.id) @@ -1070,7 +1070,7 @@ class TestResourceTags(cloudstackTestCase): listall=True, resourceType='ISO', account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, key='OS', value='CentOS' ) @@ -1115,7 +1115,7 @@ class TestResourceTags(cloudstackTestCase): listall=True, resourceType='ISO', account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, key='OS', value='CentOS' ) @@ -1141,7 +1141,7 @@ class TestResourceTags(cloudstackTestCase): self.services["volume"], zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, diskofferingid=self.disk_offering.id ) self.cleanup.append(volume) @@ -1162,7 +1162,7 @@ class TestResourceTags(cloudstackTestCase): listall=True, resourceType='volume', account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, key='region', value='India' ) @@ -1205,7 +1205,7 @@ class TestResourceTags(cloudstackTestCase): listall=True, resourceType='volume', account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, key='region' ) self.assertEqual( @@ -1262,7 +1262,7 @@ class TestResourceTags(cloudstackTestCase): listall=True, resourceType='snapshot', account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, key='type', value='manual' ) @@ -1312,7 +1312,7 @@ class TestResourceTags(cloudstackTestCase): listall=True, resourceType='snapshot', account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, key='type', value='manual' ) @@ -1337,7 +1337,7 @@ class TestResourceTags(cloudstackTestCase): networks = Network.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -1363,7 +1363,7 @@ class TestResourceTags(cloudstackTestCase): listall=True, resourceType='Network', account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, key='region', value='India' ) @@ -1381,7 +1381,7 @@ class TestResourceTags(cloudstackTestCase): networks = Network.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True, key='region', value='India' @@ -1409,7 +1409,7 @@ class TestResourceTags(cloudstackTestCase): listall=True, resourceType='Network', account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, key='region', value='India' ) @@ -1479,7 +1479,7 @@ class TestResourceTags(cloudstackTestCase): listall=True, resourceType='userVM', account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, key='region', value='India' ) @@ -1516,7 +1516,7 @@ class TestResourceTags(cloudstackTestCase): listall=True, resourceType='userVM', account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, key='region', value='India' ) @@ -1550,7 +1550,7 @@ class TestResourceTags(cloudstackTestCase): listall=True, resourceType='userVM', account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, key='region', value='India' ) @@ -1596,7 +1596,7 @@ class TestResourceTags(cloudstackTestCase): listall=True, resourceType='userVM', account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, key='region', value='India' ) @@ -1642,7 +1642,7 @@ class TestResourceTags(cloudstackTestCase): listall=True, resourceType='userVM', account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, key='region', value='India' ) @@ -1691,7 +1691,7 @@ class TestResourceTags(cloudstackTestCase): self.apiclient, self.services["project"], account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) # Cleanup created project at end of test self.cleanup.append(project) @@ -1758,7 +1758,7 @@ class TestResourceTags(cloudstackTestCase): listall=True, resourceType='project', account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, key='region', value='India' ) @@ -1799,7 +1799,7 @@ class TestResourceTags(cloudstackTestCase): self.apiclient, self.services["iso"], account=user_account.name, - domainid=user_account.account.domainid + domainid=user_account.domainid ) self.debug("ISO created with ID: %s" % iso.id) @@ -1827,7 +1827,7 @@ class TestResourceTags(cloudstackTestCase): listall=True, resourceType='ISO', account=user_account.name, - domainid=user_account.account.domainid, + domainid=user_account.domainid, key='region', ) @@ -1849,7 +1849,7 @@ class TestResourceTags(cloudstackTestCase): listall=True, resourceType='ISO', account=other_user_account.name, - domainid=other_user_account.account.domainid, + domainid=other_user_account.domainid, key='region', ) @@ -1884,7 +1884,7 @@ class TestResourceTags(cloudstackTestCase): self.apiclient, self.services["iso"], account=user_account.name, - domainid=user_account.account.domainid + domainid=user_account.domainid ) self.debug("ISO created with ID: %s" % iso.id) @@ -1912,7 +1912,7 @@ class TestResourceTags(cloudstackTestCase): listall=True, resourceType='ISO', account=user_account.name, - domainid=user_account.account.domainid, + domainid=user_account.domainid, key='region', ) @@ -2009,7 +2009,7 @@ class TestResourceTags(cloudstackTestCase): listall=True, resourceType='userVM', account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, key='region', value='India' ) @@ -2042,7 +2042,7 @@ class TestResourceTags(cloudstackTestCase): listall=True, resourceType='userVM', account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, key='region', value='India' ) @@ -2065,7 +2065,7 @@ class TestResourceTags(cloudstackTestCase): listall=True, resourceType='userVM', account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, key='region', value='India' ) @@ -2103,7 +2103,7 @@ class TestResourceTags(cloudstackTestCase): self.services["volume"], zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, diskofferingid=self.disk_offering.id ) self.cleanup.append(volume) @@ -2124,7 +2124,7 @@ class TestResourceTags(cloudstackTestCase): listall=True, resourceType='volume', account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, key='region', ) self.assertEqual( @@ -2152,7 +2152,7 @@ class TestResourceTags(cloudstackTestCase): listall=True, resourceType='userVM', account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, key='region', value='India' ) @@ -2185,7 +2185,7 @@ class TestResourceTags(cloudstackTestCase): listall=True, resourceType='userVM', account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, key='region', value='India' ) @@ -2246,7 +2246,7 @@ class TestResourceTags(cloudstackTestCase): listall=True, resourceType='userVM', account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, key='region', value='India' ) @@ -2295,7 +2295,7 @@ class TestResourceTags(cloudstackTestCase): listall=True, resourceType='userVM', account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, key='region', value='India' ) diff --git a/test/integration/component/test_vpc.py b/test/integration/component/test_vpc.py index 83b913a8738..7dbe7c4bf91 100644 --- a/test/integration/component/test_vpc.py +++ b/test/integration/component/test_vpc.py @@ -316,7 +316,7 @@ class TestVPC(cloudstackTestCase): vpcofferingid=self.vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc) @@ -348,7 +348,7 @@ class TestVPC(cloudstackTestCase): vpcofferingid=self.vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc) @@ -373,7 +373,7 @@ class TestVPC(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id, gateway=gateway, @@ -397,7 +397,7 @@ class TestVPC(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering_no_lb.id, zoneid=self.zone.id, gateway=gateway, @@ -432,7 +432,7 @@ class TestVPC(cloudstackTestCase): vpcofferingid=self.vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc) @@ -475,7 +475,7 @@ class TestVPC(cloudstackTestCase): vpcofferingid=self.vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc) @@ -500,7 +500,7 @@ class TestVPC(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id, gateway=gateway, @@ -524,7 +524,7 @@ class TestVPC(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering_no_lb.id, zoneid=self.zone.id, gateway=gateway, @@ -575,7 +575,7 @@ class TestVPC(cloudstackTestCase): routers = Router.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -613,7 +613,7 @@ class TestVPC(cloudstackTestCase): vpcofferingid=self.vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc_1) @@ -624,7 +624,7 @@ class TestVPC(cloudstackTestCase): vpcofferingid=self.vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc_2) @@ -714,7 +714,7 @@ class TestVPC(cloudstackTestCase): supportedservices='Vpn,Dhcp,Dns,SourceNat,PortForwarding,Lb,UserData,StaticNat,NetworkACL', listall=True, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.assertEqual( isinstance(vpcs, list), @@ -733,7 +733,7 @@ class TestVPC(cloudstackTestCase): restartrequired=True, listall=True, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) if vpcs is not None: for vpc in vpcs: @@ -748,7 +748,7 @@ class TestVPC(cloudstackTestCase): restartrequired=False, listall=True, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.assertEqual( isinstance(vpcs, list), @@ -789,7 +789,7 @@ class TestVPC(cloudstackTestCase): vpcofferingid=self.vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc_1) @@ -869,7 +869,7 @@ class TestVPC(cloudstackTestCase): vpcofferingid=vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc) @@ -898,7 +898,7 @@ class TestVPC(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering_no_lb.id, zoneid=self.zone.id, gateway='10.1.1.1', @@ -913,7 +913,7 @@ class TestVPC(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id, gateway='10.1.2.1', @@ -927,7 +927,7 @@ class TestVPC(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network_1.id)] ) @@ -937,7 +937,7 @@ class TestVPC(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network_1.id)] ) @@ -949,7 +949,7 @@ class TestVPC(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network_2.id)] ) @@ -959,7 +959,7 @@ class TestVPC(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network_2.id)] ) @@ -970,7 +970,7 @@ class TestVPC(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network_1.id, vpcid=vpc.id ) @@ -1002,7 +1002,7 @@ class TestVPC(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network_1.id, vpcid=vpc.id ) @@ -1031,7 +1031,7 @@ class TestVPC(cloudstackTestCase): listall=True, isstaticnat=True, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.assertEqual( isinstance(public_ips, list), @@ -1049,7 +1049,7 @@ class TestVPC(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network_2.id, vpcid=vpc.id ) @@ -1068,7 +1068,7 @@ class TestVPC(cloudstackTestCase): accountid=self.account.name, networkid=network_2.id, vpcid=vpc.id, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.debug("Adding virtual machines %s and %s to LB rule" % ( @@ -1214,7 +1214,7 @@ class TestVPC(cloudstackTestCase): vpcofferingid=vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc) @@ -1243,7 +1243,7 @@ class TestVPC(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering_no_lb.id, zoneid=self.zone.id, gateway='10.1.1.1', @@ -1258,7 +1258,7 @@ class TestVPC(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id, gateway='10.1.2.1', @@ -1272,7 +1272,7 @@ class TestVPC(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network_1.id)] ) @@ -1282,7 +1282,7 @@ class TestVPC(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network_1.id)] ) @@ -1294,7 +1294,7 @@ class TestVPC(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network_2.id)] ) @@ -1304,7 +1304,7 @@ class TestVPC(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network_2.id)] ) @@ -1315,7 +1315,7 @@ class TestVPC(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network_1.id, vpcid=vpc.id ) @@ -1347,7 +1347,7 @@ class TestVPC(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network_1.id, vpcid=vpc.id ) @@ -1376,7 +1376,7 @@ class TestVPC(cloudstackTestCase): listall=True, isstaticnat=True, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.assertEqual( isinstance(public_ips, list), @@ -1394,7 +1394,7 @@ class TestVPC(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network_2.id, vpcid=vpc.id ) @@ -1413,7 +1413,7 @@ class TestVPC(cloudstackTestCase): accountid=self.account.name, networkid=network_2.id, vpcid=vpc.id, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.debug("Adding virtual machines %s and %s to LB rule" % ( @@ -1558,7 +1558,7 @@ class TestVPC(cloudstackTestCase): vpcofferingid=self.vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc) @@ -1574,7 +1574,7 @@ class TestVPC(cloudstackTestCase): routers = Router.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -1590,7 +1590,7 @@ class TestVPC(cloudstackTestCase): src_nat_list = PublicIPAddress.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True, issourcenat=True, vpcid=vpc.id @@ -1614,7 +1614,7 @@ class TestVPC(cloudstackTestCase): vpcofferingid=self.vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc_1) @@ -1628,7 +1628,7 @@ class TestVPC(cloudstackTestCase): vpcofferingid=self.vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc_2) @@ -1642,7 +1642,7 @@ class TestVPC(cloudstackTestCase): vpcofferingid=self.vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.debug("%s" % vpc_3) except Exception as e: @@ -1677,7 +1677,7 @@ class TestVPC(cloudstackTestCase): vpcofferingid=self.vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc) self.network_offering = NetworkOffering.create( @@ -1701,7 +1701,7 @@ class TestVPC(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id, gateway=gateway, @@ -1714,7 +1714,7 @@ class TestVPC(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network.id)] ) @@ -1729,7 +1729,7 @@ class TestVPC(cloudstackTestCase): self.apiclient, zoneid=self.zone.id, accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network.id, vpcid=vpc.id ) @@ -1823,7 +1823,7 @@ class TestVPC(cloudstackTestCase): vpcofferingid=self.vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc) @@ -1852,7 +1852,7 @@ class TestVPC(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id, gateway=gateway, @@ -1879,7 +1879,7 @@ class TestVPC(cloudstackTestCase): vpcofferingid=self.vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkDomain=netdomain ) self.validate_vpc_network(vpc) @@ -1904,7 +1904,7 @@ class TestVPC(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id, gateway=gateway, @@ -1917,7 +1917,7 @@ class TestVPC(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network.id)] ) @@ -2355,7 +2355,7 @@ class TestVPC(cloudstackTestCase): self.services["vpc"], zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.services["vpc_no_name"]["cidr"] = "10.1.1.1/16" @@ -2367,7 +2367,7 @@ class TestVPC(cloudstackTestCase): vpcofferingid=self.vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) # Create VPC without zoneid param @@ -2377,7 +2377,7 @@ class TestVPC(cloudstackTestCase): self.services["vpc"], vpcofferingid=self.vpc_off.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) vpc_wo_cidr = {"name": "TestVPC_WO_CIDR", @@ -2392,7 +2392,7 @@ class TestVPC(cloudstackTestCase): vpcofferingid=self.vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) @attr(tags=["advanced", "intervlan"]) @@ -2414,7 +2414,7 @@ class TestVPC(cloudstackTestCase): vpcofferingid=self.vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc) @@ -2439,7 +2439,7 @@ class TestVPC(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id, gateway=gateway, @@ -2671,7 +2671,7 @@ class TestVPCHostMaintenance(cloudstackTestCase): vpcofferingid=self.vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc, state='Disabled') return @@ -2697,7 +2697,7 @@ class TestVPCHostMaintenance(cloudstackTestCase): vpcofferingid=self.vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc, state='Disabled') interval = list_configurations( diff --git a/test/integration/component/test_vpc_host_maintenance.py b/test/integration/component/test_vpc_host_maintenance.py index 4c14f991954..1cce2764fe8 100644 --- a/test/integration/component/test_vpc_host_maintenance.py +++ b/test/integration/component/test_vpc_host_maintenance.py @@ -242,7 +242,7 @@ class TestVMLifeCycleHostmaintenance(cloudstackTestCase): vpcofferingid=cls.vpc_off.id, zoneid=cls.zone.id, account=cls.account.name, - domainid=cls.account.account.domainid + domainid=cls.account.domainid ) cls.nw_off = NetworkOffering.create( @@ -258,7 +258,7 @@ class TestVMLifeCycleHostmaintenance(cloudstackTestCase): cls.api_client, cls.services["network"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, networkofferingid=cls.nw_off.id, zoneid=cls.zone.id, gateway='10.1.1.1', @@ -277,7 +277,7 @@ class TestVMLifeCycleHostmaintenance(cloudstackTestCase): cls.api_client, cls.services["network"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, networkofferingid=cls.nw_off_no_lb.id, zoneid=cls.zone.id, gateway='10.1.2.1', @@ -288,7 +288,7 @@ class TestVMLifeCycleHostmaintenance(cloudstackTestCase): cls.api_client, cls.services["virtual_machine"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, serviceofferingid=cls.service_offering_1.id, networkids=[str(cls.network_1.id)] ) @@ -297,7 +297,7 @@ class TestVMLifeCycleHostmaintenance(cloudstackTestCase): cls.api_client, cls.services["virtual_machine"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, serviceofferingid=cls.service_offering_1.id, networkids=[str(cls.network_1.id)] ) @@ -305,14 +305,14 @@ class TestVMLifeCycleHostmaintenance(cloudstackTestCase): cls.api_client, cls.services["virtual_machine"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, serviceofferingid=cls.service_offering_2.id, networkids=[str(cls.network_2.id)] ) routers = Router.list( cls.api_client, account=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, listall=True ) if isinstance(routers, list): @@ -376,7 +376,7 @@ class TestVMLifeCycleHostmaintenance(cloudstackTestCase): vms = VirtualMachine.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=self.network_1.id, listall=True ) @@ -391,7 +391,7 @@ class TestVMLifeCycleHostmaintenance(cloudstackTestCase): vms = VirtualMachine.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=self.network_2.id, listall=True ) @@ -441,7 +441,7 @@ class TestVMLifeCycleHostmaintenance(cloudstackTestCase): routers = Router.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -458,7 +458,7 @@ class TestVMLifeCycleHostmaintenance(cloudstackTestCase): vms = VirtualMachine.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -618,7 +618,7 @@ class TestVPCNetworkRules(cloudstackTestCase): vpcofferingid=cls.vpc_off.id, zoneid=cls.zone.id, account=cls.account.name, - domainid=cls.account.account.domainid + domainid=cls.account.domainid ) cls.nw_off = NetworkOffering.create( @@ -634,7 +634,7 @@ class TestVPCNetworkRules(cloudstackTestCase): cls.api_client, cls.services["network"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, networkofferingid=cls.nw_off.id, zoneid=cls.zone.id, gateway='10.1.1.1', @@ -653,7 +653,7 @@ class TestVPCNetworkRules(cloudstackTestCase): cls.api_client, cls.services["network"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, networkofferingid=cls.nw_off_no_lb.id, zoneid=cls.zone.id, gateway='10.1.2.1', @@ -664,7 +664,7 @@ class TestVPCNetworkRules(cloudstackTestCase): cls.api_client, cls.services["virtual_machine"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, serviceofferingid=cls.service_offering_1.id, networkids=[str(cls.network_1.id)] ) @@ -673,7 +673,7 @@ class TestVPCNetworkRules(cloudstackTestCase): cls.api_client, cls.services["virtual_machine"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, serviceofferingid=cls.service_offering_2.id, networkids=[str(cls.network_1.id)] ) @@ -681,7 +681,7 @@ class TestVPCNetworkRules(cloudstackTestCase): cls.api_client, cls.services["virtual_machine"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, serviceofferingid=cls.service_offering_1.id, networkids=[str(cls.network_2.id)] ) @@ -689,7 +689,7 @@ class TestVPCNetworkRules(cloudstackTestCase): cls.api_client, cls.services["virtual_machine"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, serviceofferingid=cls.service_offering_2.id, networkids=[str(cls.network_2.id)] ) @@ -739,7 +739,7 @@ class TestVPCNetworkRules(cloudstackTestCase): vms = VirtualMachine.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=self.network_1.id, listall=True ) @@ -754,7 +754,7 @@ class TestVPCNetworkRules(cloudstackTestCase): vms = VirtualMachine.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=self.network_2.id, listall=True ) @@ -800,7 +800,7 @@ class TestVPCNetworkRules(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=self.network_1.id, vpcid=self.vpc.id ) @@ -825,7 +825,7 @@ class TestVPCNetworkRules(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=self.network_2.id, vpcid=self.vpc.id ) diff --git a/test/integration/component/test_vpc_network.py b/test/integration/component/test_vpc_network.py index 0adf9d7fcdc..dc535851858 100644 --- a/test/integration/component/test_vpc_network.py +++ b/test/integration/component/test_vpc_network.py @@ -333,7 +333,7 @@ class TestVPCNetwork(cloudstackTestCase): vpcofferingid=vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc) @@ -353,7 +353,7 @@ class TestVPCNetwork(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id, gateway='10.1.1.1', @@ -426,7 +426,7 @@ class TestVPCNetwork(cloudstackTestCase): vpcofferingid=vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc) @@ -451,7 +451,7 @@ class TestVPCNetwork(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id, gateway='10.1.1.1', @@ -497,7 +497,7 @@ class TestVPCNetwork(cloudstackTestCase): vpcofferingid=vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc) @@ -517,7 +517,7 @@ class TestVPCNetwork(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id, gateway='10.1.1.1', @@ -588,7 +588,7 @@ class TestVPCNetwork(cloudstackTestCase): vpcofferingid=vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc) @@ -608,7 +608,7 @@ class TestVPCNetwork(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id, gateway='10.1.1.1', @@ -646,7 +646,7 @@ class TestVPCNetwork(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id, gateway='10.1.2.1', @@ -693,7 +693,7 @@ class TestVPCNetwork(cloudstackTestCase): vpcofferingid=vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc) @@ -744,7 +744,7 @@ class TestVPCNetwork(cloudstackTestCase): vpcofferingid=vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc) @@ -772,7 +772,7 @@ class TestVPCNetwork(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id, gateway='10.1.2.1', @@ -821,7 +821,7 @@ class TestVPCNetwork(cloudstackTestCase): vpcofferingid=vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc) @@ -842,7 +842,7 @@ class TestVPCNetwork(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id, gateway='10.1.2.1', @@ -890,7 +890,7 @@ class TestVPCNetwork(cloudstackTestCase): vpcofferingid=vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc) @@ -952,7 +952,7 @@ class TestVPCNetwork(cloudstackTestCase): vpcofferingid=vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc) @@ -976,7 +976,7 @@ class TestVPCNetwork(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id, gateway='10.1.1.1', @@ -1022,7 +1022,7 @@ class TestVPCNetwork(cloudstackTestCase): vpcofferingid=vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc) @@ -1193,7 +1193,7 @@ class TestVPCNetworkRanges(cloudstackTestCase): vpcofferingid=vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc) @@ -1215,7 +1215,7 @@ class TestVPCNetworkRanges(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id, gateway='10.2.1.1', @@ -1255,7 +1255,7 @@ class TestVPCNetworkRanges(cloudstackTestCase): vpcofferingid=vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc) @@ -1277,7 +1277,7 @@ class TestVPCNetworkRanges(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id, gateway='10.2.1.1', @@ -1317,7 +1317,7 @@ class TestVPCNetworkRanges(cloudstackTestCase): vpcofferingid=vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc) @@ -1342,7 +1342,7 @@ class TestVPCNetworkRanges(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id, gateway='10.1.1.1', @@ -1384,7 +1384,7 @@ class TestVPCNetworkRanges(cloudstackTestCase): vpcofferingid=vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc) @@ -1406,7 +1406,7 @@ class TestVPCNetworkRanges(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id, gateway='10.1.1.1', @@ -1447,7 +1447,7 @@ class TestVPCNetworkRanges(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id, gateway='10.1.1.1', @@ -1465,7 +1465,7 @@ class TestVPCNetworkRanges(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id, gateway='10.1.1.1', @@ -1505,7 +1505,7 @@ class TestVPCNetworkRanges(cloudstackTestCase): vpcofferingid=vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc) @@ -1540,7 +1540,7 @@ class TestVPCNetworkRanges(cloudstackTestCase): self.apiclient, self.services["network"], accountid=account.name, - domainid=account.account.domainid, + domainid=account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id, gateway='10.1.1.1', @@ -1710,7 +1710,7 @@ class TestVPCNetworkUpgrade(cloudstackTestCase): vpcofferingid=vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc) @@ -1751,7 +1751,7 @@ class TestVPCNetworkUpgrade(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=nw_off_no_pf.id, zoneid=self.zone.id, gateway='10.1.1.1', @@ -1765,7 +1765,7 @@ class TestVPCNetworkUpgrade(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network_1.id)] ) @@ -1774,7 +1774,7 @@ class TestVPCNetworkUpgrade(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network_1.id)] ) @@ -1785,7 +1785,7 @@ class TestVPCNetworkUpgrade(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network_1.id, vpcid=vpc.id ) @@ -1804,7 +1804,7 @@ class TestVPCNetworkUpgrade(cloudstackTestCase): accountid=self.account.name, networkid=network_1.id, vpcid=vpc.id, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.debug("Adding virtual machines %s and %s to LB rule" % ( @@ -1816,7 +1816,7 @@ class TestVPCNetworkUpgrade(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network_1.id, vpcid=vpc.id ) @@ -1845,7 +1845,7 @@ class TestVPCNetworkUpgrade(cloudstackTestCase): listall=True, isstaticnat=True, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.assertEqual( isinstance(public_ips, list), @@ -1934,7 +1934,7 @@ class TestVPCNetworkUpgrade(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network_1.id, vpcid=vpc.id ) @@ -2059,7 +2059,7 @@ class TestVPCNetworkUpgrade(cloudstackTestCase): vpcofferingid=vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc) @@ -2099,7 +2099,7 @@ class TestVPCNetworkUpgrade(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=nw_off.id, zoneid=self.zone.id, gateway='10.1.1.1', @@ -2113,7 +2113,7 @@ class TestVPCNetworkUpgrade(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network_1.id)] ) @@ -2182,7 +2182,7 @@ class TestVPCNetworkGc(cloudstackTestCase): vpcofferingid=cls.vpc_off.id, zoneid=cls.zone.id, account=cls.account.name, - domainid=cls.account.account.domainid + domainid=cls.account.domainid ) cls.nw_off = NetworkOffering.create( @@ -2197,7 +2197,7 @@ class TestVPCNetworkGc(cloudstackTestCase): cls.api_client, cls.services["network"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, networkofferingid=cls.nw_off.id, zoneid=cls.zone.id, gateway='10.1.1.1', @@ -2208,7 +2208,7 @@ class TestVPCNetworkGc(cloudstackTestCase): cls.api_client, cls.services["virtual_machine"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, serviceofferingid=cls.service_offering.id, networkids=[str(cls.network_1.id)] ) @@ -2216,7 +2216,7 @@ class TestVPCNetworkGc(cloudstackTestCase): cls.api_client, cls.services["virtual_machine"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, serviceofferingid=cls.service_offering.id, networkids=[str(cls.network_1.id)] ) @@ -2224,7 +2224,7 @@ class TestVPCNetworkGc(cloudstackTestCase): cls.api_client, accountid=cls.account.name, zoneid=cls.zone.id, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, networkid=cls.network_1.id, vpcid=cls.vpc.id ) @@ -2235,7 +2235,7 @@ class TestVPCNetworkGc(cloudstackTestCase): accountid=cls.account.name, networkid=cls.network_1.id, vpcid=cls.vpc.id, - domainid=cls.account.account.domainid + domainid=cls.account.domainid ) cls.lb_rule.assign(cls.api_client, [cls.vm_1, cls.vm_2]) @@ -2243,7 +2243,7 @@ class TestVPCNetworkGc(cloudstackTestCase): cls.api_client, accountid=cls.account.name, zoneid=cls.zone.id, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, networkid=cls.network_1.id, vpcid=cls.vpc.id ) @@ -2289,7 +2289,7 @@ class TestVPCNetworkGc(cloudstackTestCase): vms = VirtualMachine.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) for vm in vms: @@ -2305,7 +2305,7 @@ class TestVPCNetworkGc(cloudstackTestCase): vms = VirtualMachine.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) for vm in vms: @@ -2396,7 +2396,7 @@ class TestVPCNetworkGc(cloudstackTestCase): routers = Router.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -2515,7 +2515,7 @@ class TestVPCNetworkGc(cloudstackTestCase): routers = Router.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( diff --git a/test/integration/component/test_vpc_network_lbrules.py b/test/integration/component/test_vpc_network_lbrules.py index a24e8139b95..b4a66070d5b 100644 --- a/test/integration/component/test_vpc_network_lbrules.py +++ b/test/integration/component/test_vpc_network_lbrules.py @@ -244,7 +244,7 @@ class TestVPCNetworkLBRules(cloudstackTestCase): vpcofferingid=self.vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) return @@ -264,7 +264,7 @@ class TestVPCNetworkLBRules(cloudstackTestCase): def get_Router_For_VPC(self): routers = list_routers(self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, ) self.assertEqual(isinstance(routers, list), True, @@ -287,7 +287,7 @@ class TestVPCNetworkLBRules(cloudstackTestCase): routers = list_routers(self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, ) self.assertEqual(isinstance(routers, list), True, @@ -308,7 +308,7 @@ class TestVPCNetworkLBRules(cloudstackTestCase): routers = list_routers(self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, zoneid=self.zone.id ) self.assertEqual(isinstance(routers, list), @@ -391,7 +391,7 @@ class TestVPCNetworkLBRules(cloudstackTestCase): public_ip = PublicIPAddress.create(self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=None, #network.id, vpcid=self.vpc.id ) @@ -420,7 +420,7 @@ class TestVPCNetworkLBRules(cloudstackTestCase): vpcofferingid=vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) return vpc @@ -442,7 +442,7 @@ class TestVPCNetworkLBRules(cloudstackTestCase): obj_network = Network.create(self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=nw_off.id, zoneid=self.zone.id, gateway=gateway, @@ -460,7 +460,7 @@ class TestVPCNetworkLBRules(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network.id)], hostid=host_id @@ -487,7 +487,7 @@ class TestVPCNetworkLBRules(cloudstackTestCase): accountid=self.account.name, networkid=network.id, vpcid=self.vpc.id, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.debug("Adding virtual machines %s and %s to LB rule" % (vmarray)) lb_rule.assign(self.apiclient, vmarray) diff --git a/test/integration/component/test_vpc_network_pfrules.py b/test/integration/component/test_vpc_network_pfrules.py index aac956810d1..56792f49d00 100644 --- a/test/integration/component/test_vpc_network_pfrules.py +++ b/test/integration/component/test_vpc_network_pfrules.py @@ -243,7 +243,7 @@ class TestVPCNetworkPFRules(cloudstackTestCase): vpcofferingid=self.vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) return @@ -262,7 +262,7 @@ class TestVPCNetworkPFRules(cloudstackTestCase): def get_Router_For_VPC(self): routers = list_routers(self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, ) self.assertEqual(isinstance(routers, list), True, @@ -285,7 +285,7 @@ class TestVPCNetworkPFRules(cloudstackTestCase): routers = list_routers(self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, ) self.assertEqual(isinstance(routers, list), True, @@ -306,7 +306,7 @@ class TestVPCNetworkPFRules(cloudstackTestCase): routers = list_routers(self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, zoneid=self.zone.id ) self.assertEqual(isinstance(routers, list), @@ -391,7 +391,7 @@ class TestVPCNetworkPFRules(cloudstackTestCase): public_ip = PublicIPAddress.create(self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=None, #network.id, vpcid=self.vpc.id ) @@ -420,7 +420,7 @@ class TestVPCNetworkPFRules(cloudstackTestCase): vpcofferingid=vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) return vpc @@ -442,7 +442,7 @@ class TestVPCNetworkPFRules(cloudstackTestCase): obj_network = Network.create(self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=nw_off.id, zoneid=self.zone.id, gateway=gateway, @@ -460,7 +460,7 @@ class TestVPCNetworkPFRules(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network.id)], hostid=host_id @@ -487,7 +487,7 @@ class TestVPCNetworkPFRules(cloudstackTestCase): accountid=self.account.name, networkid=network.id, vpcid=self.vpc.id, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.debug("Adding virtual machines %s and %s to LB rule" % (vmarray)) lb_rule.assign(self.apiclient, vmarray) diff --git a/test/integration/component/test_vpc_network_staticnatrule.py b/test/integration/component/test_vpc_network_staticnatrule.py index 842d20ad089..aceca62d1fb 100644 --- a/test/integration/component/test_vpc_network_staticnatrule.py +++ b/test/integration/component/test_vpc_network_staticnatrule.py @@ -243,7 +243,7 @@ class TestVPCNetworkPFRules(cloudstackTestCase): vpcofferingid=self.vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) return @@ -262,7 +262,7 @@ class TestVPCNetworkPFRules(cloudstackTestCase): def get_Router_For_VPC(self): routers = list_routers(self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, ) self.assertEqual(isinstance(routers, list), True, @@ -285,7 +285,7 @@ class TestVPCNetworkPFRules(cloudstackTestCase): routers = list_routers(self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, ) self.assertEqual(isinstance(routers, list), True, @@ -306,7 +306,7 @@ class TestVPCNetworkPFRules(cloudstackTestCase): routers = list_routers(self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, zoneid=self.zone.id ) self.assertEqual(isinstance(routers, list), @@ -391,7 +391,7 @@ class TestVPCNetworkPFRules(cloudstackTestCase): public_ip = PublicIPAddress.create(self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=None, #network.id, vpcid=self.vpc.id ) @@ -420,7 +420,7 @@ class TestVPCNetworkPFRules(cloudstackTestCase): vpcofferingid=vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) return vpc @@ -442,7 +442,7 @@ class TestVPCNetworkPFRules(cloudstackTestCase): obj_network = Network.create(self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=nw_off.id, zoneid=self.zone.id, gateway=gateway, @@ -460,7 +460,7 @@ class TestVPCNetworkPFRules(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network.id)], hostid=host_id @@ -487,7 +487,7 @@ class TestVPCNetworkPFRules(cloudstackTestCase): accountid=self.account.name, networkid=network.id, vpcid=self.vpc.id, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.debug("Adding virtual machines %s and %s to LB rule" % (vmarray)) lb_rule.assign(self.apiclient, vmarray) diff --git a/test/integration/component/test_vpc_offerings.py b/test/integration/component/test_vpc_offerings.py index 033a90522c4..4b9877506f2 100644 --- a/test/integration/component/test_vpc_offerings.py +++ b/test/integration/component/test_vpc_offerings.py @@ -315,7 +315,7 @@ class TestVPCOffering(cloudstackTestCase): vpcofferingid=vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc) @@ -340,7 +340,7 @@ class TestVPCOffering(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id, gateway=gateway, @@ -352,7 +352,7 @@ class TestVPCOffering(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network.id)] ) @@ -363,7 +363,7 @@ class TestVPCOffering(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network.id, vpcid=vpc.id ) @@ -382,7 +382,7 @@ class TestVPCOffering(cloudstackTestCase): accountid=self.account.name, networkid=network.id, vpcid=vpc.id, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.debug("Associating public IP for network: %s" % vpc.name) @@ -390,7 +390,7 @@ class TestVPCOffering(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network.id, vpcid=vpc.id ) @@ -438,7 +438,7 @@ class TestVPCOffering(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network.id, vpcid=vpc.id ) @@ -467,7 +467,7 @@ class TestVPCOffering(cloudstackTestCase): listall=True, isstaticnat=True, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.assertEqual( isinstance(public_ips, list), @@ -485,7 +485,7 @@ class TestVPCOffering(cloudstackTestCase): # self.apiclient, # accountid=self.account.name, # zoneid=self.zone.id, -# domainid=self.account.account.domainid, +# domainid=self.account.domainid, # networkid=network.id, # vpcid=vpc.id # ) @@ -502,7 +502,7 @@ class TestVPCOffering(cloudstackTestCase): # self.apiclient, # publicipid=public_ip_4.ipaddress.id, # account=self.account.name, -# domainid=self.account.account.domainid, +# domainid=self.account.domainid, # networkid=network.id, # vpcid=vpc.id # ) @@ -516,7 +516,7 @@ class TestVPCOffering(cloudstackTestCase): # username="root", # password="password", # account=self.account.name, -# domainid=self.account.account.domainid +# domainid=self.account.domainid # ) # except Exception as e: # self.fail("Failed to create VPN user: %s" % e) @@ -525,7 +525,7 @@ class TestVPCOffering(cloudstackTestCase): # remote_vpns = Vpn.list( # self.apiclient, # account=self.account.name, -# domainid=self.account.account.domainid, +# domainid=self.account.domainid, # publicipid=public_ip_4.ipaddress.id, # listall=True # ) @@ -596,7 +596,7 @@ class TestVPCOffering(cloudstackTestCase): vpcofferingid=vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc) @@ -612,7 +612,7 @@ class TestVPCOffering(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id, gateway=gateway, @@ -626,7 +626,7 @@ class TestVPCOffering(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network.id)] ) @@ -637,7 +637,7 @@ class TestVPCOffering(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network.id, vpcid=vpc.id ) @@ -715,7 +715,7 @@ class TestVPCOffering(cloudstackTestCase): vpcofferingid=vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc) @@ -731,7 +731,7 @@ class TestVPCOffering(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id, gateway=gateway, @@ -745,7 +745,7 @@ class TestVPCOffering(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network.id)] ) @@ -756,7 +756,7 @@ class TestVPCOffering(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network.id, vpcid=vpc.id ) @@ -836,7 +836,7 @@ class TestVPCOffering(cloudstackTestCase): vpcofferingid=vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc) @@ -852,7 +852,7 @@ class TestVPCOffering(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=self.network_offering.id, zoneid=self.zone.id, gateway=gateway, @@ -864,7 +864,7 @@ class TestVPCOffering(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network.id)] ) @@ -875,7 +875,7 @@ class TestVPCOffering(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network.id, vpcid=vpc.id ) @@ -975,7 +975,7 @@ class TestVPCOffering(cloudstackTestCase): vpcofferingid=vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.debug("VPC network creation failed! (Test succeeded)") self.debug("Enabling the VPC offering created") @@ -989,7 +989,7 @@ class TestVPCOffering(cloudstackTestCase): vpcofferingid=vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc) diff --git a/test/integration/component/test_vpc_routers.py b/test/integration/component/test_vpc_routers.py index 55cb513130f..763a4cb14f9 100644 --- a/test/integration/component/test_vpc_routers.py +++ b/test/integration/component/test_vpc_routers.py @@ -214,7 +214,7 @@ class TestVPCRoutersBasic(cloudstackTestCase): vpcofferingid=cls.vpc_off.id, zoneid=cls.zone.id, account=cls.account.name, - domainid=cls.account.account.domainid + domainid=cls.account.domainid ) cls._cleanup.append(cls.service_offering) @@ -361,7 +361,7 @@ class TestVPCRoutersBasic(cloudstackTestCase): routers = Router.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -436,7 +436,7 @@ class TestVPCRoutersBasic(cloudstackTestCase): routers = Router.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -483,7 +483,7 @@ class TestVPCRoutersBasic(cloudstackTestCase): routers = Router.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -499,7 +499,7 @@ class TestVPCRoutersBasic(cloudstackTestCase): routers = Router.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -520,7 +520,7 @@ class TestVPCRoutersBasic(cloudstackTestCase): routers = Router.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -548,7 +548,7 @@ class TestVPCRoutersBasic(cloudstackTestCase): routers = Router.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -581,7 +581,7 @@ class TestVPCRoutersBasic(cloudstackTestCase): routers = Router.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) router = routers[0] @@ -639,7 +639,7 @@ class TestVPCRouterOneNetwork(cloudstackTestCase): vpcofferingid=cls.vpc_off.id, zoneid=cls.zone.id, account=cls.account.name, - domainid=cls.account.account.domainid + domainid=cls.account.domainid ) cls.nw_off = NetworkOffering.create( @@ -656,7 +656,7 @@ class TestVPCRouterOneNetwork(cloudstackTestCase): cls.apiclient, cls.services["network"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, networkofferingid=cls.nw_off.id, zoneid=cls.zone.id, gateway='10.1.1.1', @@ -668,7 +668,7 @@ class TestVPCRouterOneNetwork(cloudstackTestCase): cls.apiclient, cls.services["virtual_machine"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, serviceofferingid=cls.service_offering.id, networkids=[str(cls.network_1.id)] ) @@ -676,7 +676,7 @@ class TestVPCRouterOneNetwork(cloudstackTestCase): cls.apiclient, cls.services["virtual_machine"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, serviceofferingid=cls.service_offering.id, networkids=[str(cls.network_1.id)] ) @@ -686,7 +686,7 @@ class TestVPCRouterOneNetwork(cloudstackTestCase): cls.apiclient, cls.services["virtual_machine"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, serviceofferingid=cls.service_offering.id, networkids=[str(cls.network_1.id)] ) @@ -694,14 +694,14 @@ class TestVPCRouterOneNetwork(cloudstackTestCase): vms = VirtualMachine.list( cls.apiclient, account=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, listall=True ) public_ip_1 = PublicIPAddress.create( cls.apiclient, accountid=cls.account.name, zoneid=cls.zone.id, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, networkid=cls.network_1.id, vpcid=cls.vpc.id ) @@ -727,7 +727,7 @@ class TestVPCRouterOneNetwork(cloudstackTestCase): cls.apiclient, accountid=cls.account.name, zoneid=cls.zone.id, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, networkid=cls.network_1.id, vpcid=cls.vpc.id ) @@ -748,7 +748,7 @@ class TestVPCRouterOneNetwork(cloudstackTestCase): listall=True, isstaticnat=True, account=cls.account.name, - domainid=cls.account.account.domainid + domainid=cls.account.domainid ) # cls.assertEqual( # isinstance(public_ips, list), @@ -766,7 +766,7 @@ class TestVPCRouterOneNetwork(cloudstackTestCase): cls.apiclient, accountid=cls.account.name, zoneid=cls.zone.id, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, networkid=cls.network_1.id, vpcid=cls.vpc.id ) @@ -779,7 +779,7 @@ class TestVPCRouterOneNetwork(cloudstackTestCase): accountid=cls.account.name, networkid=cls.network_1.id, vpcid=cls.vpc.id, - domainid=cls.account.account.domainid + domainid=cls.account.domainid ) lb_rule.assign(cls.apiclient, [vm_3]) @@ -961,13 +961,13 @@ class TestVPCRouterOneNetwork(cloudstackTestCase): vms = VirtualMachine.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) public_ips = PublicIPAddress.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) for vm, public_ip in zip(vms, public_ips): @@ -1086,7 +1086,7 @@ class TestVPCRouterOneNetwork(cloudstackTestCase): routers = Router.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -1180,7 +1180,7 @@ class TestVPCRouterOneNetwork(cloudstackTestCase): routers = Router.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -1247,7 +1247,7 @@ class TestVPCRouterOneNetwork(cloudstackTestCase): routers = Router.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -1263,7 +1263,7 @@ class TestVPCRouterOneNetwork(cloudstackTestCase): routers = Router.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -1306,7 +1306,7 @@ class TestVPCRouterOneNetwork(cloudstackTestCase): routers = Router.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -1351,7 +1351,7 @@ class TestVPCRouterOneNetwork(cloudstackTestCase): routers = Router.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -1384,7 +1384,7 @@ class TestVPCRouterOneNetwork(cloudstackTestCase): routers = Router.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) router = routers[0] diff --git a/test/integration/component/test_vpc_vm_life_cycle.py b/test/integration/component/test_vpc_vm_life_cycle.py index 13726c58ce4..7658bebe2e2 100644 --- a/test/integration/component/test_vpc_vm_life_cycle.py +++ b/test/integration/component/test_vpc_vm_life_cycle.py @@ -237,7 +237,7 @@ class TestVMLifeCycleVPC(cloudstackTestCase): vpcofferingid=cls.vpc_off.id, zoneid=cls.zone.id, account=cls.account.name, - domainid=cls.account.account.domainid + domainid=cls.account.domainid ) cls.nw_off = NetworkOffering.create( @@ -253,7 +253,7 @@ class TestVMLifeCycleVPC(cloudstackTestCase): cls.api_client, cls.services["network"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, networkofferingid=cls.nw_off.id, zoneid=cls.zone.id, gateway='10.1.1.1', @@ -272,7 +272,7 @@ class TestVMLifeCycleVPC(cloudstackTestCase): cls.api_client, cls.services["network"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, networkofferingid=cls.nw_off_no_lb.id, zoneid=cls.zone.id, gateway='10.1.2.1', @@ -283,7 +283,7 @@ class TestVMLifeCycleVPC(cloudstackTestCase): cls.api_client, cls.services["virtual_machine"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, serviceofferingid=cls.service_offering.id, networkids=[str(cls.network_1.id)] ) @@ -292,7 +292,7 @@ class TestVMLifeCycleVPC(cloudstackTestCase): cls.api_client, cls.services["virtual_machine"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, serviceofferingid=cls.service_offering.id, networkids=[str(cls.network_1.id)] ) @@ -300,7 +300,7 @@ class TestVMLifeCycleVPC(cloudstackTestCase): cls.api_client, cls.services["virtual_machine"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, serviceofferingid=cls.service_offering.id, networkids=[str(cls.network_2.id)] ) @@ -309,7 +309,7 @@ class TestVMLifeCycleVPC(cloudstackTestCase): cls.api_client, accountid=cls.account.name, zoneid=cls.zone.id, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, networkid=cls.network_1.id, vpcid=cls.vpc.id ) @@ -320,7 +320,7 @@ class TestVMLifeCycleVPC(cloudstackTestCase): accountid=cls.account.name, networkid=cls.network_1.id, vpcid=cls.vpc.id, - domainid=cls.account.account.domainid + domainid=cls.account.domainid ) cls.lb_rule.assign(cls.api_client, [cls.vm_1, cls.vm_2]) @@ -328,7 +328,7 @@ class TestVMLifeCycleVPC(cloudstackTestCase): cls.api_client, accountid=cls.account.name, zoneid=cls.zone.id, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, networkid=cls.network_1.id, vpcid=cls.vpc.id ) @@ -516,7 +516,7 @@ class TestVMLifeCycleVPC(cloudstackTestCase): vms = VirtualMachine.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -966,7 +966,7 @@ class TestVMLifeCycleSharedNwVPC(cloudstackTestCase): vpcofferingid=cls.vpc_off.id, zoneid=cls.zone.id, account=cls.account.name, - domainid=cls.account.account.domainid + domainid=cls.account.domainid ) cls.nw_off = NetworkOffering.create( @@ -982,7 +982,7 @@ class TestVMLifeCycleSharedNwVPC(cloudstackTestCase): cls.api_client, cls.services["network"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, networkofferingid=cls.nw_off.id, zoneid=cls.zone.id, gateway='10.1.1.1', @@ -1006,7 +1006,7 @@ class TestVMLifeCycleSharedNwVPC(cloudstackTestCase): cls.api_client, cls.services["network"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, networkofferingid=cls.shared_nw_off.id, zoneid=cls.zone.id, gateway='10.1.2.1', @@ -1017,7 +1017,7 @@ class TestVMLifeCycleSharedNwVPC(cloudstackTestCase): cls.api_client, cls.services["virtual_machine"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, serviceofferingid=cls.service_offering.id, networkids=[str(cls.network_1.id), str(cls.network_2.id)] @@ -1027,7 +1027,7 @@ class TestVMLifeCycleSharedNwVPC(cloudstackTestCase): cls.api_client, cls.services["virtual_machine"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, serviceofferingid=cls.service_offering.id, networkids=[str(cls.network_1.id), str(cls.network_2.id)] @@ -1036,7 +1036,7 @@ class TestVMLifeCycleSharedNwVPC(cloudstackTestCase): cls.api_client, cls.services["virtual_machine"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, serviceofferingid=cls.service_offering.id, networkids=[str(cls.network_1.id), str(cls.network_2.id)] @@ -1045,7 +1045,7 @@ class TestVMLifeCycleSharedNwVPC(cloudstackTestCase): cls.api_client, accountid=cls.account.name, zoneid=cls.zone.id, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, networkid=cls.network_1.id, vpcid=cls.vpc.id ) @@ -1056,7 +1056,7 @@ class TestVMLifeCycleSharedNwVPC(cloudstackTestCase): accountid=cls.account.name, networkid=cls.network_1.id, vpcid=cls.vpc.id, - domainid=cls.account.account.domainid + domainid=cls.account.domainid ) cls.lb_rule.assign(cls.api_client, [cls.vm_1, cls.vm_2, cls.vm_3]) @@ -1064,7 +1064,7 @@ class TestVMLifeCycleSharedNwVPC(cloudstackTestCase): cls.api_client, accountid=cls.account.name, zoneid=cls.zone.id, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, networkid=cls.network_1.id, vpcid=cls.vpc.id ) @@ -1242,7 +1242,7 @@ class TestVMLifeCycleSharedNwVPC(cloudstackTestCase): vms = VirtualMachine.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -1368,7 +1368,7 @@ class TestVMLifeCycleSharedNwVPC(cloudstackTestCase): vms = VirtualMachine.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -1773,7 +1773,7 @@ class TestVMLifeCycleBothIsolated(cloudstackTestCase): vpcofferingid=cls.vpc_off.id, zoneid=cls.zone.id, account=cls.account.name, - domainid=cls.account.account.domainid + domainid=cls.account.domainid ) cls.nw_off = NetworkOffering.create( @@ -1789,7 +1789,7 @@ class TestVMLifeCycleBothIsolated(cloudstackTestCase): cls.api_client, cls.services["network"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, networkofferingid=cls.nw_off.id, zoneid=cls.zone.id, gateway='10.1.1.1', @@ -1809,7 +1809,7 @@ class TestVMLifeCycleBothIsolated(cloudstackTestCase): cls.api_client, cls.services["network"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, networkofferingid=cls.nw_off_no_lb.id, zoneid=cls.zone.id, gateway='10.1.2.1', @@ -1965,7 +1965,7 @@ class TestVMLifeCycleBothIsolated(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(self.network_1.id), str(self.network_2.id)] @@ -1990,7 +1990,7 @@ class TestVMLifeCycleBothIsolated(cloudstackTestCase): routers = Router.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=self.network_1.id, listall=True ) @@ -2033,7 +2033,7 @@ class TestVMLifeCycleBothIsolated(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(self.network_1.id)] ) @@ -2111,7 +2111,7 @@ class TestVMLifeCycleStoppedVPCVR(cloudstackTestCase): vpcofferingid=cls.vpc_off.id, zoneid=cls.zone.id, account=cls.account.name, - domainid=cls.account.account.domainid + domainid=cls.account.domainid ) cls.nw_off = NetworkOffering.create( @@ -2127,7 +2127,7 @@ class TestVMLifeCycleStoppedVPCVR(cloudstackTestCase): cls.api_client, cls.services["network"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, networkofferingid=cls.nw_off.id, zoneid=cls.zone.id, gateway='10.1.1.1', @@ -2146,7 +2146,7 @@ class TestVMLifeCycleStoppedVPCVR(cloudstackTestCase): cls.api_client, cls.services["network"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, networkofferingid=cls.nw_off_no_lb.id, zoneid=cls.zone.id, gateway='10.1.2.1', @@ -2157,7 +2157,7 @@ class TestVMLifeCycleStoppedVPCVR(cloudstackTestCase): cls.api_client, cls.services["virtual_machine"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, serviceofferingid=cls.service_offering.id, networkids=[str(cls.network_1.id)] ) @@ -2166,7 +2166,7 @@ class TestVMLifeCycleStoppedVPCVR(cloudstackTestCase): cls.api_client, cls.services["virtual_machine"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, serviceofferingid=cls.service_offering.id, networkids=[str(cls.network_1.id)] ) @@ -2174,7 +2174,7 @@ class TestVMLifeCycleStoppedVPCVR(cloudstackTestCase): cls.api_client, cls.services["virtual_machine"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, serviceofferingid=cls.service_offering.id, networkids=[str(cls.network_2.id)] ) @@ -2183,7 +2183,7 @@ class TestVMLifeCycleStoppedVPCVR(cloudstackTestCase): cls.api_client, accountid=cls.account.name, zoneid=cls.zone.id, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, networkid=cls.network_1.id, vpcid=cls.vpc.id ) @@ -2194,7 +2194,7 @@ class TestVMLifeCycleStoppedVPCVR(cloudstackTestCase): accountid=cls.account.name, networkid=cls.network_1.id, vpcid=cls.vpc.id, - domainid=cls.account.account.domainid + domainid=cls.account.domainid ) cls.lb_rule.assign(cls.api_client, [cls.vm_1, cls.vm_2]) @@ -2202,7 +2202,7 @@ class TestVMLifeCycleStoppedVPCVR(cloudstackTestCase): cls.api_client, accountid=cls.account.name, zoneid=cls.zone.id, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, networkid=cls.network_1.id, vpcid=cls.vpc.id ) @@ -2405,7 +2405,7 @@ class TestVMLifeCycleStoppedVPCVR(cloudstackTestCase): vms = VirtualMachine.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -2868,7 +2868,7 @@ class TestVMLifeCycleDiffHosts(cloudstackTestCase): vpcofferingid=cls.vpc_off.id, zoneid=cls.zone.id, account=cls.account.name, - domainid=cls.account.account.domainid + domainid=cls.account.domainid ) cls.nw_off = NetworkOffering.create( @@ -2884,7 +2884,7 @@ class TestVMLifeCycleDiffHosts(cloudstackTestCase): cls.api_client, cls.services["network"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, networkofferingid=cls.nw_off.id, zoneid=cls.zone.id, gateway='10.1.1.1', @@ -2903,7 +2903,7 @@ class TestVMLifeCycleDiffHosts(cloudstackTestCase): cls.api_client, cls.services["network"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, networkofferingid=cls.nw_off_no_lb.id, zoneid=cls.zone.id, gateway='10.1.2.1', @@ -2914,7 +2914,7 @@ class TestVMLifeCycleDiffHosts(cloudstackTestCase): cls.api_client, cls.services["virtual_machine"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, serviceofferingid=cls.service_offering_1.id, networkids=[str(cls.network_1.id)] ) @@ -2923,7 +2923,7 @@ class TestVMLifeCycleDiffHosts(cloudstackTestCase): cls.api_client, cls.services["virtual_machine"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, serviceofferingid=cls.service_offering_1.id, networkids=[str(cls.network_1.id)] ) @@ -2931,7 +2931,7 @@ class TestVMLifeCycleDiffHosts(cloudstackTestCase): cls.api_client, cls.services["virtual_machine"], accountid=cls.account.name, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, serviceofferingid=cls.service_offering_2.id, networkids=[str(cls.network_2.id)] ) @@ -2940,7 +2940,7 @@ class TestVMLifeCycleDiffHosts(cloudstackTestCase): cls.api_client, accountid=cls.account.name, zoneid=cls.zone.id, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, networkid=cls.network_1.id, vpcid=cls.vpc.id ) @@ -2951,7 +2951,7 @@ class TestVMLifeCycleDiffHosts(cloudstackTestCase): accountid=cls.account.name, networkid=cls.network_1.id, vpcid=cls.vpc.id, - domainid=cls.account.account.domainid + domainid=cls.account.domainid ) cls.lb_rule.assign(cls.api_client, [cls.vm_1, cls.vm_2]) @@ -2959,7 +2959,7 @@ class TestVMLifeCycleDiffHosts(cloudstackTestCase): cls.api_client, accountid=cls.account.name, zoneid=cls.zone.id, - domainid=cls.account.account.domainid, + domainid=cls.account.domainid, networkid=cls.network_1.id, vpcid=cls.vpc.id ) @@ -3057,7 +3057,7 @@ class TestVMLifeCycleDiffHosts(cloudstackTestCase): vms = VirtualMachine.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=self.network_1.id, listall=True ) @@ -3072,7 +3072,7 @@ class TestVMLifeCycleDiffHosts(cloudstackTestCase): vms = VirtualMachine.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=self.network_2.id, listall=True ) @@ -3204,7 +3204,7 @@ class TestVMLifeCycleDiffHosts(cloudstackTestCase): vms = VirtualMachine.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( diff --git a/test/integration/component/test_vpc_vms_deployment.py b/test/integration/component/test_vpc_vms_deployment.py index 506ae348867..c49ef458a40 100644 --- a/test/integration/component/test_vpc_vms_deployment.py +++ b/test/integration/component/test_vpc_vms_deployment.py @@ -320,7 +320,7 @@ class TestVMDeployVPC(cloudstackTestCase): vpcofferingid=vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc) @@ -339,7 +339,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=nw_off.id, zoneid=self.zone.id, gateway='10.1.1.1', @@ -363,7 +363,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=nw_off_no_lb.id, zoneid=self.zone.id, gateway='10.1.2.1', @@ -378,7 +378,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=nw_off_no_lb.id, zoneid=self.zone.id, gateway='10.1.3.1', @@ -392,7 +392,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network_1.id)] ) @@ -404,7 +404,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network_2.id)] ) @@ -415,7 +415,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network_3.id)] ) @@ -425,7 +425,7 @@ class TestVMDeployVPC(cloudstackTestCase): vms = VirtualMachine.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -468,7 +468,7 @@ class TestVMDeployVPC(cloudstackTestCase): vms = VirtualMachine.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, state="Running", listall=True ) @@ -535,7 +535,7 @@ class TestVMDeployVPC(cloudstackTestCase): vpcofferingid=vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc) @@ -554,7 +554,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=nw_off.id, zoneid=self.zone.id, gateway='10.1.1.1', @@ -578,7 +578,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=nw_off_no_lb.id, zoneid=self.zone.id, gateway='10.1.2.1', @@ -593,7 +593,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=nw_off_no_lb.id, zoneid=self.zone.id, gateway='10.1.3.1', @@ -607,7 +607,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network_1.id)] ) @@ -619,7 +619,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network_2.id)] ) @@ -630,7 +630,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network_3.id)] ) @@ -640,7 +640,7 @@ class TestVMDeployVPC(cloudstackTestCase): vms = VirtualMachine.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -720,7 +720,7 @@ class TestVMDeployVPC(cloudstackTestCase): vms = VirtualMachine.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, state="Running", listall=True ) @@ -788,7 +788,7 @@ class TestVMDeployVPC(cloudstackTestCase): vpcofferingid=vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc) @@ -807,7 +807,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=nw_off.id, zoneid=self.zone.id, gateway='10.1.1.1', @@ -831,7 +831,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=nw_off_no_lb.id, zoneid=self.zone.id, gateway='10.1.2.1', @@ -846,7 +846,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=nw_off_no_lb.id, zoneid=self.zone.id, gateway='10.1.3.1', @@ -860,7 +860,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network_1.id)] ) @@ -872,7 +872,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network_2.id)] ) @@ -883,7 +883,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network_3.id)] ) @@ -893,7 +893,7 @@ class TestVMDeployVPC(cloudstackTestCase): vms = VirtualMachine.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -965,7 +965,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=nw_off.id, zoneid=self.zone.id, gateway='10.1.4.1', @@ -978,7 +978,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network_4.id)] ) @@ -989,7 +989,7 @@ class TestVMDeployVPC(cloudstackTestCase): vms = VirtualMachine.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, state="Running", listall=True ) @@ -1057,7 +1057,7 @@ class TestVMDeployVPC(cloudstackTestCase): vpcofferingid=vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc) @@ -1076,7 +1076,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=nw_off.id, zoneid=self.zone.id, gateway='10.1.1.1', @@ -1100,7 +1100,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=nw_off_no_lb.id, zoneid=self.zone.id, gateway='10.1.2.1', @@ -1115,7 +1115,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=nw_off_no_lb.id, zoneid=self.zone.id, gateway='10.1.3.1', @@ -1129,7 +1129,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network_1.id)] ) @@ -1141,7 +1141,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network_2.id)] ) @@ -1152,7 +1152,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network_3.id)] ) @@ -1162,7 +1162,7 @@ class TestVMDeployVPC(cloudstackTestCase): vms = VirtualMachine.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -1236,7 +1236,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=nw_off_no_lb.id, zoneid=self.zone.id, gateway='10.1.4.1', @@ -1249,7 +1249,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network_4.id)] ) @@ -1283,7 +1283,7 @@ class TestVMDeployVPC(cloudstackTestCase): vms = VirtualMachine.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, state="Running", listall=True ) @@ -1337,7 +1337,7 @@ class TestVMDeployVPC(cloudstackTestCase): vpcofferingid=vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc) @@ -1360,7 +1360,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=nw_off.id, zoneid=self.zone.id, gateway='10.1.0.1', @@ -1399,7 +1399,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=nw_off_no_lb.id, zoneid=self.zone.id, gateway=gateway, @@ -1417,7 +1417,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=nw_off_no_lb.id, zoneid=self.zone.id, gateway=gateway, @@ -1436,7 +1436,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=nw_off.id, zoneid=self.zone.id, gateway=gateway, @@ -1453,7 +1453,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network.id)] ) @@ -1512,7 +1512,7 @@ class TestVMDeployVPC(cloudstackTestCase): vpcofferingid=vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc) @@ -1531,7 +1531,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=nw_off.id, zoneid=self.zone.id, gateway='10.1.1.1', @@ -1555,7 +1555,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=nw_off_no_lb.id, zoneid=self.zone.id, gateway='10.1.2.1', @@ -1569,7 +1569,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network_1.id)] ) @@ -1578,7 +1578,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network_1.id)] ) @@ -1590,7 +1590,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network_2.id)] ) @@ -1599,7 +1599,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network_2.id)] ) @@ -1609,7 +1609,7 @@ class TestVMDeployVPC(cloudstackTestCase): vms = VirtualMachine.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -1650,7 +1650,7 @@ class TestVMDeployVPC(cloudstackTestCase): vms = VirtualMachine.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network_1.id, listall=True ) @@ -1685,7 +1685,7 @@ class TestVMDeployVPC(cloudstackTestCase): vms = VirtualMachine.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network_2.id, listall=True ) @@ -1704,7 +1704,7 @@ class TestVMDeployVPC(cloudstackTestCase): routers = Router.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -1777,7 +1777,7 @@ class TestVMDeployVPC(cloudstackTestCase): vpcofferingid=vpc_off.id, zoneid=self.zone.id, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.validate_vpc_network(vpc) @@ -1796,7 +1796,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=nw_off.id, zoneid=self.zone.id, gateway='10.1.1.1', @@ -1820,7 +1820,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["network"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkofferingid=nw_off_no_lb.id, zoneid=self.zone.id, gateway='10.1.2.1', @@ -1834,7 +1834,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network_1.id)] ) @@ -1843,7 +1843,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network_1.id)] ) @@ -1855,7 +1855,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network_2.id)] ) @@ -1864,7 +1864,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], accountid=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, networkids=[str(network_2.id)] ) @@ -1874,7 +1874,7 @@ class TestVMDeployVPC(cloudstackTestCase): vms = VirtualMachine.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, listall=True ) self.assertEqual( @@ -1895,7 +1895,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network_1.id, vpcid=vpc.id ) @@ -1927,7 +1927,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network_1.id, vpcid=vpc.id ) @@ -1956,7 +1956,7 @@ class TestVMDeployVPC(cloudstackTestCase): listall=True, isstaticnat=True, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.assertEqual( isinstance(public_ips, list), @@ -1974,7 +1974,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network_2.id, vpcid=vpc.id ) @@ -1993,7 +1993,7 @@ class TestVMDeployVPC(cloudstackTestCase): accountid=self.account.name, networkid=network_2.id, vpcid=vpc.id, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.debug("Adding virtual machines %s and %s to LB rule" % ( @@ -2066,7 +2066,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network_2.id, vpcid=vpc.id ) @@ -2098,7 +2098,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network_2.id, vpcid=vpc.id ) @@ -2127,7 +2127,7 @@ class TestVMDeployVPC(cloudstackTestCase): listall=True, isstaticnat=True, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.assertEqual( isinstance(public_ips, list), @@ -2145,7 +2145,7 @@ class TestVMDeployVPC(cloudstackTestCase): self.apiclient, accountid=self.account.name, zoneid=self.zone.id, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network_2.id, vpcid=vpc.id ) @@ -2164,7 +2164,7 @@ class TestVMDeployVPC(cloudstackTestCase): accountid=self.account.name, networkid=network_2.id, vpcid=vpc.id, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.debug("Adding virtual machines %s and %s to LB rule" % ( @@ -2339,7 +2339,7 @@ class TestVMDeployVPC(cloudstackTestCase): vms = VirtualMachine.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid, + domainid=self.account.domainid, networkid=network_1.id, listall=True ) @@ -2447,7 +2447,7 @@ class TestVMDeployVPC(cloudstackTestCase): networks = Network.list( self.apiclient, account=self.account.name, - domainid=self.account.account.domainid + domainid=self.account.domainid ) self.assertEqual( networks, diff --git a/test/integration/component/test_vpn_users.py b/test/integration/component/test_vpn_users.py index 8f08fa09c38..fe020d0f555 100644 --- a/test/integration/component/test_vpn_users.py +++ b/test/integration/component/test_vpn_users.py @@ -396,7 +396,7 @@ class TestVPNUsers(cloudstackTestCase): DomainName=self.account.domain) self.debug("Adding new user to VPN as a global admin: %s" % - admin.account.name) + admin.name) try: self.create_VPN_Users(api_client=api_client) except Exception as e: @@ -438,7 +438,7 @@ class TestVPNUsers(cloudstackTestCase): DomainName=self.account.domain) self.debug("Adding new user to VPN as a domain admin: %s" % - admin.account.name) + admin.name) try: self.create_VPN_Users(api_client=api_client) except Exception as e: From cede6c11ab5e5ee7838dd84b0914a640267c7769 Mon Sep 17 00:00:00 2001 From: Dave Cahill Date: Mon, 20 May 2013 17:00:12 +0900 Subject: [PATCH 036/108] Adding package declaration to MidoNetElementTest --- .../test/com/cloud/network/element/MidoNetElementTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/network-elements/midonet/test/com/cloud/network/element/MidoNetElementTest.java b/plugins/network-elements/midonet/test/com/cloud/network/element/MidoNetElementTest.java index baf99b908d4..a7d96b0c310 100644 --- a/plugins/network-elements/midonet/test/com/cloud/network/element/MidoNetElementTest.java +++ b/plugins/network-elements/midonet/test/com/cloud/network/element/MidoNetElementTest.java @@ -17,7 +17,8 @@ * under the License. */ -import com.cloud.network.element.MidoNetElement; +package com.cloud.network.element; + import com.cloud.user.AccountVO; import com.cloud.user.dao.AccountDao; import junit.framework.TestCase; From 0365d555cf50ad551427ebe2c5634b807bd6a9c0 Mon Sep 17 00:00:00 2001 From: Chip Childers Date: Mon, 20 May 2013 19:17:33 +0100 Subject: [PATCH 037/108] Adding a name to the framework/jobs/pom.xml file Signed-off-by: Chip Childers --- framework/jobs/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/jobs/pom.xml b/framework/jobs/pom.xml index 5ae63af77ad..cf1fdd585a6 100644 --- a/framework/jobs/pom.xml +++ b/framework/jobs/pom.xml @@ -19,6 +19,7 @@ 4.0.0 cloud-framework-jobs + Apache CloudStack Framework - Jobs org.apache.cloudstack cloudstack-framework From cf6045f1aa37674f0225144ae0a1f662f955f153 Mon Sep 17 00:00:00 2001 From: Jessica Wang Date: Mon, 20 May 2013 13:40:37 -0700 Subject: [PATCH 038/108] CLOUDSTACK-747: internalLb in VPC - UI - create network offering - system offering dropdown is for router only. Change its variable name to be more intuitive. --- ui/scripts/configuration.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ui/scripts/configuration.js b/ui/scripts/configuration.js index 9a08c4c56b1..e3421a380ef 100644 --- a/ui/scripts/configuration.js +++ b/ui/scripts/configuration.js @@ -1122,7 +1122,7 @@ title: 'label.add.network.offering', preFilter: function(args) { var $availability = args.$form.find('.form-item[rel=availability]'); - var $serviceOfferingId = args.$form.find('.form-item[rel=serviceOfferingId]'); + var $systemOfferingForRouter = args.$form.find('.form-item[rel=systemOfferingForRouter]'); var $conservemode = args.$form.find('.form-item[rel=conservemode]'); var $serviceSourceNatRedundantRouterCapabilityCheckbox = args.$form.find('.form-item[rel="service.SourceNat.redundantRouterCapabilityCheckbox"]'); var hasAdvancedZones = false; @@ -1185,10 +1185,10 @@ } }); if(havingVirtualRouterForAtLeastOneService == true) { - $serviceOfferingId.css('display', 'inline-block'); + $systemOfferingForRouter.css('display', 'inline-block'); } else { - $serviceOfferingId.hide(); + $systemOfferingForRouter.hide(); } @@ -1569,8 +1569,8 @@ }, //show or hide upon checked services and selected providers above (begin) - serviceOfferingId: { - label: 'label.system.offering', + systemOfferingForRouter: { + label: 'System Offering for Router', docID: 'helpNetworkOfferingSystemOffering', select: function(args) { $.ajax({ @@ -1829,8 +1829,8 @@ if(args.$form.find('.form-item[rel=availability]').css("display") == "none") inputData['availability'] = 'Optional'; - if(args.$form.find('.form-item[rel=serviceOfferingId]').css("display") == "none") - delete inputData.serviceOfferingId; + if(args.$form.find('.form-item[rel=systemOfferingForRouter]').css("display") == "none") + delete inputData.systemOfferingForRouter; inputData['traffictype'] = 'GUEST'; //traffic type dropdown has been removed since it has only one option ('Guest'). Hardcode traffic type value here. From 7260e8d83f07d90b48c34adaeb227de265019487 Mon Sep 17 00:00:00 2001 From: Hiroaki Kawai Date: Fri, 3 May 2013 10:43:20 -0700 Subject: [PATCH 039/108] CLOUDSTACK-1638: Introduce NetworkMigrationResponder The location of the virtual machine is provided by DeployDestination, which will be passed in NetworkGuru#reserve and NetworkElement#prepare. During the virtual machine migration, it actually changes DeployDestination and it looks like that it will tell that event to network components as it has NetworkManager#prepareNicForMigration. The problem is that althogh the interface has that method, NetworkManagerImpl does not tell the DeployDestination changes to network components. So IMHO, we need to add calls of NetworkGuru#reserve and NetworkElement#prepare in NetworkManagerImpl#prepareNicForMigration . And then, we also need to add calls NetworkGuru#release and NetworkElement#release after the migration, otherwise the network resources that plugin reserved will be kept even when the vm leaves off. (Sheng Yang: rebase code, add license header) Signed-off-by: Sheng Yang --- .../network/NetworkMigrationResponder.java | 70 ++++++++++++++++++ .../src/com/cloud/network/NetworkManager.java | 29 +++++++- .../com/cloud/network/NetworkManagerImpl.java | 72 ++++++++++++++++++- .../cloud/vm/VirtualMachineManagerImpl.java | 11 +++ .../cloud/network/MockNetworkManagerImpl.java | 29 ++++++-- .../com/cloud/vpc/MockNetworkManagerImpl.java | 48 +++++++++---- 6 files changed, 239 insertions(+), 20 deletions(-) create mode 100644 api/src/com/cloud/network/NetworkMigrationResponder.java diff --git a/api/src/com/cloud/network/NetworkMigrationResponder.java b/api/src/com/cloud/network/NetworkMigrationResponder.java new file mode 100644 index 00000000000..6283cc54128 --- /dev/null +++ b/api/src/com/cloud/network/NetworkMigrationResponder.java @@ -0,0 +1,70 @@ +// 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.network; + +import com.cloud.deploy.DeployDestination; +import com.cloud.vm.NicProfile; +import com.cloud.vm.ReservationContext; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachineProfile; + +/** + * NetworkGuru and NetworkElements that implement this interface + * will be called during Virtual Machine migration. + */ +public interface NetworkMigrationResponder { + /** + * Prepare for migration. + * + * This method will be called per nic before the vm migration. + * @param nic + * @param network + * @param vm + * @param dest + * @param context + * @return true when operation was successful. + */ + public boolean prepareMigration(NicProfile nic, Network network, VirtualMachineProfile vm, DeployDestination dest, ReservationContext context); + + /** + * Cancel for migration preparation. + * + * This method will be called per nic when the entire vm migration + * process failed and need to release the resouces that was + * allocated at the migration preparation. + * @param nic destination nic + * @param network destination network + * @param vm destination vm profile + * @param src The context nic migrates from. + * @param dst The context nic migrates to. + */ + public void rollbackMigration(NicProfile nic, Network network, VirtualMachineProfile vm, ReservationContext src, ReservationContext dst); + + /** + * Commit the migration resource. + * + * This method will be called per nic when the entire vm migration + * process was successful. This is useful to release the resource of + * source deployment where vm has left. + * @param nic source nic + * @param network source network + * @param vm source vm profile + * @param src the context nic migrates from. + * @param dst the context nic migrates to. + */ + public void commitMigration(NicProfile nic, Network network, VirtualMachineProfile vm, ReservationContext src, ReservationContext dst); +} diff --git a/server/src/com/cloud/network/NetworkManager.java b/server/src/com/cloud/network/NetworkManager.java index 1e4fb8437ce..05bc26ee5c2 100755 --- a/server/src/com/cloud/network/NetworkManager.java +++ b/server/src/com/cloud/network/NetworkManager.java @@ -123,7 +123,34 @@ public interface NetworkManager { Pair implementNetwork(long networkId, DeployDestination dest, ReservationContext context) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException; - void prepareNicForMigration(VirtualMachineProfile vm, DeployDestination dest); + /** + * prepares vm nic change for migration + * + * This method will be called in migration transaction before the vm migration. + * @param vm + * @param dest + */ + void prepareNicForMigration(VirtualMachineProfile vm, DeployDestination dest); + + /** + * commit vm nic change for migration + * + * This method will be called in migration transaction after the successful + * vm migration. + * @param src + * @param dst + */ + void commitNicForMigration(VirtualMachineProfile src, VirtualMachineProfile dst); + + /** + * rollback vm nic change for migration + * + * This method will be called in migaration transaction after vm migration + * failure. + * @param src + * @param dst + */ + void rollbackNicForMigration(VirtualMachineProfile src, VirtualMachineProfile dst); boolean shutdownNetwork(long networkId, ReservationContext context, boolean cleanupElements); diff --git a/server/src/com/cloud/network/NetworkManagerImpl.java b/server/src/com/cloud/network/NetworkManagerImpl.java index b3df0da8ebe..0f43b87685e 100755 --- a/server/src/com/cloud/network/NetworkManagerImpl.java +++ b/server/src/com/cloud/network/NetworkManagerImpl.java @@ -2000,8 +2000,9 @@ public class NetworkManagerImpl extends ManagerBase implements NetworkManager, L } @Override - public void prepareNicForMigration(VirtualMachineProfile vm, DeployDestination dest) { + public void prepareNicForMigration(VirtualMachineProfile vm, DeployDestination dest) { List nics = _nicDao.listByVmId(vm.getId()); + ReservationContext context = new ReservationContextImpl(UUID.randomUUID().toString(), null, null); for (NicVO nic : nics) { NetworkVO network = _networksDao.findById(nic.getNetworkId()); Integer networkRate = _networkModel.getNetworkRate(network.getId(), vm.getId()); @@ -2009,11 +2010,80 @@ public class NetworkManagerImpl extends ManagerBase implements NetworkManager, L NetworkGuru guru = AdapterBase.getAdapterByName(_networkGurus, network.getGuruName()); NicProfile profile = new NicProfile(nic, network, nic.getBroadcastUri(), nic.getIsolationUri(), networkRate, _networkModel.isSecurityGroupSupportedInNetwork(network), _networkModel.getNetworkTag(vm.getHypervisorType(), network)); + if(guru instanceof NetworkMigrationResponder){ + if(!((NetworkMigrationResponder) guru).prepareMigration(profile, network, vm, dest, context)){ + s_logger.error("NetworkGuru "+guru+" prepareForMigration failed."); // XXX: Transaction error + } + } + for (NetworkElement element : _networkElements) { + if(element instanceof NetworkMigrationResponder){ + if(!((NetworkMigrationResponder) element).prepareMigration(profile, network, vm, dest, context)){ + s_logger.error("NetworkElement "+element+" prepareForMigration failed."); // XXX: Transaction error + } + } + } guru.updateNicProfile(profile, network); vm.addNic(profile); } } + private NicProfile findNicProfileById(VirtualMachineProfile vm, long id){ + for(NicProfile nic: vm.getNics()){ + if(nic.getId() == id){ + return nic; + } + } + return null; + } + + @Override + public void commitNicForMigration( + VirtualMachineProfile src, + VirtualMachineProfile dst) { + for(NicProfile nicSrc: src.getNics()){ + NetworkVO network = _networksDao.findById(nicSrc.getNetworkId()); + NetworkGuru guru = AdapterBase.getAdapterByName(_networkGurus, network.getGuruName()); + NicProfile nicDst = findNicProfileById(dst, nicSrc.getId()); + ReservationContext src_context = new ReservationContextImpl(nicSrc.getReservationId(), null, null); + ReservationContext dst_context = new ReservationContextImpl(nicDst.getReservationId(), null, null); + + if(guru instanceof NetworkMigrationResponder){ + ((NetworkMigrationResponder) guru).commitMigration(nicSrc, network, src, src_context, dst_context); + } + for (NetworkElement element : _networkElements) { + if(element instanceof NetworkMigrationResponder){ + ((NetworkMigrationResponder) element).commitMigration(nicSrc, network, src, src_context, dst_context); + } + } + // update the reservation id + NicVO nicVo = _nicDao.findById(nicDst.getId()); + nicVo.setReservationId(nicDst.getReservationId()); + _nicDao.persist(nicVo); + } + } + + @Override + public void rollbackNicForMigration( + VirtualMachineProfile src, + VirtualMachineProfile dst) { + for(NicProfile nicDst: dst.getNics()){ + NetworkVO network = _networksDao.findById(nicDst.getNetworkId()); + NetworkGuru guru = AdapterBase.getAdapterByName(_networkGurus, network.getGuruName()); + NicProfile nicSrc = findNicProfileById(src, nicDst.getId()); + ReservationContext src_context = new ReservationContextImpl(nicSrc.getReservationId(), null, null); + ReservationContext dst_context = new ReservationContextImpl(nicDst.getReservationId(), null, null); + + if(guru instanceof NetworkMigrationResponder){ + ((NetworkMigrationResponder) guru).rollbackMigration(nicDst, network, dst, src_context, dst_context); + } + for (NetworkElement element : _networkElements) { + if(element instanceof NetworkMigrationResponder){ + ((NetworkMigrationResponder) element).rollbackMigration(nicDst, network, dst, src_context, dst_context); + } + } + } + } + @Override @DB public void release(VirtualMachineProfile vmProfile, boolean forced) throws diff --git a/server/src/com/cloud/vm/VirtualMachineManagerImpl.java b/server/src/com/cloud/vm/VirtualMachineManagerImpl.java index 58d95ab81a1..f4875377f93 100755 --- a/server/src/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/server/src/com/cloud/vm/VirtualMachineManagerImpl.java @@ -1414,6 +1414,11 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac alertType = AlertManager.ALERT_TYPE_CONSOLE_PROXY_MIGRATE; } + VirtualMachineProfile vmSrc = new VirtualMachineProfileImpl(vm); + for(NicProfile nic: _networkMgr.getNicProfiles(vm)){ + vmSrc.addNic(nic); + } + VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm); _networkMgr.prepareNicForMigration(profile, dest); this.volumeMgr.prepareForMigration(profile, dest); @@ -1439,6 +1444,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac throw new AgentUnavailableException("Operation timed out", dstHostId); } finally { if (pfma == null) { + _networkMgr.rollbackNicForMigration(vmSrc, profile); work.setStep(Step.Done); _workDao.update(work.getId(), work); } @@ -1447,10 +1453,12 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac vm.setLastHostId(srcHostId); try { if (vm == null || vm.getHostId() == null || vm.getHostId() != srcHostId || !changeState(vm, Event.MigrationRequested, dstHostId, work, Step.Migrating)) { + _networkMgr.rollbackNicForMigration(vmSrc, profile); s_logger.info("Migration cancelled because state has changed: " + vm); throw new ConcurrentOperationException("Migration cancelled because state has changed: " + vm); } } catch (NoTransitionException e1) { + _networkMgr.rollbackNicForMigration(vmSrc, profile); s_logger.info("Migration cancelled because " + e1.getMessage()); throw new ConcurrentOperationException("Migration cancelled because " + e1.getMessage()); } @@ -1502,6 +1510,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac } finally { if (!migrated) { s_logger.info("Migration was unsuccessful. Cleaning up: " + vm); + _networkMgr.rollbackNicForMigration(vmSrc, profile); _alertMgr.sendAlert(alertType, fromHost.getDataCenterId(), fromHost.getPodId(), "Unable to migrate vm " + vm.getInstanceName() + " from host " + fromHost.getName() + " in zone " + dest.getDataCenter().getName() + " and pod " + dest.getPod().getName(), "Migrate Command failed. Please check logs."); @@ -1516,6 +1525,8 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac } catch (NoTransitionException e) { s_logger.warn(e.getMessage()); } + }else{ + _networkMgr.commitNicForMigration(vmSrc, profile); } work.setStep(Step.Done); diff --git a/server/test/com/cloud/network/MockNetworkManagerImpl.java b/server/test/com/cloud/network/MockNetworkManagerImpl.java index 1ac014d502a..e5d34fbacc7 100755 --- a/server/test/com/cloud/network/MockNetworkManagerImpl.java +++ b/server/test/com/cloud/network/MockNetworkManagerImpl.java @@ -277,12 +277,6 @@ public class MockNetworkManagerImpl extends ManagerBase implements NetworkManage } - @Override - public void prepareNicForMigration(VirtualMachineProfile vm, DeployDestination dest) { - // TODO Auto-generated method stub - - } - @Override public boolean destroyNetwork(long networkId, ReservationContext context) { // TODO Auto-generated method stub @@ -966,4 +960,27 @@ public class MockNetworkManagerImpl extends ManagerBase implements NetworkManage public PublicIp assignPublicIpAddressFromVlans(long dcId, Long podId, Account owner, VlanType type, List vlanDbIds, Long networkId, String requestedIp, boolean isSystem) throws InsufficientAddressCapacityException { return null; //To change body of implemented methods use File | Settings | File Templates. } + + @Override + public void prepareNicForMigration( + VirtualMachineProfile vm, + DeployDestination dest) { + // TODO Auto-generated method stub + + } + + @Override + public void commitNicForMigration( + VirtualMachineProfile src, + VirtualMachineProfile dst) { + // TODO Auto-generated method stub + + } + + @Override + public void rollbackNicForMigration( + VirtualMachineProfile src, + VirtualMachineProfile dst) { + // TODO Auto-generated method stub + } } diff --git a/server/test/com/cloud/vpc/MockNetworkManagerImpl.java b/server/test/com/cloud/vpc/MockNetworkManagerImpl.java index 62599b8d504..7129273a50c 100644 --- a/server/test/com/cloud/vpc/MockNetworkManagerImpl.java +++ b/server/test/com/cloud/vpc/MockNetworkManagerImpl.java @@ -845,18 +845,6 @@ public class MockNetworkManagerImpl extends ManagerBase implements NetworkManage - /* (non-Javadoc) - * @see com.cloud.network.NetworkManager#prepareNicForMigration(com.cloud.vm.VirtualMachineProfile, com.cloud.deploy.DeployDestination) - */ - @Override - public void prepareNicForMigration(VirtualMachineProfile vm, DeployDestination dest) { - // TODO Auto-generated method stub - - } - - - - /* (non-Javadoc) * @see com.cloud.network.NetworkManager#shutdownNetwork(long, com.cloud.vm.ReservationContext, boolean) @@ -1471,4 +1459,40 @@ public class MockNetworkManagerImpl extends ManagerBase implements NetworkManage } + + + + @Override + public void prepareNicForMigration( + VirtualMachineProfile vm, + DeployDestination dest) { + // TODO Auto-generated method stub + + } + + + + + + @Override + public void commitNicForMigration( + VirtualMachineProfile src, + VirtualMachineProfile dst) { + // TODO Auto-generated method stub + + } + + + + + + @Override + public void rollbackNicForMigration( + VirtualMachineProfile src, + VirtualMachineProfile dst) { + // TODO Auto-generated method stub + + } + + } From eb7c3214260371b0551040831ad7404f5880fc8d Mon Sep 17 00:00:00 2001 From: Sheng Yang Date: Fri, 3 May 2013 11:02:44 -0700 Subject: [PATCH 040/108] PVLAN: CLOUDSTACK-2401: VM Migration support --- .../network/element/VirtualRouterElement.java | 70 ++++++++++++++++++- .../VirtualNetworkApplianceManager.java | 2 +- .../VirtualNetworkApplianceManagerImpl.java | 32 ++++++--- server/src/com/cloud/vm/UserVmManager.java | 1 + .../src/com/cloud/vm/UserVmManagerImpl.java | 13 ++-- .../com/cloud/vm/MockUserVmManagerImpl.java | 6 ++ ...MockVpcVirtualNetworkApplianceManager.java | 6 ++ 7 files changed, 110 insertions(+), 20 deletions(-) diff --git a/server/src/com/cloud/network/element/VirtualRouterElement.java b/server/src/com/cloud/network/element/VirtualRouterElement.java index 8021e6f0074..19166787e9b 100755 --- a/server/src/com/cloud/network/element/VirtualRouterElement.java +++ b/server/src/com/cloud/network/element/VirtualRouterElement.java @@ -47,7 +47,9 @@ import com.cloud.network.Network; import com.cloud.network.Network.Capability; import com.cloud.network.Network.Provider; import com.cloud.network.Network.Service; +import com.cloud.network.NetworkMigrationResponder; import com.cloud.network.NetworkModel; +import com.cloud.network.Networks; import com.cloud.network.Networks.BroadcastDomainType; import com.cloud.network.Networks.TrafficType; import com.cloud.network.PhysicalNetworkServiceProvider; @@ -85,9 +87,13 @@ import com.cloud.utils.db.SearchCriteriaService; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.DomainRouterVO; import com.cloud.vm.NicProfile; +import com.cloud.vm.NicVO; import com.cloud.vm.ReservationContext; +import com.cloud.vm.UserVmManager; +import com.cloud.vm.UserVmVO; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachine.State; +import com.cloud.vm.VirtualMachine.Type; import com.cloud.vm.VirtualMachineProfile; import com.cloud.vm.dao.DomainRouterDao; import com.cloud.vm.dao.UserVmDao; @@ -108,10 +114,12 @@ import java.util.Set; @Local(value = {NetworkElement.class, FirewallServiceProvider.class, DhcpServiceProvider.class, UserDataServiceProvider.class, StaticNatServiceProvider.class, LoadBalancingServiceProvider.class, - PortForwardingServiceProvider.class, IpDeployer.class, RemoteAccessVPNServiceProvider.class} ) + PortForwardingServiceProvider.class, IpDeployer.class, + RemoteAccessVPNServiceProvider.class, NetworkMigrationResponder.class} ) public class VirtualRouterElement extends AdapterBase implements VirtualRouterElementService, DhcpServiceProvider, UserDataServiceProvider, SourceNatServiceProvider, StaticNatServiceProvider, FirewallServiceProvider, - LoadBalancingServiceProvider, PortForwardingServiceProvider, RemoteAccessVPNServiceProvider, IpDeployer { + LoadBalancingServiceProvider, PortForwardingServiceProvider, RemoteAccessVPNServiceProvider, IpDeployer, + NetworkMigrationResponder { private static final Logger s_logger = Logger.getLogger(VirtualRouterElement.class); protected static final Map> capabilities = setCapabilities(); @@ -130,6 +138,8 @@ public class VirtualRouterElement extends AdapterBase implements VirtualRouterEl ConfigurationManager _configMgr; @Inject RulesManager _rulesMgr; + @Inject + UserVmManager _userVmMgr; @Inject UserVmDao _userVmDao; @@ -1024,7 +1034,6 @@ public class VirtualRouterElement extends AdapterBase implements VirtualRouterEl // TODO Auto-generated method stub return null; } - private boolean canHandleLbRules(List rules) { Map lbCaps = this.getCapabilities().get(Service.Lb); if (!lbCaps.isEmpty()) { @@ -1039,5 +1048,60 @@ public class VirtualRouterElement extends AdapterBase implements VirtualRouterEl } } return true; + } + + @Override + public boolean prepareMigration(NicProfile nic, Network network, + VirtualMachineProfile vm, + DeployDestination dest, ReservationContext context) { + if (nic.getBroadcastType() != Networks.BroadcastDomainType.Pvlan) { + return true; + } + if (vm.getType() == Type.DomainRouter) { + assert vm instanceof DomainRouterVO; + DomainRouterVO router = (DomainRouterVO)vm.getVirtualMachine(); + _routerMgr.setupDhcpForPvlan(false, router, router.getHostId(), nic); + } else if (vm.getType() == Type.User){ + assert vm instanceof UserVmVO; + UserVmVO userVm = (UserVmVO)vm.getVirtualMachine(); + _userVmMgr.setupVmForPvlan(false, userVm.getHostId(), nic); + } + return true; + } + + @Override + public void rollbackMigration(NicProfile nic, Network network, + VirtualMachineProfile vm, + ReservationContext src, ReservationContext dst) { + if (nic.getBroadcastType() != Networks.BroadcastDomainType.Pvlan) { + return; + } + if (vm.getType() == Type.DomainRouter) { + assert vm instanceof DomainRouterVO; + DomainRouterVO router = (DomainRouterVO)vm.getVirtualMachine(); + _routerMgr.setupDhcpForPvlan(true, router, router.getHostId(), nic); + } else if (vm.getType() == Type.User){ + assert vm instanceof UserVmVO; + UserVmVO userVm = (UserVmVO)vm.getVirtualMachine(); + _userVmMgr.setupVmForPvlan(true, userVm.getHostId(), nic); + } + } + + @Override + public void commitMigration(NicProfile nic, Network network, + VirtualMachineProfile vm, + ReservationContext src, ReservationContext dst) { + if (nic.getBroadcastType() != Networks.BroadcastDomainType.Pvlan) { + return; + } + if (vm.getType() == Type.DomainRouter) { + assert vm instanceof DomainRouterVO; + DomainRouterVO router = (DomainRouterVO)vm.getVirtualMachine(); + _routerMgr.setupDhcpForPvlan(true, router, router.getHostId(), nic); + } else if (vm.getType() == Type.User){ + assert vm instanceof UserVmVO; + UserVmVO userVm = (UserVmVO)vm.getVirtualMachine(); + _userVmMgr.setupVmForPvlan(true, userVm.getHostId(), nic); + } } } diff --git a/server/src/com/cloud/network/router/VirtualNetworkApplianceManager.java b/server/src/com/cloud/network/router/VirtualNetworkApplianceManager.java index 9852c47dc85..72fddf4f87d 100644 --- a/server/src/com/cloud/network/router/VirtualNetworkApplianceManager.java +++ b/server/src/com/cloud/network/router/VirtualNetworkApplianceManager.java @@ -33,7 +33,6 @@ import com.cloud.user.User; import com.cloud.uservm.UserVm; import com.cloud.utils.component.Manager; import com.cloud.vm.DomainRouterVO; -import com.cloud.vm.Nic; import com.cloud.vm.NicProfile; import com.cloud.vm.VirtualMachineProfile; @@ -113,4 +112,5 @@ public interface VirtualNetworkApplianceManager extends Manager, VirtualNetworkA boolean removeDhcpSupportForSubnet(Network network, List routers) throws ResourceUnavailableException; + boolean setupDhcpForPvlan(boolean add, DomainRouterVO router, Long hostId, NicProfile nic); } diff --git a/server/src/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java b/server/src/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java index 50ae4c16fb6..64e412ad1df 100755 --- a/server/src/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java +++ b/server/src/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java @@ -2223,8 +2223,9 @@ public class VirtualNetworkApplianceManagerImpl extends ManagerBase implements V return dhcpRange; } - private boolean setupDhcpForPvlan(boolean add, DomainRouterVO router, Nic nic) { - if (!nic.getBroadcastUri().getScheme().equals("pvlan")) { + @Override + public boolean setupDhcpForPvlan(boolean add, DomainRouterVO router, Long hostId, NicProfile nic) { + if (!nic.getBroadCastUri().getScheme().equals("pvlan")) { return false; } String op = "add"; @@ -2233,15 +2234,22 @@ public class VirtualNetworkApplianceManagerImpl extends ManagerBase implements V } Network network = _networkDao.findById(nic.getNetworkId()); String networkTag = _networkModel.getNetworkTag(router.getHypervisorType(), network); - PvlanSetupCommand cmd = PvlanSetupCommand.createDhcpSetup(op, nic.getBroadcastUri(), networkTag, router.getInstanceName(), nic.getMacAddress(), nic.getIp4Address()); - Commands cmds = new Commands(cmd); + PvlanSetupCommand cmd = PvlanSetupCommand.createDhcpSetup(op, nic.getBroadCastUri(), networkTag, router.getInstanceName(), nic.getMacAddress(), nic.getIp4Address()); // In fact we send command to the host of router, we're not programming router but the host - try { - sendCommandsToRouter(router, cmds); - } catch (AgentUnavailableException e) { + Answer answer = null; + try { + answer = _agentMgr.send(hostId, cmd); + } catch (OperationTimedoutException e) { + s_logger.warn("Timed Out", e); + return false; + } catch (AgentUnavailableException e) { s_logger.warn("Agent Unavailable ", e); - return false; - } + return false; + } + + if (answer == null || !answer.getResult()) { + return false; + } return true; } @@ -2563,7 +2571,8 @@ public class VirtualNetworkApplianceManagerImpl extends ManagerBase implements V if (network.getTrafficType() == TrafficType.Guest) { guestNetworks.add(network); if (nic.getBroadcastUri().getScheme().equals("pvlan")) { - result = setupDhcpForPvlan(true, router, nic); + NicProfile nicProfile = new NicProfile(nic, network, nic.getBroadcastUri(), nic.getIsolationUri(), 0, false, "pvlan-nic"); + result = setupDhcpForPvlan(true, router, router.getHostId(), nicProfile); } } } @@ -2601,7 +2610,8 @@ public class VirtualNetworkApplianceManagerImpl extends ManagerBase implements V for (Nic nic : routerNics) { Network network = _networkModel.getNetwork(nic.getNetworkId()); if (network.getTrafficType() == TrafficType.Guest && nic.getBroadcastUri().getScheme().equals("pvlan")) { - setupDhcpForPvlan(false, domR, nic); + NicProfile nicProfile = new NicProfile(nic, network, nic.getBroadcastUri(), nic.getIsolationUri(), 0, false, "pvlan-nic"); + setupDhcpForPvlan(false, domR, domR.getHostId(), nicProfile); } } diff --git a/server/src/com/cloud/vm/UserVmManager.java b/server/src/com/cloud/vm/UserVmManager.java index 0f8e36804bb..4dcfb73e2a1 100755 --- a/server/src/com/cloud/vm/UserVmManager.java +++ b/server/src/com/cloud/vm/UserVmManager.java @@ -94,4 +94,5 @@ public interface UserVmManager extends VirtualMachineGuru, UserVmServi boolean upgradeVirtualMachine(Long id, Long serviceOfferingId) throws ResourceUnavailableException, ConcurrentOperationException, ManagementServerException, VirtualMachineMigrationException; + boolean setupVmForPvlan(boolean add, Long hostId, NicProfile nic); } diff --git a/server/src/com/cloud/vm/UserVmManagerImpl.java b/server/src/com/cloud/vm/UserVmManagerImpl.java index 86150a2ab9c..71b4e3fafb7 100755 --- a/server/src/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/com/cloud/vm/UserVmManagerImpl.java @@ -2821,8 +2821,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use return true; } - private boolean setupVmForPvlan(boolean add, Long hostId, NicVO nic) { - if (!nic.getBroadcastUri().getScheme().equals("pvlan")) { + @Override + public boolean setupVmForPvlan(boolean add, Long hostId, NicProfile nic) { + if (!nic.getBroadCastUri().getScheme().equals("pvlan")) { return false; } String op = "add"; @@ -2833,7 +2834,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use Network network = _networkDao.findById(nic.getNetworkId()); Host host = _hostDao.findById(hostId); String networkTag = _networkModel.getNetworkTag(host.getHypervisorType(), network); - PvlanSetupCommand cmd = PvlanSetupCommand.createVmSetup(op, nic.getBroadcastUri(), networkTag, nic.getMacAddress()); + PvlanSetupCommand cmd = PvlanSetupCommand.createVmSetup(op, nic.getBroadCastUri(), networkTag, nic.getMacAddress()); Answer answer = null; try { answer = _agentMgr.send(hostId, cmd); @@ -2916,7 +2917,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use // In vmware, we will be effecting pvlan settings in portgroups in StartCommand. if (profile.getHypervisorType() != HypervisorType.VMware) { if (nic.getBroadcastUri().getScheme().equals("pvlan")) { - if (!setupVmForPvlan(true, hostId, nic)) { + NicProfile nicProfile = new NicProfile(nic, network, nic.getBroadcastUri(), nic.getIsolationUri(), 0, false, "pvlan-nic"); + if (!setupVmForPvlan(true, hostId, nicProfile)) { return false; } } @@ -3058,7 +3060,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use NetworkVO network = _networkDao.findById(nic.getNetworkId()); if (network.getTrafficType() == TrafficType.Guest) { if (nic.getBroadcastUri().getScheme().equals("pvlan")) { - setupVmForPvlan(false, vm.getHostId(), nic); + NicProfile nicProfile = new NicProfile(nic, network, nic.getBroadcastUri(), nic.getIsolationUri(), 0, false, "pvlan-nic"); + setupVmForPvlan(false, vm.getHostId(), nicProfile); } } } diff --git a/server/test/com/cloud/vm/MockUserVmManagerImpl.java b/server/test/com/cloud/vm/MockUserVmManagerImpl.java index 50a90f200c9..448a5dd9a21 100644 --- a/server/test/com/cloud/vm/MockUserVmManagerImpl.java +++ b/server/test/com/cloud/vm/MockUserVmManagerImpl.java @@ -455,4 +455,10 @@ public class MockUserVmManagerImpl extends ManagerBase implements UserVmManager, // TODO Auto-generated method stub return null; } + + @Override + public boolean setupVmForPvlan(boolean add, Long hostId, NicProfile nic) { + // TODO Auto-generated method stub + return false; + } } diff --git a/server/test/com/cloud/vpc/MockVpcVirtualNetworkApplianceManager.java b/server/test/com/cloud/vpc/MockVpcVirtualNetworkApplianceManager.java index 8d502112e8d..f325c4af077 100644 --- a/server/test/com/cloud/vpc/MockVpcVirtualNetworkApplianceManager.java +++ b/server/test/com/cloud/vpc/MockVpcVirtualNetworkApplianceManager.java @@ -420,4 +420,10 @@ VpcVirtualNetworkApplianceService { return null; } + @Override + public boolean setupDhcpForPvlan(boolean add, DomainRouterVO router, Long hostId, + NicProfile nic) { + // TODO Auto-generated method stub + return false; + } } From 503392119d515a81742e9c36d3f285afe2a960fb Mon Sep 17 00:00:00 2001 From: Likitha Shetty Date: Tue, 21 May 2013 11:19:02 +0530 Subject: [PATCH 041/108] CLOUDSTACK-2599. Fix EC2 Rest to support tag related EC2 API's - CreateTags/DeleteTags/DescribeTags --- .../cloud/bridge/service/EC2RestServlet.java | 91 +++++++++++ .../bridge/service/EC2SoapServiceImpl.java | 143 ++++++++++-------- 2 files changed, 172 insertions(+), 62 deletions(-) diff --git a/awsapi/src/com/cloud/bridge/service/EC2RestServlet.java b/awsapi/src/com/cloud/bridge/service/EC2RestServlet.java index 6dd7a8cbb75..6b634e86d9d 100644 --- a/awsapi/src/com/cloud/bridge/service/EC2RestServlet.java +++ b/awsapi/src/com/cloud/bridge/service/EC2RestServlet.java @@ -35,8 +35,11 @@ import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.Enumeration; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Properties; +import java.util.Set; import java.util.UUID; import javax.inject.Inject; @@ -68,10 +71,12 @@ import com.amazon.ec2.CreateImageResponse; import com.amazon.ec2.CreateKeyPairResponse; import com.amazon.ec2.CreateSecurityGroupResponse; import com.amazon.ec2.CreateSnapshotResponse; +import com.amazon.ec2.CreateTagsResponse; import com.amazon.ec2.CreateVolumeResponse; import com.amazon.ec2.DeleteKeyPairResponse; import com.amazon.ec2.DeleteSecurityGroupResponse; import com.amazon.ec2.DeleteSnapshotResponse; +import com.amazon.ec2.DeleteTagsResponse; import com.amazon.ec2.DeleteVolumeResponse; import com.amazon.ec2.DeregisterImageResponse; import com.amazon.ec2.DescribeAvailabilityZonesResponse; @@ -82,6 +87,7 @@ import com.amazon.ec2.DescribeInstancesResponse; import com.amazon.ec2.DescribeKeyPairsResponse; import com.amazon.ec2.DescribeSecurityGroupsResponse; import com.amazon.ec2.DescribeSnapshotsResponse; +import com.amazon.ec2.DescribeTagsResponse; import com.amazon.ec2.DescribeVolumesResponse; import com.amazon.ec2.DetachVolumeResponse; import com.amazon.ec2.DisassociateAddressResponse; @@ -122,6 +128,7 @@ import com.cloud.bridge.service.core.ec2.EC2DescribeInstances; import com.cloud.bridge.service.core.ec2.EC2DescribeKeyPairs; import com.cloud.bridge.service.core.ec2.EC2DescribeSecurityGroups; import com.cloud.bridge.service.core.ec2.EC2DescribeSnapshots; +import com.cloud.bridge.service.core.ec2.EC2DescribeTags; import com.cloud.bridge.service.core.ec2.EC2DescribeVolumes; import com.cloud.bridge.service.core.ec2.EC2DisassociateAddress; import com.cloud.bridge.service.core.ec2.EC2Engine; @@ -143,6 +150,10 @@ import com.cloud.bridge.service.core.ec2.EC2SecurityGroup; import com.cloud.bridge.service.core.ec2.EC2SnapshotFilterSet; import com.cloud.bridge.service.core.ec2.EC2StartInstances; import com.cloud.bridge.service.core.ec2.EC2StopInstances; +import com.cloud.bridge.service.core.ec2.EC2TagKeyValue; +import com.cloud.bridge.service.core.ec2.EC2TagTypeId; +import com.cloud.bridge.service.core.ec2.EC2Tags; +import com.cloud.bridge.service.core.ec2.EC2TagsFilterSet; import com.cloud.bridge.service.core.ec2.EC2Volume; import com.cloud.bridge.service.core.ec2.EC2VolumeFilterSet; import com.cloud.bridge.service.exception.EC2ServiceException; @@ -294,6 +305,9 @@ public class EC2RestServlet extends HttpServlet { else if (action.equalsIgnoreCase( "ImportKeyPair" )) importKeyPair(request, response); else if (action.equalsIgnoreCase( "DeleteKeyPair" )) deleteKeyPair(request, response); else if (action.equalsIgnoreCase( "DescribeKeyPairs" )) describeKeyPairs(request, response); + else if (action.equalsIgnoreCase( "CreateTags" )) createTags(request, response); + else if (action.equalsIgnoreCase( "DeleteTags" )) deleteTags(request, response); + else if (action.equalsIgnoreCase( "DescribeTags" )) describeTags(request, response); else if (action.equalsIgnoreCase( "GetPasswordData" )) getPasswordData(request, response); else { logger.error("Unsupported action " + action); @@ -1824,6 +1838,83 @@ public class EC2RestServlet extends HttpServlet { serializeResponse(response, EC2Response); } + private void createTags(HttpServletRequest request, HttpServletResponse response) + throws ADBException, XMLStreamException, IOException { + EC2Tags ec2Request = createTagsRequest(request, response); + if (ec2Request == null) return; + CreateTagsResponse EC2Response = EC2SoapServiceImpl.toCreateTagsResponse( + ServiceProvider.getInstance().getEC2Engine().modifyTags( ec2Request, "create")); + serializeResponse(response, EC2Response); + } + + private void deleteTags(HttpServletRequest request, HttpServletResponse response) + throws ADBException, XMLStreamException, IOException { + EC2Tags ec2Request = createTagsRequest(request, response); + if (ec2Request == null) return; + DeleteTagsResponse EC2Response = EC2SoapServiceImpl.toDeleteTagsResponse( + ServiceProvider.getInstance().getEC2Engine().modifyTags( ec2Request, "delete")); + serializeResponse(response, EC2Response); + } + + private EC2Tags createTagsRequest(HttpServletRequest request, HttpServletResponse response) + throws IOException { + EC2Tags ec2Request = new EC2Tags(); + ArrayList resourceIdList = new ArrayList(); + Map resourceTagList = new HashMap(); + + int nCount = 1; + do { + String[] resourceIds = request.getParameterValues( "ResourceId." + nCount ); + if (resourceIds != null && resourceIds.length > 0) + resourceIdList.add(resourceIds[0]); + else break; + nCount++; + } while (true); + if ( resourceIdList.isEmpty() ) { + response.sendError(530, "At least one Resource is required" ); + return null; + } + ec2Request = EC2SoapServiceImpl.toResourceTypeAndIds(ec2Request, resourceIdList); + + nCount = 1; + do { + String[] tagKey = request.getParameterValues( "Tag." + nCount + ".Key" ); + if ( tagKey != null && tagKey.length > 0 ) { + String[] tagValue = request.getParameterValues( "Tag." + nCount + ".Value" ); + if ( tagValue != null && tagValue.length > 0 ) { + resourceTagList.put(tagKey[0], tagValue[0]); + } else + resourceTagList.put(tagKey[0], null); + } else break; + nCount++; + } while (true); + if ( resourceTagList.isEmpty() ) { + response.sendError(530, "At least one Tag is required" ); + return null; + } + ec2Request = EC2SoapServiceImpl.toResourceTag(ec2Request, resourceTagList); + + return ec2Request; + } + + private void describeTags(HttpServletRequest request, HttpServletResponse response) + throws ADBException, XMLStreamException, IOException { + EC2DescribeTags ec2Request = new EC2DescribeTags(); + + EC2Filter[] filterSet = extractFilters( request ); + if (null != filterSet) { + EC2TagsFilterSet tfs = new EC2TagsFilterSet(); + for( int i=0; i < filterSet.length; i++ ) + tfs.addFilter( filterSet[i] ); + ec2Request.setFilterSet( tfs ); + } + DescribeTagsResponse EC2Response = EC2SoapServiceImpl.toDescribeTagsResponse( + ServiceProvider.getInstance().getEC2Engine().describeTags( ec2Request)); + serializeResponse(response, EC2Response); + } + + + /** * This function implements the EC2 REST authentication algorithm. It uses the given * "AWSAccessKeyId" parameter to look up the Cloud.com account holder's secret key which is diff --git a/awsapi/src/com/cloud/bridge/service/EC2SoapServiceImpl.java b/awsapi/src/com/cloud/bridge/service/EC2SoapServiceImpl.java index cebac0b159e..9362b810e32 100644 --- a/awsapi/src/com/cloud/bridge/service/EC2SoapServiceImpl.java +++ b/awsapi/src/com/cloud/bridge/service/EC2SoapServiceImpl.java @@ -18,7 +18,10 @@ package com.cloud.bridge.service; import java.util.ArrayList; import java.util.Calendar; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.UUID; import org.apache.commons.codec.binary.Base64; @@ -210,87 +213,103 @@ public class EC2SoapServiceImpl implements AmazonEC2SkeletonInterface { public CreateTagsResponse createTags(CreateTags createTags) { EC2Tags request = new EC2Tags(); + ArrayList resourceIdList = new ArrayList(); + Map resourceTagList = new HashMap(); + CreateTagsType ctt = createTags.getCreateTags(); ResourceIdSetType resourceIds = ctt.getResourcesSet(); ResourceTagSetType resourceTags = ctt.getTagSet(); - request = toResourceTypeAndIds(resourceIds); - //add resource tag's to the request - if (resourceTags != null) { - ResourceTagSetItemType[] items = resourceTags.getItem(); - if (items != null) { - for( int i=0; i < items.length; i++ ) { - EC2TagKeyValue param1 = new EC2TagKeyValue(); - param1.setKey(items[i].getKey()); - param1.setValue(items[i].getValue()); - request.addResourceTag(param1); - } - } + + ResourceIdSetItemType[] resourceIdItems = resourceIds.getItem(); + if (resourceIdItems != null) { + for( int i=0; i < resourceIdItems.length; i++ ) + resourceIdList.add(resourceIdItems[i].getResourceId()); } + request = toResourceTypeAndIds(request, resourceIdList); + + //add resource tag's to the request + ResourceTagSetItemType[] resourceTagItems = resourceTags.getItem(); + if (resourceTagItems != null) { + for( int i=0; i < resourceTagItems.length; i++ ) + resourceTagList.put(resourceTagItems[i].getKey(), resourceTagItems[i].getValue()); + } + request = toResourceTag(request, resourceTagList); + return toCreateTagsResponse( engine.modifyTags( request, "create")); } public DeleteTagsResponse deleteTags(DeleteTags deleteTags) { EC2Tags request = new EC2Tags(); + ArrayList resourceIdList = new ArrayList(); + Map resourceTagList = new HashMap(); + DeleteTagsType dtt = deleteTags.getDeleteTags(); ResourceIdSetType resourceIds = dtt.getResourcesSet(); DeleteTagsSetType resourceTags = dtt.getTagSet(); - request = toResourceTypeAndIds(resourceIds); - //add resource tag's to the request - if (resourceTags != null) { - DeleteTagsSetItemType[] items = resourceTags.getItem(); - if (items != null) { - for( int i=0; i < items.length; i++ ) { - EC2TagKeyValue param1 = new EC2TagKeyValue(); - param1.setKey(items[i].getKey()); - if (items[i].getValue() != null) - param1.setValue(items[i].getValue()); - request.addResourceTag(param1); - } - } + + ResourceIdSetItemType[] resourceIdItems = resourceIds.getItem(); + + if (resourceIdItems != null) { + for( int i=0; i < resourceIdItems.length; i++ ) + resourceIdList.add(resourceIdItems[i].getResourceId()); } + request = toResourceTypeAndIds(request, resourceIdList); + + //add resource tag's to the request + DeleteTagsSetItemType[] resourceTagItems = resourceTags.getItem(); + if (resourceTagItems != null) { + for( int i=0; i < resourceTagItems.length; i++ ) + resourceTagList.put(resourceTagItems[i].getKey(), resourceTagItems[i].getValue()); + } + request = toResourceTag(request, resourceTagList); + return toDeleteTagsResponse( engine.modifyTags( request, "delete")); } - private EC2Tags toResourceTypeAndIds(ResourceIdSetType resourceIds) { - EC2Tags request = new EC2Tags(); - //add resource-type and resource-id's to the request - if (resourceIds != null) { - ResourceIdSetItemType[] items = resourceIds.getItem(); - List resourceTypeList = new ArrayList(); - if (items != null) { - for( int i=0; i < items.length; i++ ) { - if (!items[i].getResourceId().contains(":") || items[i].getResourceId().split(":").length != 2) { - throw new EC2ServiceException( ClientError.InvalidResourceId_Format, - "Invalid Format. ResourceId format is resource-type:resource-uuid"); - } - String resourceType = items[i].getResourceId().split(":")[0]; - if (resourceTypeList.isEmpty()) - resourceTypeList.add(resourceType); - else { - Boolean existsInList = false; - for (String addedResourceType : resourceTypeList) { - if (addedResourceType.equalsIgnoreCase(resourceType)) { - existsInList = true; - break; - } - } - if (!existsInList) - resourceTypeList.add(resourceType); - } - } - for (String resourceType : resourceTypeList){ - EC2TagTypeId param1 = new EC2TagTypeId(); - param1.setResourceType(resourceType); - for( int i=0; i < items.length; i++ ) { - String[] resourceTag = items[i].getResourceId().split(":"); - if (resourceType.equals(resourceTag[0])) - param1.addResourceId(resourceTag[1]); - } - request.addResourceType(param1); - } + public static EC2Tags toResourceTypeAndIds( EC2Tags request, ArrayList resourceIdList ) { + List resourceTypeList = new ArrayList(); + for (String resourceId : resourceIdList) { + if (!resourceId.contains(":") || resourceId.split(":").length != 2) { + throw new EC2ServiceException( ClientError.InvalidResourceId_Format, + "Invalid Format. ResourceId format is resource-type:resource-uuid"); } + String resourceType = resourceId.split(":")[0]; + if (resourceTypeList.isEmpty()) + resourceTypeList.add(resourceType); + else { + Boolean existsInList = false; + for (String addedResourceType : resourceTypeList) { + if (addedResourceType.equalsIgnoreCase(resourceType)) { + existsInList = true; + break; + } + } + if (!existsInList) + resourceTypeList.add(resourceType); + } + } + for (String resourceType : resourceTypeList) { + EC2TagTypeId param1 = new EC2TagTypeId(); + param1.setResourceType(resourceType); + for (String resourceId : resourceIdList) { + String[] resourceTag = resourceId.split(":"); + if (resourceType.equals(resourceTag[0])) + param1.addResourceId(resourceTag[1]); + } + request.addResourceType(param1); + } + return request; + } + + public static EC2Tags toResourceTag( EC2Tags request, Map resourceTagList ) { + Set resourceTagKeySet = resourceTagList.keySet(); + for (String resourceTagKey : resourceTagKeySet) { + EC2TagKeyValue param1 = new EC2TagKeyValue(); + param1.setKey(resourceTagKey); + param1.setValue(resourceTagList.get(resourceTagKey)); + request.addResourceTag(param1); } return request; } From 45d2e60656a7c9b2b4171bc63450afa229ca4e75 Mon Sep 17 00:00:00 2001 From: Dave Brosius Date: Tue, 21 May 2013 11:42:53 +0530 Subject: [PATCH 042/108] Nop assignment due to missing 'this.' Signed-off-by: Prasanna Santhanam --- .../src/com/cloud/network/dao/ExternalLoadBalancerDeviceVO.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/schema/src/com/cloud/network/dao/ExternalLoadBalancerDeviceVO.java b/engine/schema/src/com/cloud/network/dao/ExternalLoadBalancerDeviceVO.java index 04714a6a4de..c1c983489a3 100644 --- a/engine/schema/src/com/cloud/network/dao/ExternalLoadBalancerDeviceVO.java +++ b/engine/schema/src/com/cloud/network/dao/ExternalLoadBalancerDeviceVO.java @@ -198,7 +198,7 @@ public class ExternalLoadBalancerDeviceVO implements InternalIdentity, Identity } public void setGslbProvider(boolean gslbProvider) { - gslbProvider = gslbProvider; + this.gslbProvider = gslbProvider; } public void setGslbSitePublicIP(String gslbSitePublicIP) { From 49faa002e220311f10a5f99993fad66a22836dad Mon Sep 17 00:00:00 2001 From: Dave Brosius Date: Tue, 21 May 2013 11:45:10 +0530 Subject: [PATCH 043/108] code compares Long values with == which will work for Long cached values Fixed by switch to use .equals Signed-off-by: Prasanna Santhanam --- .../engine/subsystem/api/storage/AbstractScope.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/AbstractScope.java b/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/AbstractScope.java index c94db66b202..083b1fe6c39 100644 --- a/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/AbstractScope.java +++ b/engine/api/src/org/apache/cloudstack/engine/subsystem/api/storage/AbstractScope.java @@ -21,10 +21,6 @@ package org.apache.cloudstack.engine.subsystem.api.storage; public abstract class AbstractScope implements Scope { @Override public boolean isSameScope(Scope scope) { - if (this.getScopeType() == scope.getScopeType() && this.getScopeId() == scope.getScopeId()) { - return true; - } else { - return false; - } + return this.getScopeType() == scope.getScopeType() && this.getScopeId().equals(scope.getScopeId()); } } From 55c384651acf3778a4cce57543f79c6e5838fef4 Mon Sep 17 00:00:00 2001 From: Sanjay Tripathi Date: Tue, 21 May 2013 11:49:42 +0530 Subject: [PATCH 044/108] CLOUDSTACK-2305: [Automation] NPE: not able to create volume from snapshot Signed off by : Nitin Mehta --- .../src/com/cloud/storage/VolumeManagerImpl.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/server/src/com/cloud/storage/VolumeManagerImpl.java b/server/src/com/cloud/storage/VolumeManagerImpl.java index 55e20cf0273..4b654eb2253 100644 --- a/server/src/com/cloud/storage/VolumeManagerImpl.java +++ b/server/src/com/cloud/storage/VolumeManagerImpl.java @@ -878,14 +878,6 @@ public class VolumeManagerImpl extends ManagerBase implements VolumeManager { size = diskOffering.getDiskSize(); } - if(displayVolumeEnabled == null){ - displayVolumeEnabled = true; - } else{ - if(!_accountMgr.isRootAdmin(caller.getType())){ - throw new PermissionDeniedException( "Cannot update parameter displayvolume, only admin permitted "); - } - } - if (!validateVolumeSizeRange(size)) {// convert size from mb to gb // for validation throw new InvalidParameterValueException( @@ -917,6 +909,14 @@ public class VolumeManagerImpl extends ManagerBase implements VolumeManager { _accountMgr.checkAccess(caller, null, true, snapshotCheck); } + if(displayVolumeEnabled == null){ + displayVolumeEnabled = true; + } else{ + if(!_accountMgr.isRootAdmin(caller.getType())){ + throw new PermissionDeniedException( "Cannot update parameter displayvolume, only admin permitted "); + } + } + // Check that the resource limit for primary storage won't be exceeded _resourceLimitMgr.checkResourceLimit(_accountMgr.getAccount(ownerId), ResourceType.primary_storage, new Long(size)); From 218d26be60a556dccd0a40499a04901f2ac4adc1 Mon Sep 17 00:00:00 2001 From: Prasanna Santhanam Date: Tue, 21 May 2013 12:03:27 +0530 Subject: [PATCH 045/108] Adding the 'advanced' attribute for test_public_ip_range Fixing typo in class name. Signed-off-by: Prasanna Santhanam --- test/integration/smoke/test_public_ip_range.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/integration/smoke/test_public_ip_range.py b/test/integration/smoke/test_public_ip_range.py index f2099ff432a..e1d78d99d15 100644 --- a/test/integration/smoke/test_public_ip_range.py +++ b/test/integration/smoke/test_public_ip_range.py @@ -52,11 +52,11 @@ class Services: "vlan": "4444", } -class TesDedicatePublicIPRange(cloudstackTestCase): +class TestDedicatePublicIPRange(cloudstackTestCase): @classmethod def setUpClass(cls): - cls.api_client = super(TesDedicatePublicIPRange, cls).getClsTestClient().getApiClient() + cls.api_client = super(TestDedicatePublicIPRange, cls).getClsTestClient().getApiClient() cls.services = Services().services # Get Zone, Domain cls.domain = get_domain(cls.api_client, cls.services) @@ -96,7 +96,7 @@ class TesDedicatePublicIPRange(cloudstackTestCase): raise Exception("Warning: Exception during cleanup : %s" % e) return - @attr(tags = ["simulator", "publiciprange", "dedicate", "release"]) + @attr(tags = ["simulator", "advanced", "publiciprange", "dedicate", "release"]) def test_dedicatePublicIpRange(self): """Test public IP range dedication """ From 6217b0fb4cd79d7c39ce9fdff2c18e4e7504d62f Mon Sep 17 00:00:00 2001 From: Likitha Shetty Date: Tue, 21 May 2013 11:58:01 +0530 Subject: [PATCH 046/108] CLOUDSTACK-2600. Fix CS AWSAPI to add the below filters that are missing in DescribeImages API- architecture description image-id image-type is-public name owner-id state tag-key tag-value tag:key hypervisor --- .../cloud/bridge/service/EC2RestServlet.java | 10 ++ .../bridge/service/EC2SoapServiceImpl.java | 41 ++++- .../service/core/ec2/EC2DescribeImages.java | 10 ++ .../bridge/service/core/ec2/EC2Engine.java | 34 +++- .../bridge/service/core/ec2/EC2Image.java | 63 +++++-- .../service/core/ec2/EC2ImageFilterSet.java | 168 ++++++++++++++++++ 6 files changed, 290 insertions(+), 36 deletions(-) create mode 100644 awsapi/src/com/cloud/bridge/service/core/ec2/EC2ImageFilterSet.java diff --git a/awsapi/src/com/cloud/bridge/service/EC2RestServlet.java b/awsapi/src/com/cloud/bridge/service/EC2RestServlet.java index 6b634e86d9d..83645a38de8 100644 --- a/awsapi/src/com/cloud/bridge/service/EC2RestServlet.java +++ b/awsapi/src/com/cloud/bridge/service/EC2RestServlet.java @@ -135,6 +135,7 @@ import com.cloud.bridge.service.core.ec2.EC2Engine; import com.cloud.bridge.service.core.ec2.EC2Filter; import com.cloud.bridge.service.core.ec2.EC2GroupFilterSet; import com.cloud.bridge.service.core.ec2.EC2Image; +import com.cloud.bridge.service.core.ec2.EC2ImageFilterSet; import com.cloud.bridge.service.core.ec2.EC2ImageAttributes.ImageAttribute; import com.cloud.bridge.service.core.ec2.EC2ImageLaunchPermission; import com.cloud.bridge.service.core.ec2.EC2ImportKeyPair; @@ -1380,6 +1381,15 @@ public class EC2RestServlet extends HttpServlet { if (null != value && 0 < value.length) EC2request.addImageSet( value[0] ); } } + // add filters + EC2Filter[] filterSet = extractFilters( request ); + if ( filterSet != null ) { + EC2ImageFilterSet ifs = new EC2ImageFilterSet(); + for( int i=0; i < filterSet.length; i++ ) { + ifs.addFilter(filterSet[i]); + } + EC2request.setFilterSet( ifs ); + } // -> execute the request EC2Engine engine = ServiceProvider.getInstance().getEC2Engine(); DescribeImagesResponse EC2response = EC2SoapServiceImpl.toDescribeImagesResponse( engine.describeImages( EC2request )); diff --git a/awsapi/src/com/cloud/bridge/service/EC2SoapServiceImpl.java b/awsapi/src/com/cloud/bridge/service/EC2SoapServiceImpl.java index 9362b810e32..dc0d993e0b8 100644 --- a/awsapi/src/com/cloud/bridge/service/EC2SoapServiceImpl.java +++ b/awsapi/src/com/cloud/bridge/service/EC2SoapServiceImpl.java @@ -51,6 +51,7 @@ import com.cloud.bridge.service.core.ec2.EC2DescribeInstances; import com.cloud.bridge.service.core.ec2.EC2DescribeInstancesResponse; import com.cloud.bridge.service.core.ec2.EC2DescribeKeyPairs; import com.cloud.bridge.service.core.ec2.EC2DescribeKeyPairsResponse; +import com.cloud.bridge.service.core.ec2.EC2ImageFilterSet; import com.cloud.bridge.service.core.ec2.EC2ImageLaunchPermission; import com.cloud.bridge.service.core.ec2.EC2ResourceTag; import com.cloud.bridge.service.core.ec2.EC2DescribeSecurityGroups; @@ -407,7 +408,10 @@ public class EC2SoapServiceImpl implements AmazonEC2SkeletonInterface { for( int i=0; i < items3.length; i++ ) request.addOwnersSet( items3[i].getOwner()); } } - + FilterSetType fst = dit.getFilterSet(); + if ( fst != null) { + request.setFilterSet(toImageFilterSet(fst)); + } return toDescribeImagesResponse( engine.describeImages( request )); } @@ -948,7 +952,7 @@ public class EC2SoapServiceImpl implements AmazonEC2SkeletonInterface { DescribeImagesResponseItemType param3 = new DescribeImagesResponseItemType(); param3.setImageId( images[i].getId()); param3.setImageLocation( "" ); - param3.setImageState( (images[i].getIsReady() ? "available" : "unavailable" )); + param3.setImageState( images[i].getState()); param3.setImageOwnerId(ownerId); param3.setIsPublic( images[i].getIsPublic()); @@ -960,16 +964,14 @@ public class EC2SoapServiceImpl implements AmazonEC2SkeletonInterface { String description = images[i].getDescription(); param3.setDescription( (null == description ? "" : description)); - - if (null == description) param3.setArchitecture( "" ); - else if (-1 != description.indexOf( "x86_64" )) param3.setArchitecture( "x86_64" ); - else if (-1 != description.indexOf( "i386" )) param3.setArchitecture( "i386" ); - else param3.setArchitecture( "" ); - - param3.setImageType( "machine" ); + + param3.setArchitecture( images[i].getArchitecture()); + + param3.setImageType( images[i].getImageType()); param3.setKernelId( "" ); param3.setRamdiskId( "" ); param3.setPlatform( "" ); + param3.setHypervisor( images[i].getHypervisor()); StateReasonType param6 = new StateReasonType(); param6.setCode( "" ); @@ -1287,6 +1289,27 @@ public class EC2SoapServiceImpl implements AmazonEC2SkeletonInterface { return tfs; } + private EC2ImageFilterSet toImageFilterSet( FilterSetType fst ) { + EC2ImageFilterSet ifs = new EC2ImageFilterSet(); + + FilterType[] items = fst.getItem(); + if (items != null) { + for (FilterType item : items) { + EC2Filter oneFilter = new EC2Filter(); + String filterName = item.getName(); + oneFilter.setName( filterName ); + + ValueSetType vft = item.getValueSet(); + ValueType[] valueItems = vft.getItem(); + for (ValueType valueItem : valueItems) { + oneFilter.addValueEncoded( valueItem.getValue()); + } + ifs.addFilter( oneFilter ); + } + } + return ifs; + } + // toMethods public static DescribeVolumesResponse toDescribeVolumesResponse( EC2DescribeVolumesResponse engineResponse ) { diff --git a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2DescribeImages.java b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2DescribeImages.java index 9c51b68df7f..96f2a85ffe2 100644 --- a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2DescribeImages.java +++ b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2DescribeImages.java @@ -24,6 +24,7 @@ public class EC2DescribeImages { private List executableBySet = new ArrayList();; // a list of strings identifying users private List imageSet = new ArrayList(); // a list of AMI id's private List ownersSet = new ArrayList(); // a list of AMI owner id's + private EC2ImageFilterSet ifs = null; public EC2DescribeImages() { } @@ -51,4 +52,13 @@ public class EC2DescribeImages { public String[] getOwnersSet() { return ownersSet.toArray(new String[0]); } + + public EC2ImageFilterSet getFilterSet() { + return ifs; + } + + public void setFilterSet( EC2ImageFilterSet param ) { + ifs = param; + } + } diff --git a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2Engine.java b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2Engine.java index e92f845f2b1..137111a3070 100644 --- a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2Engine.java +++ b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2Engine.java @@ -902,15 +902,19 @@ public class EC2Engine extends ManagerBase { try { String[] templateIds = request.getImageSet(); + EC2ImageFilterSet ifs = request.getFilterSet(); - if ( 0 == templateIds.length ) { - return listTemplates(null, images); + if ( templateIds.length == 0 ) { + images = listTemplates(null, images); + } else { + for (String s : templateIds) { + images = listTemplates(s, images); + } } - for (String s : templateIds) { - images = listTemplates(s, images); - } - return images; - + if (ifs == null) + return images; + else + return ifs.evaluate(images); } catch( Exception e ) { logger.error( "EC2 DescribeImages - ", e); throw new EC2ServiceException(ServerError.InternalError, e.getMessage() != null ? e.getMessage() : "An unexpected error occurred."); @@ -1951,8 +1955,22 @@ public class EC2Engine extends ManagerBase { ec2Image.setDescription(temp.getDisplayText()); ec2Image.setOsTypeId(temp.getOsTypeId().toString()); ec2Image.setIsPublic(temp.getIsPublic()); - ec2Image.setIsReady(temp.getIsReady()); + ec2Image.setState( temp.getIsReady() ? "available" : "pending"); ec2Image.setDomainId(temp.getDomainId()); + if ( temp.getHyperVisor().equalsIgnoreCase("xenserver")) + ec2Image.setHypervisor("xen"); + else if ( temp.getHyperVisor().equalsIgnoreCase("ovm")) + ec2Image.setHypervisor( "ovm"); // valid values for hypervisor is 'ovm' and 'xen' + else + ec2Image.setHypervisor(""); + if (temp.getDisplayText() == null) + ec2Image.setArchitecture(""); + else if (temp.getDisplayText().indexOf( "x86_64" ) != -1) + ec2Image.setArchitecture("x86_64"); + else if (temp.getDisplayText().indexOf( "i386" ) != -1) + ec2Image.setArchitecture("i386"); + else + ec2Image.setArchitecture(""); List resourceTags = temp.getTags(); for(CloudStackKeyValue resourceTag : resourceTags) { EC2TagKeyValue param = new EC2TagKeyValue(); diff --git a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2Image.java b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2Image.java index 1c30b674f60..8ca9ce7384c 100644 --- a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2Image.java +++ b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2Image.java @@ -28,10 +28,13 @@ public class EC2Image { private String name; private String description; private String osTypeId; - private boolean isPublic; - private boolean isReady; + private Boolean isPublic; + private String state; private String accountName; - private String domainId; + private String domainId; + private String hypervisor; + private String architecture; + private String imageType; private List tagsSet; public EC2Image() { @@ -40,9 +43,12 @@ public class EC2Image { description = null; osTypeId = null; isPublic = false; - isReady = false; + state = null; accountName = null; - domainId = null; + domainId = null; + hypervisor = null; + architecture = null; + imageType = "machine"; tagsSet = new ArrayList(); } @@ -78,21 +84,21 @@ public class EC2Image { return this.osTypeId; } - public void setIsPublic( boolean isPublic ) { - this.isPublic = isPublic; - } - - public boolean getIsPublic() { - return this.isPublic; - } + public void setIsPublic( Boolean isPublic ) { + this.isPublic = isPublic; + } - public void setIsReady( boolean isReady ) { - this.isReady = isReady; - } - - public boolean getIsReady() { - return this.isReady; - } + public Boolean getIsPublic() { + return this.isPublic; + } + + public void setState( String state ) { + this.state = state; + } + + public String getState() { + return this.state; + } public String getAccountName() { return accountName; @@ -110,6 +116,25 @@ public class EC2Image { this.domainId = domainId; } + public String getHypervisor() { + return hypervisor; + } + + public void setHypervisor(String hypervisor) { + this.hypervisor = hypervisor; + } + + public String getArchitecture() { + return architecture; + } + + public void setArchitecture(String architecture) { + this.architecture = architecture; + } + + public String getImageType() { + return imageType; + } public void addResourceTag( EC2TagKeyValue param ) { tagsSet.add( param ); diff --git a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2ImageFilterSet.java b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2ImageFilterSet.java new file mode 100644 index 00000000000..aea3df05dea --- /dev/null +++ b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2ImageFilterSet.java @@ -0,0 +1,168 @@ +// 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.bridge.service.core.ec2; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.log4j.Logger; + +import com.cloud.bridge.service.exception.EC2ServiceException; + +public class EC2ImageFilterSet { + protected final static Logger logger = Logger.getLogger(EC2ImageFilterSet.class); + + protected List filterSet = new ArrayList(); + private Map filterTypes = new HashMap(); + + public EC2ImageFilterSet() { + // -> supported filters + filterTypes.put( "architecture", "string" ); + filterTypes.put( "description", "string" ); + filterTypes.put( "hypervisor", "string" ); + filterTypes.put( "image-id", "string" ); + filterTypes.put( "image-type", "string" ); + filterTypes.put( "is-public", "Boolean" ); + filterTypes.put( "name", "string" ); + filterTypes.put( "owner-id", "string" ); + filterTypes.put( "state", "string" ); + filterTypes.put( "tag-key", "string" ); + filterTypes.put( "tag-value", "string" ); + } + + public void addFilter( EC2Filter param ) { + String filterName = param.getName(); + if ( !filterName.startsWith("tag:") ) { + String value = (String) filterTypes.get( filterName ); + if (null == value) + throw new EC2ServiceException( "Unsupported filter [" + filterName + "] - 1", 501 ); + if (null != value && value.equalsIgnoreCase( "null" )) + throw new EC2ServiceException( "Unsupported filter [" + filterName + "] - 2", 501 ); + } + + filterSet.add( param ); + } + + public EC2Filter[] getFilterSet() { + return filterSet.toArray(new EC2Filter[0]); + } + + public EC2DescribeImagesResponse evaluate( EC2DescribeImagesResponse sampleList) throws ParseException { + EC2DescribeImagesResponse resultList = new EC2DescribeImagesResponse(); + + boolean matched; + + EC2Image[] imageSet = sampleList.getImageSet(); + EC2Filter[] filterSet = getFilterSet(); + for (EC2Image image : imageSet) { + matched = true; + for (EC2Filter filter : filterSet) { + if (!filterMatched(image, filter)) { + matched = false; + break; + } + } + if (matched == true) + resultList.addImage(image); + } + return resultList; + } + +private boolean filterMatched( EC2Image image, EC2Filter filter ) throws ParseException { + String filterName = filter.getName(); + String[] valueSet = filter.getValueSet(); + + if ( filterName.equalsIgnoreCase( "architecture" )) + return containsString( image.getArchitecture(), valueSet ); + if ( filterName.equalsIgnoreCase( "description" )) + return containsString( image.getDescription(), valueSet ); + if ( filterName.equalsIgnoreCase( "hypervisor" )) + return containsString( image.getHypervisor(), valueSet ); + if ( filterName.equalsIgnoreCase( "image-id" )) + return containsString( image.getId(), valueSet ); + if ( filterName.equalsIgnoreCase( "image-type" )) + return containsString( image.getImageType(), valueSet ); + if ( filterName.equalsIgnoreCase( "is-public" )) + return image.getIsPublic().toString().equalsIgnoreCase(valueSet[0]); + if ( filterName.equalsIgnoreCase( "name" )) + return containsString( image.getName(), valueSet ); + if ( filterName.equalsIgnoreCase( "owner-id" )) { + String owner = new String( image.getDomainId() + ":" + image.getAccountName()); + return containsString( owner, valueSet ); + } + if ( filterName.equalsIgnoreCase( "state" )) + return containsString( image.getState(), valueSet ); + else if (filterName.equalsIgnoreCase("tag-key")) + { + EC2TagKeyValue[] tagSet = image.getResourceTags(); + for (EC2TagKeyValue tag : tagSet) + if (containsString(tag.getKey(), valueSet)) return true; + return false; + } + else if (filterName.equalsIgnoreCase("tag-value")) + { + EC2TagKeyValue[] tagSet = image.getResourceTags(); + for (EC2TagKeyValue tag : tagSet){ + if (tag.getValue() == null) { + if (containsEmptyValue(valueSet)) return true; + } + else { + if (containsString(tag.getValue(), valueSet)) return true; + } + } + return false; + } + else if (filterName.startsWith("tag:")) + { + String key = filterName.split(":")[1]; + EC2TagKeyValue[] tagSet = image.getResourceTags(); + for (EC2TagKeyValue tag : tagSet){ + if (tag.getKey().equalsIgnoreCase(key)) { + if (tag.getValue() == null) { + if (containsEmptyValue(valueSet)) return true; + } + else { + if (containsString(tag.getValue(), valueSet)) return true; + } + } + } + return false; + } + else return false; + } + + private boolean containsString( String lookingFor, String[] set ) { + if (lookingFor == null) + return false; + + for (String filter: set) { + if (lookingFor.matches( filter )) return true; + } + return false; + } + + private boolean containsEmptyValue( String[] set ) { + for( int i=0; i < set.length; i++ ) { + if (set[i].isEmpty()) return true; + } + return false; + } + +} From 9350441dd3dfc27a5852c5662463a1d5af1cd026 Mon Sep 17 00:00:00 2001 From: Dave Brosius Date: Tue, 21 May 2013 12:09:21 +0530 Subject: [PATCH 047/108] remove bogus self assign to parent Signed off by : Nitin Mehta --- .../cloud/agent/api/storage/CreateEntityDownloadURLCommand.java | 1 - 1 file changed, 1 deletion(-) diff --git a/core/src/com/cloud/agent/api/storage/CreateEntityDownloadURLCommand.java b/core/src/com/cloud/agent/api/storage/CreateEntityDownloadURLCommand.java index d928e0c5b2b..98a957f9a4e 100755 --- a/core/src/com/cloud/agent/api/storage/CreateEntityDownloadURLCommand.java +++ b/core/src/com/cloud/agent/api/storage/CreateEntityDownloadURLCommand.java @@ -28,7 +28,6 @@ public class CreateEntityDownloadURLCommand extends AbstractDownloadCommand { public CreateEntityDownloadURLCommand(String installPath, String uuid) { super(); - this.parent = parent; this.installPath = installPath; this.extractLinkUUID = uuid; } From 904a2a87f799392c70053f7949b9a6b1e6a2e4dc Mon Sep 17 00:00:00 2001 From: Devdeep Singh Date: Tue, 21 May 2013 13:32:09 +0530 Subject: [PATCH 048/108] CLOUDSTACK-2601 : xen.heartbeat.interval doesn't change the parameter passed to xenheartbeat.sh. Made changes to read the parameter from config and to pass it to the resource. Signed-off-by: Devdeep Singh --- .../com/cloud/hypervisor/xen/discoverer/XcpServerDiscoverer.java | 1 + server/src/com/cloud/resource/DiscovererBase.java | 1 + 2 files changed, 2 insertions(+) diff --git a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/discoverer/XcpServerDiscoverer.java b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/discoverer/XcpServerDiscoverer.java index f0121e7220b..fd498365e3d 100755 --- a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/discoverer/XcpServerDiscoverer.java +++ b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/discoverer/XcpServerDiscoverer.java @@ -315,6 +315,7 @@ public class XcpServerDiscoverer extends DiscovererBase implements Discoverer, L details.put("wait", Integer.toString(_wait)); params.put("migratewait", _configDao.getValue(Config.MigrateWait.toString())); params.put(Config.XenMaxNics.toString().toLowerCase(), _configDao.getValue(Config.XenMaxNics.toString())); + params.put(Config.XenHeartBeatInterval.toString().toLowerCase(), _configDao.getValue(Config.XenHeartBeatInterval.toString())); params.put(Config.InstanceName.toString().toLowerCase(), _instance); details.put(Config.InstanceName.toString().toLowerCase(), _instance); try { diff --git a/server/src/com/cloud/resource/DiscovererBase.java b/server/src/com/cloud/resource/DiscovererBase.java index b7c5b6f58de..0c9dd2551e5 100644 --- a/server/src/com/cloud/resource/DiscovererBase.java +++ b/server/src/com/cloud/resource/DiscovererBase.java @@ -129,6 +129,7 @@ public abstract class DiscovererBase extends AdapterBase implements Discoverer { params.put("max.template.iso.size", _configDao.getValue(Config.MaxTemplateAndIsoSize.toString())); params.put("migratewait", _configDao.getValue(Config.MigrateWait.toString())); params.put(Config.XenMaxNics.toString().toLowerCase(), _configDao.getValue(Config.XenMaxNics.toString())); + params.put(Config.XenHeartBeatInterval.toString().toLowerCase(), _configDao.getValue(Config.XenHeartBeatInterval.toString())); return params; } From eb92135d55e454799a647490c501f13505325bcb Mon Sep 17 00:00:00 2001 From: Likitha Shetty Date: Tue, 21 May 2013 15:13:18 +0530 Subject: [PATCH 049/108] CLOUDSTACK-2603. EC2RunInstances return xen or ovm as the response value for attribute "hypervisor" --- .../bridge/service/core/ec2/EC2Engine.java | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2Engine.java b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2Engine.java index 137111a3070..9ac2bc68a88 100644 --- a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2Engine.java +++ b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2Engine.java @@ -1458,7 +1458,7 @@ public class EC2Engine extends ManagerBase { vm.setIpAddress(resp.getIpAddress()); vm.setAccountName(resp.getAccountName()); vm.setDomainId(resp.getDomainId()); - vm.setHypervisor(resp.getHypervisor()); + vm.setHypervisor( mapToAmazonHypervisorType(resp.getHypervisor()) ); vm.setServiceOffering( svcOffering.getName()); vm.setKeyPairName(resp.getKeyPairName()); instances.addInstance(vm); @@ -1860,7 +1860,7 @@ public class EC2Engine extends ManagerBase { ec2Vm.setIpAddress(cloudVm.getIpAddress()); ec2Vm.setAccountName(cloudVm.getAccountName()); ec2Vm.setDomainId(cloudVm.getDomainId()); - ec2Vm.setHypervisor(cloudVm.getHypervisor()); + ec2Vm.setHypervisor( mapToAmazonHypervisorType(cloudVm.getHypervisor()) ); ec2Vm.setRootDeviceType(cloudVm.getRootDeviceType()); ec2Vm.setRootDeviceId(cloudVm.getRootDeviceId()); ec2Vm.setServiceOffering(serviceOfferingIdToInstanceType(cloudVm.getServiceOfferingId().toString())); @@ -2498,6 +2498,21 @@ public class EC2Engine extends ManagerBase { return (resourceType.toLowerCase()); } + /** + * Map CloudStack hypervisor to CloudStack hypervisor + * + * @param CloudStack hypervisor + * @return Amazon hypervisor + */ + private String mapToAmazonHypervisorType( String hypervisor) { + if (hypervisor.equalsIgnoreCase("Xenserver")) + return("xen"); + else if(hypervisor.equalsIgnoreCase("Ovm")) + return("ovm"); + else + return (""); + } + /** * Stop an instance * Wait until one specific VM has stopped From 5fb1a1610933b989df25f717d6bd2df10ff34386 Mon Sep 17 00:00:00 2001 From: Prasanna Santhanam Date: Tue, 21 May 2013 14:39:33 +0530 Subject: [PATCH 050/108] Fix apidocs build Portable IP section added post portable IP merge Signed-off-by: Prasanna Santhanam --- tools/apidoc/gen_toc.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/apidoc/gen_toc.py b/tools/apidoc/gen_toc.py index 375850304b7..793f7209449 100644 --- a/tools/apidoc/gen_toc.py +++ b/tools/apidoc/gen_toc.py @@ -143,6 +143,7 @@ known_categories = { 'AffinityGroup': 'Affinity Group', 'InternalLoadBalancer': 'Internal LB', 'DeploymentPlanners': 'Configuration', + 'PortableIp': 'Portable IP' } From 2adc8e9a03fd657423add436400f761bd155564f Mon Sep 17 00:00:00 2001 From: Prasanna Santhanam Date: Tue, 21 May 2013 15:26:23 +0530 Subject: [PATCH 051/108] Adding docstrings for the portablip test 1. Test to create a portable public ip range 2. Test to acquire a provisioned public ip range Signed-off-by: Prasanna Santhanam --- test/integration/smoke/test_portable_publicip.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/integration/smoke/test_portable_publicip.py b/test/integration/smoke/test_portable_publicip.py index 101747d958e..5b2fbc7e307 100644 --- a/test/integration/smoke/test_portable_publicip.py +++ b/test/integration/smoke/test_portable_publicip.py @@ -138,7 +138,7 @@ class TestPortablePublicIPRange(cloudstackTestCase): @attr(tags = ["simulator", "basic", "advanced", "portablepublicip"]) def test_createPortablePublicIPRange(self): - """ + """ Test to create a portable public ip range """ self.debug("attempting to create a portable Public IP range") self.portable_ip_range = PortablePublicIpRange.create( @@ -155,7 +155,6 @@ class TestPortablePublicIPRange(cloudstackTestCase): class TestPortablePublicIPAcquire(cloudstackTestCase): - """ This test validates functionality where - admin has provisioned a portable public ip range @@ -223,7 +222,7 @@ class TestPortablePublicIPAcquire(cloudstackTestCase): @attr(tags = ["simulator", "basic", "advanced", "portablepublicip"]) def test_createPortablePublicIPAcquire(self): - """ + """ Test to acquire a provisioned public ip range """ self.debug("attempting to create a portable Public IP range") self.portable_ip_range = PortablePublicIpRange.create( From a58ee74e1c3760852214be28d8ef7051f27e1f29 Mon Sep 17 00:00:00 2001 From: Nitin Mehta Date: Tue, 21 May 2013 16:36:10 +0530 Subject: [PATCH 052/108] CLOUDSTACK-2567 - Check whether DMC - dynamic memory control is enabled for the hyervisor before trying to scale the vm. If its not then dont scale and instead throw and exception. --- .../cloud/hypervisor/xen/resource/CitrixResourceBase.java | 7 +++++++ .../hypervisor/xen/resource/CitrixResourceBaseTest.java | 5 ++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java index cd4977312ae..d08aaecb171 100644 --- a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java +++ b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java @@ -660,6 +660,13 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe Connection conn = getConnection(); Set vms = VM.getByNameLabel(conn, vmName); Host host = Host.getByUuid(conn, _host.uuid); + + // If DMC is not enable then dont execute this command. + if (isDmcEnabled(conn, host)) { + String msg = "Unable to scale the vm: " + vmName + " as DMC - Dynamic memory control is not enabled for the XenServer:" + _host.uuid + " ,check your license and hypervisor version."; + s_logger.info(msg); + return new ScaleVmAnswer(cmd, false, msg); + } // stop vm which is running on this host or is in halted state Iterator iter = vms.iterator(); while ( iter.hasNext() ) { diff --git a/plugins/hypervisors/xen/test/com/cloud/hypervisor/xen/resource/CitrixResourceBaseTest.java b/plugins/hypervisors/xen/test/com/cloud/hypervisor/xen/resource/CitrixResourceBaseTest.java index 877e3bc5120..3328d4be50c 100644 --- a/plugins/hypervisors/xen/test/com/cloud/hypervisor/xen/resource/CitrixResourceBaseTest.java +++ b/plugins/hypervisors/xen/test/com/cloud/hypervisor/xen/resource/CitrixResourceBaseTest.java @@ -58,7 +58,10 @@ public class CitrixResourceBaseTest { super.scaleVM(conn, vm, vmSpec, host); } - + @Override + protected boolean isDmcEnabled(Connection conn, Host host) throws Types.XenAPIException, XmlRpcException { + return true; + } }; @Mock XsHost _host; @Mock Host host; From 730e6571f6c054b75777694be05ec00c9f16a0f8 Mon Sep 17 00:00:00 2001 From: Wido den Hollander Date: Tue, 21 May 2013 11:23:50 +0200 Subject: [PATCH 053/108] debian: Packaging fixes for AWSAPI --- debian/rules | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/rules b/debian/rules index ff12154db31..575a015effd 100755 --- a/debian/rules +++ b/debian/rules @@ -153,6 +153,7 @@ install: mkdir $(DESTDIR)/usr/share/$(PACKAGE)-bridge mkdir -p $(DESTDIR)/usr/share/$(PACKAGE)-bridge/webapps/awsapi mkdir $(DESTDIR)/usr/share/$(PACKAGE)-bridge/setup + ln -s /usr/share/$(PACKAGE)-bridge/webapps/awsapi $(DESTDIR)/usr/share/$(PACKAGE)-management/webapps7080/awsapi cp -r awsapi/target/cloud-awsapi-$(VERSION)-SNAPSHOT/* $(DESTDIR)/usr/share/$(PACKAGE)-bridge/webapps/awsapi install -D awsapi-setup/setup/cloud-setup-bridge $(DESTDIR)/usr/bin/cloudstack-setup-bridge install -D awsapi-setup/setup/cloudstack-aws-api-register $(DESTDIR)/usr/bin/cloudstack-aws-api-register From 79dc83d1ac055d62b70d172789a6883f29bab186 Mon Sep 17 00:00:00 2001 From: Bharat Kumar Date: Tue, 21 May 2013 17:02:19 +0530 Subject: [PATCH 054/108] blocker bug in dnsmaq config Signed-off-by: Abhinandan Prateek --- core/src/com/cloud/network/DnsMasqConfigurator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/com/cloud/network/DnsMasqConfigurator.java b/core/src/com/cloud/network/DnsMasqConfigurator.java index bbf721d5509..ee8e5fc2e13 100644 --- a/core/src/com/cloud/network/DnsMasqConfigurator.java +++ b/core/src/com/cloud/network/DnsMasqConfigurator.java @@ -110,7 +110,7 @@ import java.util.List; dnsServers = dnsServers+dnsMasqconfigcmd.getDns2()+","; } dnsServers = dnsServers +"*"; - dnsServers = dnsServers.replace(";*", ""); + dnsServers = dnsServers.replace(",*", ""); dnsMasqconf.set(24,"dhcp-option=6,"+dnsServers); return dnsMasqconf.toArray( new String[dnsMasqconf.size()]); } From 37308ebff4a28ae7a1fb7b0bece58ec2cb71de92 Mon Sep 17 00:00:00 2001 From: Wido den Hollander Date: Tue, 21 May 2013 14:35:46 +0200 Subject: [PATCH 055/108] debian: Create the webapps7080 directory --- debian/rules | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/rules b/debian/rules index 575a015effd..e381b1a8ebe 100755 --- a/debian/rules +++ b/debian/rules @@ -80,6 +80,7 @@ install: mkdir -p $(DESTDIR)/$(SYSCONFDIR)/sudoers.d/ mkdir -p $(DESTDIR)/usr/share/$(PACKAGE)-management mkdir -p $(DESTDIR)/usr/share/$(PACKAGE)-management/webapps/client + mkdir -p $(DESTDIR)/usr/share/$(PACKAGE)-management/webapps7080 mkdir $(DESTDIR)/usr/share/$(PACKAGE)-management/setup mkdir $(DESTDIR)/var/log/$(PACKAGE)/management mkdir $(DESTDIR)/var/cache/$(PACKAGE)/management From a8975f952214cd1e7f015934c53311ed7f710779 Mon Sep 17 00:00:00 2001 From: Girish Shilamkar Date: Tue, 21 May 2013 19:25:58 +0530 Subject: [PATCH 056/108] CLOUDSTACK-2474: Remove garbage code which was added while resolving merge conflicts. Signed-off-by: Prasanna Santhanam --- .../integration/component/test_vpc_routers.py | 37 ------------------- 1 file changed, 37 deletions(-) diff --git a/test/integration/component/test_vpc_routers.py b/test/integration/component/test_vpc_routers.py index 763a4cb14f9..7dc95e826e3 100644 --- a/test/integration/component/test_vpc_routers.py +++ b/test/integration/component/test_vpc_routers.py @@ -918,43 +918,6 @@ class TestVPCRouterOneNetwork(cloudstackTestCase): self.debug("VPC network validated - %s" % network.name) return - try: - ssh_1 = self.vm_1.get_ssh_client( - ipaddress=self.public_ip_1.ipaddress.ipaddress) - self.debug("SSH into VM is successfully") - - self.debug("Verifying if we can ping to outside world from VM?") - # Ping to outsite world - res = ssh_1.execute("ping -c 1 www.google.com") - # res = 64 bytes from maa03s17-in-f20.1e100.net (74.125.236.212): - # icmp_req=1 ttl=57 time=25.9 ms - # --- www.l.google.com ping statistics --- - # 1 packets transmitted, 1 received, 0% packet loss, time 0ms - # rtt min/avg/max/mdev = 25.970/25.970/25.970/0.000 ms - result = str(res) - self.assertEqual( - result.count("1 received"), - 1, - "Ping to outside world from VM should be successful" - ) - - self.debug("We should be allowed to ping virtual gateway") - self.debug("VM gateway: %s" % self.vm_1.nic[0].gateway) - - res = ssh_1.execute("ping -c 1 %s" % self.vm_1.nic[0].gateway) - self.debug("ping -c 1 %s: %s" % (self.vm_1.nic[0].gateway, res)) - - result = str(res) - self.assertEqual( - result.count("1 received"), - 1, - "Ping to VM gateway should be successful" - ) - except Exception as e: - self.fail("Failed to SSH into VM - %s, %s" % - (self.public_ip_1.ipaddress.ipaddress, e)) - return - def validate_network_rules(self): """ Validate network rules """ From 1736031fcbd51bcac29f2bd0feef0018bd27c715 Mon Sep 17 00:00:00 2001 From: Girish Shilamkar Date: Tue, 21 May 2013 17:02:58 +0530 Subject: [PATCH 057/108] CLOUDSTACK-2473: Fix a typo in test_vpc_network.py Signed-off-by: Prasanna Santhanam --- test/integration/component/test_vpc_network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/component/test_vpc_network.py b/test/integration/component/test_vpc_network.py index dc535851858..c0c393c4ca1 100644 --- a/test/integration/component/test_vpc_network.py +++ b/test/integration/component/test_vpc_network.py @@ -2003,7 +2003,7 @@ class TestVPCNetworkUpgrade(cloudstackTestCase): ) self.debug("Checking if we can SSH into VM using NAT rule?") try: - ssh_3 = vm_3.get_ssh_client( + ssh_3 = vm_1.get_ssh_client( ipaddress=public_ip_3.ipaddress.ipaddress, reconnect=True, port=self.services["natrule"]["publicport"] From b74d13f9b1daf7e09fa3fa23d963b20ed12e588a Mon Sep 17 00:00:00 2001 From: Girish Shilamkar Date: Tue, 21 May 2013 16:38:39 +0530 Subject: [PATCH 058/108] CLOUDSTACK-2472: Fix unresolved reference to max_value Signed-off-by: Prasanna Santhanam --- test/integration/component/test_project_limits.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/integration/component/test_project_limits.py b/test/integration/component/test_project_limits.py index 17ddfc67da5..9184dca5202 100644 --- a/test/integration/component/test_project_limits.py +++ b/test/integration/component/test_project_limits.py @@ -193,7 +193,7 @@ class TestProjectLimits(cloudstackTestCase): # Also, verify resource limits for the project are independent of # account resource limits # 3. Increase Projects Resources limits above domains limit. Verify - # project can’t have more resources than domain level limit allows. + # project can't have more resources than domain level limit allows. # 4. Create Resource more than its set limit for a project. Verify # resource allocation should fail giving proper message @@ -312,6 +312,7 @@ class TestProjectLimits(cloudstackTestCase): max=2 ) with self.assertRaises(Exception): + max_value = 3 self.debug( "Attempting to update project: %s resource limit to: %s" % ( project.id, @@ -321,7 +322,7 @@ class TestProjectLimits(cloudstackTestCase): update_resource_limit( self.apiclient, resource.resourcetype, - max=3, + max=max_value, projectid=project.id ) return From 2bc88ea277a4024afd3b365910ea9f85fde44ebf Mon Sep 17 00:00:00 2001 From: Girish Shilamkar Date: Tue, 21 May 2013 14:46:24 +0530 Subject: [PATCH 059/108] CLOUDSTACK-778: Add tests for user provided hostname for vms Automation tests to qualify User provides hostname feature. 1. Defines services class 2. Test to verify custom hostname for the instance with internal name 3. Test to verify custom hostname for the instance without internal name Signed-off-by: Prasanna Santhanam --- .../component/test_custom_hostname.py | 369 ++++++++++++++++++ 1 file changed, 369 insertions(+) create mode 100644 test/integration/component/test_custom_hostname.py diff --git a/test/integration/component/test_custom_hostname.py b/test/integration/component/test_custom_hostname.py new file mode 100644 index 00000000000..e5452141d9c --- /dev/null +++ b/test/integration/component/test_custom_hostname.py @@ -0,0 +1,369 @@ +# 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. +""" P1 tests for user provide hostname cases +""" +#Import Local Modules +import marvin +from nose.plugins.attrib import attr +from marvin.cloudstackTestCase import * +from marvin.cloudstackAPI import * +from marvin.integration.lib.utils import * +from marvin.integration.lib.base import * +from marvin.integration.lib.common import * +from marvin.remoteSSHClient import remoteSSHClient +import datetime + + +class Services: + """Test user provided hostname Services + """ + + def __init__(self): + self.services = { + "domain": { + "name": "Domain", + }, + "project": { + "name": "Project", + "displaytext": "Test project", + }, + "account": { + "email": "administrator@clogeny.com", + "firstname": "Test", + "lastname": "User", + "username": "test", + # Random characters are appended for unique + # username + "password": "password", + }, + "user": { + "email": "administrator@clogeny.com", + "firstname": "User", + "lastname": "User", + "username": "User", + # Random characters are appended for unique + # username + "password": "password", + }, + "disk_offering": { + "displaytext": "Tiny Disk Offering", + "name": "Tiny Disk Offering", + "disksize": 1 + }, + "volume": { + "diskname": "Test Volume", + }, + "service_offering": { + "name": "Tiny Instance", + "displaytext": "Tiny Instance", + "cpunumber": 1, + "cpuspeed": 100, # in MHz + "memory": 128, # In MBs + }, + "virtual_machine": { + "displayname": "TestVM", + "username": "root", + "password": "password", + "ssh_port": 22, + "hypervisor": 'XenServer', + # Hypervisor type should be same as + # hypervisor type of cluster + "privateport": 22, + "publicport": 22, + "protocol": 'TCP', + }, + "ostype": 'CentOS 5.3 (64-bit)', + # Cent OS 5.3 (64 bit) + "sleep": 60, + "timeout": 10, + } + + +class TestInstanceNameFlagTrue(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + cls.api_client = super( + TestInstanceNameFlagTrue, + cls + ).getClsTestClient().getApiClient() + cls.services = Services().services + # Get Zone, default template + cls.zone = get_zone(cls.api_client, cls.services) + cls.services["mode"] = cls.zone.networktype + cls.template = get_template( + cls.api_client, + cls.zone.id, + cls.services["ostype"] + ) + + # Create domains, account etc. + cls.domain = get_domain( + cls.api_client, + cls.services + ) + + cls.account = Account.create( + cls.api_client, + cls.services["account"], + admin=True, + domainid=cls.domain.id + ) + + cls.services["virtual_machine"]["zoneid"] = cls.zone.id + cls.services["virtual_machine"]["template"] = cls.template.id + + cls.service_offering = ServiceOffering.create( + cls.api_client, + cls.services["service_offering"] + ) + cls._cleanup = [cls.account] + return + + @classmethod + def tearDownClass(cls): + try: + #Cleanup resources used + cleanup_resources(cls.api_client, cls._cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + return + + def tearDown(self): + try: + #Clean up, terminate the created accounts, domains etc + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + @attr(configuration='vm.instancename.flag') + @attr(tags=["advanced", "basic", "sg", "eip", "advancedns", "simulator"]) + def test_01_user_provided_hostname(self): + """ Verify user provided hostname to an instance + """ + + # Validate the following + # 1. Set the vm.instancename.flag to true. Hostname and displayname + # should be user provided display name + # 2. Give the user provided user name. Internal name should be + # i---display name + + self.debug("Deploying VM in account: %s" % self.account.account.name) + # Spawn an instance in that network + virtual_machine = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + accountid=self.account.account.name, + domainid=self.account.account.domainid, + serviceofferingid=self.service_offering.id, + ) + self.debug( + "Checking if the virtual machine is created properly or not?") + vms = VirtualMachine.list( + self.apiclient, + id=virtual_machine.id, + listall=True + ) + + self.assertEqual( + isinstance(vms, list), + True, + "List vms should return a valid name" + ) + vm = vms[0] + self.assertEqual( + vm.state, + "Running", + "Vm state should be running after deployment" + ) + self.debug("vm.displayname: %s, original: %s" % + (vm.displayname, + self.services["virtual_machine"]["displayname"])) + self.assertEqual( + vm.displayname, + self.services["virtual_machine"]["displayname"], + "Vm display name should match the given name" + ) + + # Fetch account ID and VMID from database to check internal name + self.debug("select id from account where uuid = '%s';" \ + % self.account.account.id) + + qresultset = self.dbclient.execute( + "select id from account where uuid = '%s';" \ + % self.account.account.id + ) + self.assertEqual( + isinstance(qresultset, list), + True, + "Check DB query result set for valid data" + ) + + self.assertNotEqual( + len(qresultset), + 0, + "Check DB Query result set" + ) + qresult = qresultset[0] + account_id = qresult[0] + + self.debug("select id from vm_instance where uuid = '%s';" % vm.id) + + qresultset = self.dbclient.execute( + "select id from vm_instance where uuid = '%s';" % + vm.id) + + self.assertEqual( + isinstance(qresultset, list), + True, + "Check DB query result set for valid data" + ) + + self.assertNotEqual( + len(qresultset), + 0, + "Check DB Query result set" + ) + qresult = qresultset[0] + self.debug("Query result: %s" % qresult) + vmid = qresult[0] + + #internal Name = i---Display name + internal_name = "i-" + str(account_id) + "-" + str(vmid) + "-" + vm.displayname + self.debug("Internal name: %s" % internal_name) + self.assertEqual( + vm.instancename, + internal_name, + "VM internal name should match with that of the format" + ) + return + + @attr(configuration='vm.instancename.flag') + @attr(tags=["advanced", "basic", "sg", "eip", "advancedns", "simulator"]) + def test_02_instancename_from_default_configuration(self): + """ Verify for globally set instancename + """ + + # Validate the following + # 1. Set the vm.instancename.flag to true. Hostname and displayname + # should be user provided display name + # 2. Dont give the user provided user name. Internal name should be + # i--- in global config + + # Removing display name from config + del self.services["virtual_machine"]["displayname"] + self.debug("Deploying VM in account: %s" % self.account.account.name) + virtual_machine = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + accountid=self.account.account.name, + domainid=self.account.account.domainid, + serviceofferingid=self.service_offering.id, + ) + self.debug( + "Checking if the virtual machine is created properly or not?") + vms = VirtualMachine.list( + self.apiclient, + id=virtual_machine.id, + listall=True + ) + + self.assertEqual( + isinstance(vms, list), + True, + "List vms should retuen a valid name" + ) + vm = vms[0] + self.assertEqual( + vm.state, + "Running", + "Vm state should be running after deployment" + ) + self.assertEqual( + vm.displayname, + vm.id, + "Vm display name should not match the given name" + ) + # Fetch account ID and VMID from database to check internal name + self.debug("select id from account where uuid = '%s';" \ + % self.account.account.id) + + qresultset = self.dbclient.execute( + "select id from account where uuid = '%s';" \ + % self.account.account.id + ) + self.assertEqual( + isinstance(qresultset, list), + True, + "Check DB query result set for valid data" + ) + + self.assertNotEqual( + len(qresultset), + 0, + "Check DB Query result set" + ) + qresult = qresultset[0] + account_id = qresult[0] + + self.debug("select id from vm_instance where uuid = '%s';" % vm.id) + + qresultset = self.dbclient.execute( + "select id from vm_instance where uuid = '%s';" % + vm.id) + + self.assertEqual( + isinstance(qresultset, list), + True, + "Check DB query result set for valid data" + ) + + self.assertNotEqual( + len(qresultset), + 0, + "Check DB Query result set" + ) + qresult = qresultset[0] + self.debug("Query result: %s" % qresult) + vmid = qresult[0] + + self.debug("Fetching the global config value for instance.name") + configs = Configurations.list( + self.apiclient, + name="instance.name", + listall=True + ) + + config = configs[0] + instance_name = config.value + self.debug("Instance.name: %s" % instance_name) + + #internal Name = i--- Instance_name + internal_name = "i-" + str(account_id) + "-" + str(vmid) + "-" + instance_name + self.assertEqual( + vm.instancename, + internal_name, + "VM internal name should match with that of the format" + ) + return From e42ddb83c2a5bed8bec8640372518616592c6d4c Mon Sep 17 00:00:00 2001 From: Jessica Wang Date: Mon, 20 May 2013 19:17:21 -0700 Subject: [PATCH 060/108] CLOUDSTACK-747: internalLb in VPC - UI - create network offering - add LB Type dropdodwn which is shown when VPC is checked and LB service is checked, hidden otherwise. LB Type (publicLb, internalLb) will determine the options in LB Provider dropdown. --- ui/scripts/configuration.js | 85 +++++++++++++++++++++++++++++++++---- 1 file changed, 77 insertions(+), 8 deletions(-) diff --git a/ui/scripts/configuration.js b/ui/scripts/configuration.js index e3421a380ef..058f44097b8 100644 --- a/ui/scripts/configuration.js +++ b/ui/scripts/configuration.js @@ -1122,6 +1122,7 @@ title: 'label.add.network.offering', preFilter: function(args) { var $availability = args.$form.find('.form-item[rel=availability]'); + var $lbType = args.$form.find('.form-item[rel=lbType]'); var $systemOfferingForRouter = args.$form.find('.form-item[rel=systemOfferingForRouter]'); var $conservemode = args.$form.find('.form-item[rel=conservemode]'); var $serviceSourceNatRedundantRouterCapabilityCheckbox = args.$form.find('.form-item[rel="service.SourceNat.redundantRouterCapabilityCheckbox"]'); @@ -1147,18 +1148,18 @@ //check whether to show or hide availability field var $sourceNATField = args.$form.find('input[name=\"service.SourceNat.isEnabled\"]'); var $guestTypeField = args.$form.find('select[name=guestIpType]'); - + + var $useVpc = args.$form.find('.form-item[rel=\"useVpc\"]'); + var $useVpcCb = $useVpc.find("input[type=checkbox]"); if($guestTypeField.val() == 'Shared') { //Shared network offering - args.$form.find('.form-item[rel=\"useVpc\"]').hide(); - - var $useVpcCb = args.$form.find('.form-item[rel=\"useVpc\"]').find("input[type=checkbox]"); + $useVpc.hide(); if($useVpcCb.is(':checked')) { //if useVpc is checked, $useVpcCb.removeAttr("checked"); //remove "checked" attribute in useVpc $useVpcCb.trigger("click"); //trigger useVpc.onChange() } } else { //Isolated network offering - args.$form.find('.form-item[rel=\"useVpc\"]').css('display', 'inline-block'); + $useVpc.css('display', 'inline-block'); } @@ -1170,7 +1171,14 @@ $availability.hide(); } - + //when useVpc is checked and service.Lb.isEnabled is checked + if($useVpcCb.is(':checked') && $("input[name='service.Lb.isEnabled']").is(":checked") == true) { + $lbType.css('display', 'inline-block'); + } + else { + $lbType.hide(); + } + //when service(s) has Virtual Router as provider..... var havingVirtualRouterForAtLeastOneService = false; $(serviceCheckboxNames).each(function(){ @@ -1427,7 +1435,7 @@ label: 'VPC', docID: 'helpNetworkOfferingVPC', isBoolean: true, - onChange: function(args) { + onChange: function(args) { var $checkbox = args.$checkbox; var $selects = $checkbox.closest('form').find('.dynamic-input select'); var $vpcOptions = $selects.find('option[value=VpcVirtualRouter]'); @@ -1444,7 +1452,67 @@ } } }, - + + lbType: { //only shown when VPC is checked and LB service is checked + label: 'Load Balancer Type', + isHidden: true, + select: function(args) { + args.response.success({data: [ + {id: 'publicLb', description: 'Public LB'}, + {id: 'internalLb', description: 'Internal LB'} + ]}); + + args.$select.change(function() { + if($(this).is(':visible') == false) + return; //if lbType is not visible, do nothing. + + var $lbProvider = $(this).closest('form').find('.form-item[rel=\"service.Lb.provider\"]').find('select'); + var $lbProviderOptions = $lbProvider.find('option'); + + if($(this).val() == 'publicLb') { //disable all providers except the ones in lbProviderMap.publicLb.vpc => ["VpcVirtualRouter", "Netscaler"] + for(var i = 0; i < $lbProviderOptions.length; i++ ) { + var $option = $lbProviderOptions.eq(i); + var supportedProviders = lbProviderMap.publicLb.vpc; + var thisOpionIsSupported = false; + for(var k = 0; k < supportedProviders.length; k++ ) { + if($option.val() == supportedProviders[k]) { + thisOpionIsSupported = true; + break; + } + } + if(thisOpionIsSupported == true) { + $option.attr('disabled', false); + } + else { + $option.attr('disabled', true); + } + } + } + else if($(this).val() == 'internalLb') { //disable all providers except the ones in lbProviderMap.internalLb.vpc => ["InternalLbVm"] + for(var i = 0; i < $lbProviderOptions.length; i++ ) { + var $option = $lbProviderOptions.eq(i); + var supportedProviders = lbProviderMap.internalLb.vpc; + var thisOpionIsSupported = false; + for(var k = 0; k < supportedProviders.length; k++ ) { + if($option.val() == supportedProviders[k]) { + thisOpionIsSupported = true; + break; + } + } + if(thisOpionIsSupported == true) { + $option.attr('disabled', false); + } + else { + $option.attr('disabled', true); + } + } + } + + $lbProvider.val($lbProvider.find('option:first')); + }); + } + }, + supportedServices: { label: 'label.supported.services', @@ -1571,6 +1639,7 @@ //show or hide upon checked services and selected providers above (begin) systemOfferingForRouter: { label: 'System Offering for Router', + isHidden: true, docID: 'helpNetworkOfferingSystemOffering', select: function(args) { $.ajax({ From 263cc9a62c731a22518056a601c9c57fb3777690 Mon Sep 17 00:00:00 2001 From: Jessica Wang Date: Tue, 21 May 2013 10:57:12 -0700 Subject: [PATCH 061/108] CLOUDSTACK-747: internalLb in VPC - UI - create network offering - when VPC checkbox is checked, enable provider InternalLbVm, VpcVirtualRouter, Netscaler. When VPC checkbox is unchecked, disable provider InternalLbVm, VpcVirtualRouter. --- ui/scripts/configuration.js | 41 ++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/ui/scripts/configuration.js b/ui/scripts/configuration.js index 058f44097b8..7485898dc19 100644 --- a/ui/scripts/configuration.js +++ b/ui/scripts/configuration.js @@ -1436,20 +1436,37 @@ docID: 'helpNetworkOfferingVPC', isBoolean: true, onChange: function(args) { - var $checkbox = args.$checkbox; - var $selects = $checkbox.closest('form').find('.dynamic-input select'); - var $vpcOptions = $selects.find('option[value=VpcVirtualRouter]'); + var $vpc = args.$checkbox; + var $providers = $vpc.closest('form').find('.dynamic-input select'); + var $optionsOfProviders = $providers.find('option'); - if ($checkbox.is(':checked')) { - $vpcOptions.siblings().attr('disabled', true); - $selects.val('VpcVirtualRouter'); - } else { - $vpcOptions.siblings().attr('disabled', false); - $vpcOptions.attr('disabled', true); - $selects.each(function() { - $(this).val($(this).find('option:first')); - }); + //p.s. Netscaler is supported in both vpc and non-vpc + + if ($vpc.is(':checked')) { //*** vpc *** + $optionsOfProviders.each(function(index) { + if($(this).val() == 'InternalLbVm' || $(this).val() == 'VpcVirtualRouter' || $(this).val() == 'Netscaler') { + $(this).attr('disabled', false); + } + else { + $(this).attr('disabled', true); + } + }); + } + else { //*** non-vpc *** + $optionsOfProviders.each(function(index) { + if($(this).val() == 'InternalLbVm' || $(this).val() == 'VpcVirtualRouter') { + $(this).attr('disabled', true); + } + else { + $(this).attr('disabled', false); + } + }); } + + $providers.each(function() { + $(this).val($(this).find('option:first')); + }); + } }, From a75cf9a79d9f3f3c6c399b03e73362523e3d815a Mon Sep 17 00:00:00 2001 From: Jessica Wang Date: Tue, 21 May 2013 11:37:12 -0700 Subject: [PATCH 062/108] CLOUDSTACK-747: internalLb in VPC - UI - create network offering - when Lb service is checked and LB provider is InternalLbVm, pass capability type as lbSchemes and capability value as internal. --- ui/scripts/configuration.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ui/scripts/configuration.js b/ui/scripts/configuration.js index 7485898dc19..92c3d00478e 100644 --- a/ui/scripts/configuration.js +++ b/ui/scripts/configuration.js @@ -1834,7 +1834,13 @@ inputData['servicecapabilitylist[' + serviceCapabilityIndex + '].capabilitytype'] = 'associatePublicIP'; inputData['servicecapabilitylist[' + serviceCapabilityIndex + '].capabilityvalue'] = true; //because this checkbox's value == "on" serviceCapabilityIndex++; - } + } + else if((key == 'service.Lb.provider') && ("Lb" in serviceProviderMap) && (serviceProviderMap.Lb == "InternalLbVm")) { + inputData['servicecapabilitylist[' + serviceCapabilityIndex + '].service'] = 'lb'; + inputData['servicecapabilitylist[' + serviceCapabilityIndex + '].capabilitytype'] = 'lbSchemes'; + inputData['servicecapabilitylist[' + serviceCapabilityIndex + '].capabilityvalue'] = 'internal'; + serviceCapabilityIndex++; + } } else if (value != '') { // Normal data inputData[key] = value; From 92ad6abab0063771dffaabb7c9d6d8256083c5be Mon Sep 17 00:00:00 2001 From: Sheng Yang Date: Tue, 21 May 2013 14:46:31 -0700 Subject: [PATCH 063/108] PVLAN: Fix NPE when VM are in allocated state If vlan is not assigned for VM, nic.getBroadcastUri() would be null. Then just ignore it. --- .../network/router/VirtualNetworkApplianceManagerImpl.java | 2 +- server/src/com/cloud/vm/UserVmManagerImpl.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java b/server/src/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java index 64e412ad1df..b969be25fde 100755 --- a/server/src/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java +++ b/server/src/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java @@ -2609,7 +2609,7 @@ public class VirtualNetworkApplianceManagerImpl extends ManagerBase implements V List routerNics = _nicDao.listByVmId(profile.getId()); for (Nic nic : routerNics) { Network network = _networkModel.getNetwork(nic.getNetworkId()); - if (network.getTrafficType() == TrafficType.Guest && nic.getBroadcastUri().getScheme().equals("pvlan")) { + if (network.getTrafficType() == TrafficType.Guest && nic.getBroadcastUri() != null && nic.getBroadcastUri().getScheme().equals("pvlan")) { NicProfile nicProfile = new NicProfile(nic, network, nic.getBroadcastUri(), nic.getIsolationUri(), 0, false, "pvlan-nic"); setupDhcpForPvlan(false, domR, domR.getHostId(), nicProfile); } diff --git a/server/src/com/cloud/vm/UserVmManagerImpl.java b/server/src/com/cloud/vm/UserVmManagerImpl.java index 71b4e3fafb7..5e206567bed 100755 --- a/server/src/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/com/cloud/vm/UserVmManagerImpl.java @@ -3059,7 +3059,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Use for (NicVO nic : nics) { NetworkVO network = _networkDao.findById(nic.getNetworkId()); if (network.getTrafficType() == TrafficType.Guest) { - if (nic.getBroadcastUri().getScheme().equals("pvlan")) { + if (nic.getBroadcastUri() != null && nic.getBroadcastUri().getScheme().equals("pvlan")) { NicProfile nicProfile = new NicProfile(nic, network, nic.getBroadcastUri(), nic.getIsolationUri(), 0, false, "pvlan-nic"); setupVmForPvlan(false, vm.getHostId(), nicProfile); } From 0a443697ea48d48310a24c5e25a2298522183901 Mon Sep 17 00:00:00 2001 From: Brian Federle Date: Tue, 21 May 2013 15:26:55 -0700 Subject: [PATCH 064/108] Add base internal LB provider module --- .../internalLbProvider/internalLbProvider.js | 182 ++++++++++++++++++ ui/modules/modules.js | 3 +- 2 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 ui/modules/internalLbProvider/internalLbProvider.js diff --git a/ui/modules/internalLbProvider/internalLbProvider.js b/ui/modules/internalLbProvider/internalLbProvider.js new file mode 100644 index 00000000000..aaa386ee246 --- /dev/null +++ b/ui/modules/internalLbProvider/internalLbProvider.js @@ -0,0 +1,182 @@ +// 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. +(function($, cloudStack) { + cloudStack.modules.internalLbProvider = function(module) { + var internalLbDeviceViewAll = [ + { + label: 'Devices', + path: '_zone.internalLbDevices' + } + ]; + + var internalLbListView = { + id: 'internalLbDevices', + fields: { + resourcename: { label: 'Resource Name' }, + provider: { label: 'Provider' } + }, + dataProvider: function(args) { + args.response.success({ data: [] }); + }, + actions: { + add: { + label: 'Add internal LB device', + + messages: { + notification: function(args) { + return 'Add internal LB device'; + } + }, + + createForm: { + title: 'Add internal LB device', + fields: { + hostname: { + label: 'label.host', + validation: { required: true } + }, + username: { + label: 'label.username', + validation: { required: true } + }, + password: { + label: 'label.password', + isPassword: true, + validation: { required: true } + } + } + }, + + action: function(args) { + args.response.success(); + }, + + notification: { + poll: function(args) { + args.complete(); + } + } + } + }, + + detailView: { + name: 'Internal LB resource details', + actions: { + remove: { + label: 'delete Internal LB resource', + messages: { + confirm: function(args) { + return 'Please confirm you want to delete Internal LB resource'; + }, + notification: function(args) { + return 'delete Internal LB resource'; + } + }, + action: function(args) { + args.response.success(); + }, + notification: { + poll: function(args) { + args.complete(); + } + } + } + }, + + tabs: { + details: { + title: 'label.details', + fields: [ + { + resourcename: { label: 'Resource Name' } + }, + { + resourceid: { label: 'Resource ID'}, + provider: { label: 'Provider' }, + RESOURCE_NAME: { label: 'Resource Name'} + } + ], + dataProvider: function(args) { + args.response.success({ data: args.context.internalLbDevices[0] }); + } + } + } + } + }; + + var internalLbProviderDetailView = { + id: 'internalLbProvider', + label: 'internal LB', + viewAll: internalLbDeviceViewAll, + tabs: { + details: { + title: 'label.details', + fields: [ + { + name: { label: 'label.name' } + }, + { + state: { label: 'label.state' }, + id: { label: 'label.id' }, + servicelist: { + label: 'Services', + converter: function(args){ + if(args) + return args.join(', '); + else + return ''; + } + } + } + ], + dataProvider: function(args) { + $.ajax({ + url: createURL('listNetworkServiceProviders'), + data: { + name: 'InternalLb', + physicalnetworkid: args.context.physicalNetworks[0].id + }, + success: function(json){ + var items = json.listnetworkserviceprovidersresponse.networkserviceprovider; + if(items != null && items.length > 0) { + args.response.success({ data: items[0] }); + } + else { + args.response.success({ + data: { + name: 'InternalLb', + state: 'Disabled' + } + }) + } + } + }); + } + } + } + }; + + module.infrastructure.networkServiceProvider({ + id: 'internalLb', + name: 'Internal LB', + //state: 'Disabled', //don't know state until log in and visit Infrastructure menu > zone detail > physical network > network service providers + listView: internalLbListView, + + detailView: internalLbProviderDetailView + }); + }; +}(jQuery, cloudStack)); diff --git a/ui/modules/modules.js b/ui/modules/modules.js index d4502a195bc..31701220dd8 100644 --- a/ui/modules/modules.js +++ b/ui/modules/modules.js @@ -18,6 +18,7 @@ cloudStack.modules = [ 'infrastructure', 'vnmcNetworkProvider', - 'vnmcAsa1000v' + 'vnmcAsa1000v', + 'internalLbProvider' ]; }(jQuery, cloudStack)); From dce42581710ce3613f4bf765d713fab9552747ca Mon Sep 17 00:00:00 2001 From: Prachi Damle Date: Tue, 21 May 2013 16:05:07 -0700 Subject: [PATCH 065/108] CLOUDSTACK-2568: ACS41 regression in storage subsystem (seen with local storage and 2 or more hosts) Changes: - In VolumeReservationVO, the getter method of a column had a typo, causing us to create a wrong searchbuilder. It was searching over the 'id' column instead of 'vm_reservation_id' causing - This bug was causing the vm deployment to choose a wrong pool during deployment since the search was choosing incorrectly - This bug in the GenericSearchBuilder is also fixed - if the getter method does not use the standard 'get' or 'is' prefix, one should annotate that method using @Column(name = "") and indicate which column this method refers to. This will cause the GenericSearchBuilder to identify the field correctly. --- .../entity/api/db/VolumeReservationVO.java | 22 +++---------------- .../api/db/dao/VolumeReservationDaoImpl.java | 2 +- .../cloud/utils/db/GenericSearchBuilder.java | 18 +++++++++------ 3 files changed, 15 insertions(+), 27 deletions(-) diff --git a/engine/orchestration/src/org/apache/cloudstack/engine/cloud/entity/api/db/VolumeReservationVO.java b/engine/orchestration/src/org/apache/cloudstack/engine/cloud/entity/api/db/VolumeReservationVO.java index f064623f887..e858f740a07 100644 --- a/engine/orchestration/src/org/apache/cloudstack/engine/cloud/entity/api/db/VolumeReservationVO.java +++ b/engine/orchestration/src/org/apache/cloudstack/engine/cloud/entity/api/db/VolumeReservationVO.java @@ -16,22 +16,14 @@ // under the License. package org.apache.cloudstack.engine.cloud.entity.api.db; -import java.util.Date; -import java.util.Map; - import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; -import javax.persistence.Transient; - -import org.apache.cloudstack.api.Identity; import org.apache.cloudstack.api.InternalIdentity; -import com.cloud.utils.db.GenericDao; - @Entity @Table(name = "volume_reservation") public class VolumeReservationVO implements InternalIdentity{ @@ -42,7 +34,7 @@ public class VolumeReservationVO implements InternalIdentity{ private long id; @Column(name = "vm_reservation_id") - private Long vmReservationId; + private long vmReservationId; @Column(name = "vm_id") private long vmId; @@ -53,10 +45,6 @@ public class VolumeReservationVO implements InternalIdentity{ @Column(name="pool_id") private long poolId; - // VolumeId -> poolId - @Transient - Map volumeReservationMap; - /** * There should never be a public constructor for this class. Since it's * only here to define the table for the DAO class. @@ -64,7 +52,7 @@ public class VolumeReservationVO implements InternalIdentity{ protected VolumeReservationVO() { } - public VolumeReservationVO(long vmId, long volumeId, long poolId, Long vmReservationId) { + public VolumeReservationVO(long vmId, long volumeId, long poolId, long vmReservationId) { this.vmId = vmId; this.volumeId = volumeId; this.poolId = poolId; @@ -80,7 +68,7 @@ public class VolumeReservationVO implements InternalIdentity{ return vmId; } - public Long geVmReservationId() { + public long getVmReservationId() { return vmReservationId; } @@ -93,8 +81,4 @@ public class VolumeReservationVO implements InternalIdentity{ } - public Map getVolumeReservation(){ - return volumeReservationMap; - } - } diff --git a/engine/orchestration/src/org/apache/cloudstack/engine/cloud/entity/api/db/dao/VolumeReservationDaoImpl.java b/engine/orchestration/src/org/apache/cloudstack/engine/cloud/entity/api/db/dao/VolumeReservationDaoImpl.java index 26bc65f35c1..4f3761b7f45 100644 --- a/engine/orchestration/src/org/apache/cloudstack/engine/cloud/entity/api/db/dao/VolumeReservationDaoImpl.java +++ b/engine/orchestration/src/org/apache/cloudstack/engine/cloud/entity/api/db/dao/VolumeReservationDaoImpl.java @@ -49,7 +49,7 @@ public class VolumeReservationDaoImpl extends GenericDaoBase implements MethodInterceptor { set(fieldName); return null; } else { - name = name.toLowerCase(); - for (String fieldName : _attrs.keySet()) { - if (name.endsWith(fieldName.toLowerCase())) { - set(fieldName); - return null; - } - } + Column ann = method.getAnnotation(Column.class); + if (ann != null) { + String colName = ann.name(); + for (Map.Entry attr : _attrs.entrySet()) { + if (colName.equals(attr.getValue().columnName)) { + set(attr.getKey()); + return null; + } + } + } assert false : "Perhaps you need to make the method start with get or is?"; } } From e31553aff827abd88e3dfa9b65bfb335e09fbd22 Mon Sep 17 00:00:00 2001 From: Jayapal Date: Thu, 16 May 2013 19:01:17 +0530 Subject: [PATCH 066/108] CLOUDSTACK-2308 fixed adding route in vware for mgmt subnet Signed-off-by: Abhinandan Prateek --- patches/systemvm/debian/config/etc/init.d/cloud-early-config | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/patches/systemvm/debian/config/etc/init.d/cloud-early-config b/patches/systemvm/debian/config/etc/init.d/cloud-early-config index 893a2455bc4..d918670edab 100755 --- a/patches/systemvm/debian/config/etc/init.d/cloud-early-config +++ b/patches/systemvm/debian/config/etc/init.d/cloud-early-config @@ -757,7 +757,10 @@ EOF fi if [ -n "$MGMTNET" -a -n "$LOCAL_GW" ] then - ip route add $MGMTNET via $LOCAL_GW dev eth1 + if [ "$hyp" == "vmware" ] + then + ip route add $MGMTNET via $LOCAL_GW dev eth0 + fi fi ip route delete default From 49e39e51f2bd39c1e5cd60389f4433d58f9415bc Mon Sep 17 00:00:00 2001 From: Pranav Saxena Date: Wed, 22 May 2013 12:31:48 +0530 Subject: [PATCH 067/108] CLOUDSTACK-681:Implicit Dedication UI support --- ui/scripts/configuration.js | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/ui/scripts/configuration.js b/ui/scripts/configuration.js index 92c3d00478e..8856d4b5545 100644 --- a/ui/scripts/configuration.js +++ b/ui/scripts/configuration.js @@ -144,6 +144,27 @@ }, + deploymentPlanner:{ + label:'Deployment Planner', + select:function(args){ + $.ajax({ + url:createURL('listDeploymentPlanners'), + dataType:'json', + success:function(json){ + var items=[]; + var plannerObjs = json.listdeploymentplannersresponse.deploymentPlanner; + $(plannerObjs).each(function(){ + items.push({id: this.name, description: this.name}); + }); + args.response.success({data: items}); + + + } + }); + } + }, + + domainId: { label: 'label.domain', docID: 'helpComputeOfferingDomain', @@ -176,7 +197,9 @@ storageType: args.data.storageType, cpuNumber: args.data.cpuNumber, cpuSpeed: args.data.cpuSpeed, - memory: args.data.memory + memory: args.data.memory, + deploymentplanner: args.data.deploymentPlanner + }; if(args.data.networkRate != null && args.data.networkRate.length > 0) { @@ -362,6 +385,7 @@ converter: cloudStack.converters.toBooleanText }, isvolatile:{ label:'Volatile' , converter: cloudStack.converters.toBooleanText }, + deploymentplanner:{label:'Deployment Planner'}, tags: { label: 'label.storage.tags' }, hosttags: { label: 'label.host.tags' }, domain: { label: 'label.domain' }, From 11f85c9c9e5ab06e53ebdf8f42fca8054850b1c9 Mon Sep 17 00:00:00 2001 From: Rajesh Battala Date: Wed, 15 May 2013 17:21:46 +0530 Subject: [PATCH 068/108] CLOUDSTACK-2398: ssvm-check.sh failed with permission error RPCbind service is running in the 4.2 systemVMs resulting in ssvm-check trying to write to the mountpoint. Avoid writing to the rpc_pipefs. Signed-off-by: Prasanna Santhanam --- services/secondary-storage/scripts/ssvm-check.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/secondary-storage/scripts/ssvm-check.sh b/services/secondary-storage/scripts/ssvm-check.sh index a4011647f07..7b83c989218 100644 --- a/services/secondary-storage/scripts/ssvm-check.sh +++ b/services/secondary-storage/scripts/ssvm-check.sh @@ -67,12 +67,12 @@ fi # check to see if we have the NFS volume mounted echo ================================================ -mount|grep -v sunrpc|grep nfs 1> /dev/null 2>&1 +mount|grep -v sunrpc|grep -v rpc_pipefs|grep nfs 1> /dev/null 2>&1 if [ $? -eq 0 ] then echo "NFS is currently mounted" # check for write access - for MOUNTPT in `mount|grep -v sunrpc|grep nfs| awk '{print $3}'` + for MOUNTPT in `mount|grep -v sunrpc|grep -v rpc_pipefs|grep nfs| awk '{print $3}'` do if [ $MOUNTPT != "/proc/xen" ] # mounted by xen then From 5d3e6bd397464f93ba8890aa29cd5a32558c0c0a Mon Sep 17 00:00:00 2001 From: Pranav Saxena Date: Wed, 22 May 2013 14:35:16 +0530 Subject: [PATCH 069/108] Implicit Dedication - Key and Value for iMplicit Dedication planner --- ui/scripts/configuration.js | 19 +++++++++++++++++-- ui/scripts/docs.js | 9 +++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/ui/scripts/configuration.js b/ui/scripts/configuration.js index 8856d4b5545..fdeaba015b7 100644 --- a/ui/scripts/configuration.js +++ b/ui/scripts/configuration.js @@ -164,6 +164,17 @@ } }, + plannerKey:{label:'Planner Key' , docID:'helpImplicitPlannerKey'}, + plannerMode:{ + label:'Planner Mode', + select:function(args){ + var items=[]; + items.push({id:'',description:''}); + items.push({id:'Strict', description:'Strict'}); + items.push({id:'Preffered', description:'Preffered'}); + args.response.success({data:items}); + } + }, domainId: { label: 'label.domain', @@ -201,7 +212,11 @@ deploymentplanner: args.data.deploymentPlanner }; - + var array1 =[]; + if(args.data.plannerMode != null && args.data.plannerKey !=""){ + array1.push("&serviceofferingdetails[0]." + args.data.plannerKey + "=" + args.data.plannerMode); + } + if(args.data.networkRate != null && args.data.networkRate.length > 0) { $.extend(data, { networkrate: args.data.networkRate @@ -239,7 +254,7 @@ } $.ajax({ - url: createURL('createServiceOffering'), + url: createURL('createServiceOffering' + array1.join("")), data: data, success: function(json) { var item = json.createserviceofferingresponse.serviceoffering; diff --git a/ui/scripts/docs.js b/ui/scripts/docs.js index 4a70ca1df7f..7c1aaf83c35 100755 --- a/ui/scripts/docs.js +++ b/ui/scripts/docs.js @@ -16,6 +16,15 @@ // under the License. cloudStack.docs = { + //Implicit Planner + + helpImplicitPlannerKey:{ + + desc:'Please provide a Planner key for the Implicit Planner you are going to use and then select its mode below .Eg - Planner Key :ImplicitDedicationMode', + externalLink:'' + + }, + //Delete/archive events helpEventsDeleteType:{ From 83f84adda2715eae60c47738eee886a48fbc5b03 Mon Sep 17 00:00:00 2001 From: Koushik Das Date: Wed, 22 May 2013 14:49:48 +0530 Subject: [PATCH 070/108] CLOUDSTACK-2585: Failed to apply new PF rules after deleting the existing PF Rule with Cisco VNMC Provider Each rule created in VNMC under a policy object needs to have an unique order value. Rules are evaluated based on this value. Eariler order was computed based on the rule count under a policy object. This resulted in duplicate order value when rules get deleted and recreated. Changed the logic to compute order based on the CS db id of the rule which is unique. --- .../network/cisco/CiscoVnmcConnection.java | 22 ++-- .../cisco/CiscoVnmcConnectionImpl.java | 112 +++++++----------- .../network/resource/CiscoVnmcResource.java | 20 ++-- .../resource/CiscoVnmcResourceTest.java | 22 ++-- 4 files changed, 77 insertions(+), 99 deletions(-) diff --git a/plugins/network-elements/cisco-vnmc/src/com/cloud/network/cisco/CiscoVnmcConnection.java b/plugins/network-elements/cisco-vnmc/src/com/cloud/network/cisco/CiscoVnmcConnection.java index 28e2535ca91..be4814ae6be 100644 --- a/plugins/network-elements/cisco-vnmc/src/com/cloud/network/cisco/CiscoVnmcConnection.java +++ b/plugins/network-elements/cisco-vnmc/src/com/cloud/network/cisco/CiscoVnmcConnection.java @@ -74,16 +74,16 @@ public interface CiscoVnmcConnection { String ipAddress) throws ExecutionException; public boolean createTenantVDCDNatRule(String tenantName, - String identifier, String policyIdentifier, + long ruleId, String policyIdentifier, String publicIp) throws ExecutionException; public boolean deleteTenantVDCDNatRule(String tenantName, - String identifier, String policyIdentifier) + long ruleId, String policyIdentifier) throws ExecutionException; public boolean createTenantVDCAclRuleForDNat(String tenantName, - String identifier, String policyIdentifier, + long ruleId, String policyIdentifier, String ipAddress) throws ExecutionException; @@ -104,17 +104,17 @@ public interface CiscoVnmcConnection { String ipAddress) throws ExecutionException; public boolean createTenantVDCPFRule(String tenantName, - String identifier, String policyIdentifier, + long ruleId, String policyIdentifier, String protocol, String publicIp, String startPort, String endPort) throws ExecutionException; public boolean deleteTenantVDCPFRule(String tenantName, - String identifier, String policyIdentifier) + long ruleId, String policyIdentifier) throws ExecutionException; public boolean createTenantVDCAclRuleForPF(String tenantName, - String identifier, String policyIdentifier, + long ruleId, String policyIdentifier, String protocol, String ipAddress, String startPort, String endPort) throws ExecutionException; @@ -138,29 +138,29 @@ public interface CiscoVnmcConnection { throws ExecutionException; public boolean createTenantVDCIngressAclRule(String tenantName, - String identifier, String policyIdentifier, + long ruleId, String policyIdentifier, String protocol, String sourceStartIp, String sourceEndIp, String destStartPort, String destEndPort) throws ExecutionException; public boolean createTenantVDCIngressAclRule(String tenantName, - String identifier, String policyIdentifier, + long ruleId, String policyIdentifier, String protocol, String sourceStartIp, String sourceEndIp) throws ExecutionException; public boolean createTenantVDCEgressAclRule(String tenantName, - String identifier, String policyIdentifier, + long ruleId, String policyIdentifier, String protocol, String sourceStartIp, String sourceEndIp, String destStartPort, String destEndPort) throws ExecutionException; public boolean createTenantVDCEgressAclRule(String tenantName, - String identifier, String policyIdentifier, + long ruleId, String policyIdentifier, String protocol, String sourceStartIp, String sourceEndIp) throws ExecutionException; public boolean deleteTenantVDCAclRule(String tenantName, - String identifier, String policyIdentifier) throws ExecutionException; + long ruleId, String policyIdentifier) throws ExecutionException; public boolean createTenantVDCAclPolicy(String tenantName, String identifier) throws ExecutionException; diff --git a/plugins/network-elements/cisco-vnmc/src/com/cloud/network/cisco/CiscoVnmcConnectionImpl.java b/plugins/network-elements/cisco-vnmc/src/com/cloud/network/cisco/CiscoVnmcConnectionImpl.java index a9e8cf633f9..72ecc67cad6 100644 --- a/plugins/network-elements/cisco-vnmc/src/com/cloud/network/cisco/CiscoVnmcConnectionImpl.java +++ b/plugins/network-elements/cisco-vnmc/src/com/cloud/network/cisco/CiscoVnmcConnectionImpl.java @@ -490,12 +490,8 @@ public class CiscoVnmcConnectionImpl implements CiscoVnmcConnection { xml = replaceXmlValue(xml, "srcendip", endSourceIp); xml = replaceXmlValue(xml, "ippoolname", getNameForSourceNatIpPool(tenantName)); - List rules = listChildren(getDnForSourceNatPolicy(tenantName)); - int order = 100; - if (rules != null) { - order += rules.size(); - } - xml = replaceXmlValue(xml, "order", Integer.toString(order)); + long order = 100; + xml = replaceXmlValue(xml, "order", Long.toString(order)); String response = sendRequest(service, xml); return verifySuccess(response); @@ -671,12 +667,13 @@ public class CiscoVnmcConnectionImpl implements CiscoVnmcConnection { @Override public boolean createTenantVDCIngressAclRule(String tenantName, - String identifier, String policyIdentifier, + long ruleId, String policyIdentifier, String protocol, String sourceStartIp, String sourceEndIp, String destStartPort, String destEndPort) throws ExecutionException { String xml = VnmcXml.CREATE_INGRESS_ACL_RULE.getXml(); String service = VnmcXml.CREATE_INGRESS_ACL_RULE.getService(); + String identifier = Long.toString(ruleId); xml = replaceXmlValue(xml, "cookie", _cookie); xml = replaceXmlValue(xml, "aclruledn", getDnForAclRule(tenantName, identifier, policyIdentifier)); xml = replaceXmlValue(xml, "aclrulename", getNameForAclRule(tenantName, identifier)); @@ -688,12 +685,8 @@ public class CiscoVnmcConnectionImpl implements CiscoVnmcConnection { xml = replaceXmlValue(xml, "deststartport", destStartPort); xml = replaceXmlValue(xml, "destendport", destEndPort); - List rules = listChildren(getDnForAclPolicy(tenantName, policyIdentifier)); - int order = 100; - if (rules != null) { - order += rules.size(); - } - xml = replaceXmlValue(xml, "order", Integer.toString(order)); + long order = 100 + ruleId; + xml = replaceXmlValue(xml, "order", Long.toString(order)); String response = sendRequest(service, xml); return verifySuccess(response); @@ -701,11 +694,12 @@ public class CiscoVnmcConnectionImpl implements CiscoVnmcConnection { @Override public boolean createTenantVDCIngressAclRule(String tenantName, - String identifier, String policyIdentifier, + long ruleId, String policyIdentifier, String protocol, String sourceStartIp, String sourceEndIp) throws ExecutionException { String xml = VnmcXml.CREATE_GENERIC_INGRESS_ACL_RULE.getXml(); String service = VnmcXml.CREATE_GENERIC_INGRESS_ACL_RULE.getService(); + String identifier = Long.toString(ruleId); xml = replaceXmlValue(xml, "cookie", _cookie); xml = replaceXmlValue(xml, "aclruledn", getDnForAclRule(tenantName, identifier, policyIdentifier)); xml = replaceXmlValue(xml, "aclrulename", getNameForAclRule(tenantName, identifier)); @@ -715,12 +709,8 @@ public class CiscoVnmcConnectionImpl implements CiscoVnmcConnection { xml = replaceXmlValue(xml, "sourcestartip", sourceStartIp); xml = replaceXmlValue(xml, "sourceendip", sourceEndIp); - List rules = listChildren(getDnForAclPolicy(tenantName, policyIdentifier)); - int order = 100; - if (rules != null) { - order += rules.size(); - } - xml = replaceXmlValue(xml, "order", Integer.toString(order)); + long order = 100 + ruleId; + xml = replaceXmlValue(xml, "order", Long.toString(order)); String response = sendRequest(service, xml); return verifySuccess(response); @@ -728,12 +718,13 @@ public class CiscoVnmcConnectionImpl implements CiscoVnmcConnection { @Override public boolean createTenantVDCEgressAclRule(String tenantName, - String identifier, String policyIdentifier, + long ruleId, String policyIdentifier, String protocol, String sourceStartIp, String sourceEndIp, String destStartPort, String destEndPort) throws ExecutionException { String xml = VnmcXml.CREATE_EGRESS_ACL_RULE.getXml(); String service = VnmcXml.CREATE_EGRESS_ACL_RULE.getService(); + String identifier = Long.toString(ruleId); xml = replaceXmlValue(xml, "cookie", _cookie); xml = replaceXmlValue(xml, "aclruledn", getDnForAclRule(tenantName, identifier, policyIdentifier)); xml = replaceXmlValue(xml, "aclrulename", getNameForAclRule(tenantName, identifier)); @@ -745,12 +736,8 @@ public class CiscoVnmcConnectionImpl implements CiscoVnmcConnection { xml = replaceXmlValue(xml, "deststartport", destStartPort); xml = replaceXmlValue(xml, "destendport", destEndPort); - List rules = listChildren(getDnForAclPolicy(tenantName, policyIdentifier)); - int order = 100; - if (rules != null) { - order += rules.size(); - } - xml = replaceXmlValue(xml, "order", Integer.toString(order)); + long order = 100 + ruleId; + xml = replaceXmlValue(xml, "order", Long.toString(order)); String response = sendRequest(service, xml); return verifySuccess(response); @@ -758,7 +745,7 @@ public class CiscoVnmcConnectionImpl implements CiscoVnmcConnection { @Override public boolean createTenantVDCEgressAclRule(String tenantName, - String identifier, String policyIdentifier, + long ruleId, String policyIdentifier, String protocol, String sourceStartIp, String sourceEndIp) throws ExecutionException { String xml = VnmcXml.CREATE_GENERIC_EGRESS_ACL_RULE.getXml(); String service = VnmcXml.CREATE_GENERIC_EGRESS_ACL_RULE.getService(); @@ -768,6 +755,8 @@ public class CiscoVnmcConnectionImpl implements CiscoVnmcConnection { } else { // specific protocol xml = replaceXmlValue(xml, "protocolvalue", protocol); } + + String identifier = Long.toString(ruleId); xml = replaceXmlValue(xml, "cookie", _cookie); xml = replaceXmlValue(xml, "aclruledn", getDnForAclRule(tenantName, identifier, policyIdentifier)); xml = replaceXmlValue(xml, "aclrulename", getNameForAclRule(tenantName, identifier)); @@ -776,19 +765,16 @@ public class CiscoVnmcConnectionImpl implements CiscoVnmcConnection { xml = replaceXmlValue(xml, "sourcestartip", sourceStartIp); xml = replaceXmlValue(xml, "sourceendip", sourceEndIp); - List rules = listChildren(getDnForAclPolicy(tenantName, policyIdentifier)); - int order = 100; - if (rules != null) { - order += rules.size(); - } - xml = replaceXmlValue(xml, "order", Integer.toString(order)); + long order = 100 + ruleId; + xml = replaceXmlValue(xml, "order", Long.toString(order)); String response = sendRequest(service, xml); return verifySuccess(response); } @Override - public boolean deleteTenantVDCAclRule(String tenantName, String identifier, String policyIdentifier) throws ExecutionException { + public boolean deleteTenantVDCAclRule(String tenantName, long ruleId, String policyIdentifier) throws ExecutionException { + String identifier = Long.toString(ruleId); return deleteTenantVDCRule( getDnForAclRule(tenantName, identifier, policyIdentifier), getNameForAclRule(tenantName, identifier)); @@ -1001,11 +987,13 @@ public class CiscoVnmcConnectionImpl implements CiscoVnmcConnection { @Override public boolean createTenantVDCPFRule(String tenantName, - String identifier, String policyIdentifier, + long ruleId, String policyIdentifier, String protocol, String publicIp, String startPort, String endPort) throws ExecutionException { String xml = VnmcXml.CREATE_PF_RULE.getXml(); String service = VnmcXml.CREATE_PF_RULE.getService(); + + String identifier = Long.toString(ruleId); xml = replaceXmlValue(xml, "cookie", _cookie); xml = replaceXmlValue(xml, "natruledn", getDnForPFRule(tenantName, identifier, policyIdentifier)); xml = replaceXmlValue(xml, "natrulename", getNameForPFRule(tenantName, identifier)); @@ -1017,20 +1005,16 @@ public class CiscoVnmcConnectionImpl implements CiscoVnmcConnection { xml = replaceXmlValue(xml, "endport", endPort); xml = replaceXmlValue(xml, "protocolvalue", protocol); - List rules = listChildren(getDnForPFPolicy(tenantName, policyIdentifier)); - int order = 100; - if (rules != null) { - order += rules.size(); - } - xml = replaceXmlValue(xml, "order", Integer.toString(order)); + long order = 100 + ruleId; + xml = replaceXmlValue(xml, "order", Long.toString(order)); String response = sendRequest(service, xml); return verifySuccess(response); } @Override - public boolean deleteTenantVDCPFRule(String tenantName, String identifier, - String policyIdentifier) throws ExecutionException { + public boolean deleteTenantVDCPFRule(String tenantName, long ruleId, String policyIdentifier) throws ExecutionException { + String identifier = Long.toString(ruleId); return deleteTenantVDCRule( getDnForPFRule(tenantName, identifier, policyIdentifier), getNameForPFRule(tenantName, identifier)); @@ -1038,11 +1022,13 @@ public class CiscoVnmcConnectionImpl implements CiscoVnmcConnection { @Override public boolean createTenantVDCAclRuleForPF(String tenantName, - String identifier, String policyIdentifier, String protocol, + long ruleId, String policyIdentifier, String protocol, String ipAddress, String startPort, String endPort) throws ExecutionException { String xml = VnmcXml.CREATE_ACL_RULE_FOR_PF.getXml(); String service = VnmcXml.CREATE_ACL_RULE_FOR_PF.getService(); + + String identifier = Long.toString(ruleId); xml = replaceXmlValue(xml, "cookie", _cookie); xml = replaceXmlValue(xml, "aclruledn", getDnForAclRule(tenantName, identifier, policyIdentifier)); xml = replaceXmlValue(xml, "aclrulename", getNameForAclRule(tenantName, identifier)); @@ -1053,12 +1039,8 @@ public class CiscoVnmcConnectionImpl implements CiscoVnmcConnection { xml = replaceXmlValue(xml, "startport", startPort); xml = replaceXmlValue(xml, "endport", endPort); - List rules = listChildren(getDnForAclPolicy(tenantName, policyIdentifier)); - int order = 100; - if (rules != null) { - order += rules.size(); - } - xml = replaceXmlValue(xml, "order", Integer.toString(order)); + long order = 100 + ruleId; + xml = replaceXmlValue(xml, "order", Long.toString(order)); String response = sendRequest(service, xml); return verifySuccess(response); @@ -1127,10 +1109,12 @@ public class CiscoVnmcConnectionImpl implements CiscoVnmcConnection { @Override public boolean createTenantVDCDNatRule(String tenantName, - String identifier, String policyIdentifier, String publicIp) + long ruleId, String policyIdentifier, String publicIp) throws ExecutionException { String xml = VnmcXml.CREATE_DNAT_RULE.getXml(); String service = VnmcXml.CREATE_DNAT_RULE.getService(); + + String identifier = Long.toString(ruleId); xml = replaceXmlValue(xml, "cookie", _cookie); xml = replaceXmlValue(xml, "natruledn", getDnForDNatRule(tenantName, identifier, policyIdentifier)); xml = replaceXmlValue(xml, "natrulename", getNameForDNatRule(tenantName, identifier)); @@ -1138,21 +1122,17 @@ public class CiscoVnmcConnectionImpl implements CiscoVnmcConnection { xml = replaceXmlValue(xml, "ippoolname", getNameForDNatIpPool(tenantName, identifier)); xml = replaceXmlValue(xml, "ip", publicIp); - List rules = listChildren(getDnForDNatPolicy(tenantName, policyIdentifier)); - int order = 100; - if (rules != null) { - order += rules.size(); - } - xml = replaceXmlValue(xml, "order", Integer.toString(order)); + long order = 100 + ruleId; + xml = replaceXmlValue(xml, "order", Long.toString(order)); String response = sendRequest(service, xml); return verifySuccess(response); } @Override - public boolean deleteTenantVDCDNatRule(String tenantName, - String identifier, String policyIdentifier) + public boolean deleteTenantVDCDNatRule(String tenantName, long ruleId, String policyIdentifier) throws ExecutionException { + String identifier = Long.toString(ruleId); return deleteTenantVDCRule( getDnForDNatRule(tenantName, identifier, policyIdentifier), getNameForDNatRule(tenantName, identifier)); @@ -1160,10 +1140,12 @@ public class CiscoVnmcConnectionImpl implements CiscoVnmcConnection { @Override public boolean createTenantVDCAclRuleForDNat(String tenantName, - String identifier, String policyIdentifier, String ipAddress) + long ruleId, String policyIdentifier, String ipAddress) throws ExecutionException { String xml = VnmcXml.CREATE_ACL_RULE_FOR_DNAT.getXml(); String service = VnmcXml.CREATE_ACL_RULE_FOR_DNAT.getService(); + + String identifier = Long.toString(ruleId); xml = replaceXmlValue(xml, "cookie", _cookie); xml = replaceXmlValue(xml, "aclruledn", getDnForAclRule(tenantName, identifier, policyIdentifier)); xml = replaceXmlValue(xml, "aclrulename", getNameForAclRule(tenantName, identifier)); @@ -1171,12 +1153,8 @@ public class CiscoVnmcConnectionImpl implements CiscoVnmcConnection { xml = replaceXmlValue(xml, "actiontype", "permit"); xml = replaceXmlValue(xml, "ip", ipAddress); - List rules = listChildren(getDnForAclPolicy(tenantName, policyIdentifier)); - int order = 100; - if (rules != null) { - order += rules.size(); - } - xml = replaceXmlValue(xml, "order", Integer.toString(order)); + long order = 100 + ruleId; + xml = replaceXmlValue(xml, "order", Long.toString(order)); String response = sendRequest(service, xml); return verifySuccess(response); diff --git a/plugins/network-elements/cisco-vnmc/src/com/cloud/network/resource/CiscoVnmcResource.java b/plugins/network-elements/cisco-vnmc/src/com/cloud/network/resource/CiscoVnmcResource.java index 29bbbe67a31..fc0c33483ac 100644 --- a/plugins/network-elements/cisco-vnmc/src/com/cloud/network/resource/CiscoVnmcResource.java +++ b/plugins/network-elements/cisco-vnmc/src/com/cloud/network/resource/CiscoVnmcResource.java @@ -174,7 +174,7 @@ public class CiscoVnmcResource implements ServerResource { cmd.setPod(""); cmd.setPrivateIpAddress(_ip); cmd.setStorageIpAddress(""); - cmd.setVersion(""); + cmd.setVersion(CiscoVnmcResource.class.getPackage().getImplementationVersion()); cmd.setGuid(_guid); return new StartupCommand[] { cmd }; } @@ -359,7 +359,7 @@ public class CiscoVnmcResource implements ServerResource { for (FirewallRuleTO rule : publicIpRulesMap.get(publicIp)) { if (rule.revoked()) { - if (!_connection.deleteTenantVDCAclRule(tenant, Long.toString(rule.getId()), policyIdentifier)) { + if (!_connection.deleteTenantVDCAclRule(tenant, rule.getId(), policyIdentifier)) { throw new ExecutionException("Failed to delete ACL rule in VNMC for guest network with vlan " + vlanId); } } else { @@ -368,14 +368,14 @@ public class CiscoVnmcResource implements ServerResource { if (!rule.getProtocol().equalsIgnoreCase("icmp") && rule.getSrcPortRange() != null) { if (!_connection.createTenantVDCIngressAclRule(tenant, - Long.toString(rule.getId()), policyIdentifier, + rule.getId(), policyIdentifier, rule.getProtocol().toUpperCase(), externalIpRange[0], externalIpRange[1], Integer.toString(rule.getSrcPortRange()[0]), Integer.toString(rule.getSrcPortRange()[1]))) { throw new ExecutionException("Failed to create ACL ingress rule in VNMC for guest network with vlan " + vlanId); } } else { if (!_connection.createTenantVDCIngressAclRule(tenant, - Long.toString(rule.getId()), policyIdentifier, + rule.getId(), policyIdentifier, rule.getProtocol().toUpperCase(), externalIpRange[0], externalIpRange[1])) { throw new ExecutionException("Failed to create ACL ingress rule in VNMC for guest network with vlan " + vlanId); } @@ -384,7 +384,7 @@ public class CiscoVnmcResource implements ServerResource { if ((rule.getProtocol().equalsIgnoreCase("tcp") || rule.getProtocol().equalsIgnoreCase("udp")) && rule.getSrcPortRange() != null) { if (!_connection.createTenantVDCEgressAclRule(tenant, - Long.toString(rule.getId()), policyIdentifier, + rule.getId(), policyIdentifier, rule.getProtocol().toUpperCase(), externalIpRange[0], externalIpRange[1], Integer.toString(rule.getSrcPortRange()[0]), Integer.toString(rule.getSrcPortRange()[1]))) { @@ -392,7 +392,7 @@ public class CiscoVnmcResource implements ServerResource { } } else { if (!_connection.createTenantVDCEgressAclRule(tenant, - Long.toString(rule.getId()), policyIdentifier, + rule.getId(), policyIdentifier, rule.getProtocol().toUpperCase(), externalIpRange[0], externalIpRange[1])) { throw new ExecutionException("Failed to create ACL egress rule in VNMC for guest network with vlan " + vlanId); } @@ -472,7 +472,7 @@ public class CiscoVnmcResource implements ServerResource { for (StaticNatRuleTO rule : publicIpRulesMap.get(publicIp)) { if (rule.revoked()) { - if (!_connection.deleteTenantVDCDNatRule(tenant, Long.toString(rule.getId()), policyIdentifier)) { + if (!_connection.deleteTenantVDCDNatRule(tenant, rule.getId(), policyIdentifier)) { throw new ExecutionException("Failed to delete DNAT rule in VNMC for guest network with vlan " + vlanId); } } else { @@ -481,7 +481,7 @@ public class CiscoVnmcResource implements ServerResource { } if (!_connection.createTenantVDCDNatRule(tenant, - Long.toString(rule.getId()), policyIdentifier, rule.getSrcIp())) { + rule.getId(), policyIdentifier, rule.getSrcIp())) { throw new ExecutionException("Failed to create DNAT rule in VNMC for guest network with vlan " + vlanId); } } @@ -558,7 +558,7 @@ public class CiscoVnmcResource implements ServerResource { for (PortForwardingRuleTO rule : publicIpRulesMap.get(publicIp)) { if (rule.revoked()) { - if (!_connection.deleteTenantVDCPFRule(tenant, Long.toString(rule.getId()), policyIdentifier)) { + if (!_connection.deleteTenantVDCPFRule(tenant, rule.getId(), policyIdentifier)) { throw new ExecutionException("Failed to delete PF rule in VNMC for guest network with vlan " + vlanId); } } else { @@ -571,7 +571,7 @@ public class CiscoVnmcResource implements ServerResource { } if (!_connection.createTenantVDCPFRule(tenant, - Long.toString(rule.getId()), policyIdentifier, + rule.getId(), policyIdentifier, rule.getProtocol().toUpperCase(), rule.getSrcIp(), Integer.toString(rule.getSrcPortRange()[0]), Integer.toString(rule.getSrcPortRange()[1]))) { throw new ExecutionException("Failed to create PF rule in VNMC for guest network with vlan " + vlanId); diff --git a/plugins/network-elements/cisco-vnmc/test/com/cloud/network/resource/CiscoVnmcResourceTest.java b/plugins/network-elements/cisco-vnmc/test/com/cloud/network/resource/CiscoVnmcResourceTest.java index f1942ea5fe5..b3821d78bd5 100755 --- a/plugins/network-elements/cisco-vnmc/test/com/cloud/network/resource/CiscoVnmcResourceTest.java +++ b/plugins/network-elements/cisco-vnmc/test/com/cloud/network/resource/CiscoVnmcResourceTest.java @@ -163,13 +163,13 @@ public class CiscoVnmcResourceTest { when(_connection.createTenantVDCAclPolicySet(anyString(), anyBoolean())).thenReturn(true); when(_connection.createTenantVDCAclPolicy(anyString(), anyString())).thenReturn(true); when(_connection.createTenantVDCAclPolicyRef(anyString(), anyString(), anyBoolean())).thenReturn(true); - when(_connection.deleteTenantVDCAclRule(anyString(), anyString(), anyString())).thenReturn(true); + when(_connection.deleteTenantVDCAclRule(anyString(), anyLong(), anyString())).thenReturn(true); when(_connection.createTenantVDCIngressAclRule( - anyString(), anyString(), anyString(), + anyString(), anyLong(), anyString(), anyString(), anyString(), anyString(), anyString(), anyString())).thenReturn(true); when(_connection.createTenantVDCEgressAclRule( - anyString(), anyString(), anyString(), + anyString(), anyLong(), anyString(), anyString(), anyString(), anyString(), anyString(), anyString())).thenReturn(true); when(_connection.associateAclPolicySet(anyString())).thenReturn(true); @@ -201,13 +201,13 @@ public class CiscoVnmcResourceTest { when(_connection.createTenantVDCDNatPolicyRef(anyString(), anyString())).thenReturn(true); when(_connection.createTenantVDCAclPolicy(anyString(), anyString())).thenReturn(true); when(_connection.createTenantVDCAclPolicyRef(anyString(), anyString(), anyBoolean())).thenReturn(true); - when(_connection.deleteTenantVDCDNatRule(anyString(), anyString(), anyString())).thenReturn(true); - when(_connection.deleteTenantVDCAclRule(anyString(), anyString(), anyString())).thenReturn(true); + when(_connection.deleteTenantVDCDNatRule(anyString(), anyLong(), anyString())).thenReturn(true); + when(_connection.deleteTenantVDCAclRule(anyString(), anyLong(), anyString())).thenReturn(true); when(_connection.createTenantVDCDNatIpPool(anyString(), anyString(), anyString())).thenReturn(true); when(_connection.createTenantVDCDNatRule(anyString(), - anyString(), anyString(), anyString())).thenReturn(true); + anyLong(), anyString(), anyString())).thenReturn(true); when(_connection.createTenantVDCAclRuleForDNat(anyString(), - anyString(), anyString(), anyString())).thenReturn(true); + anyLong(), anyString(), anyString())).thenReturn(true); when(_connection.associateAclPolicySet(anyString())).thenReturn(true); Answer answer = _resource.executeRequest(cmd); @@ -237,15 +237,15 @@ public class CiscoVnmcResourceTest { when(_connection.createTenantVDCPFPolicyRef(anyString(), anyString())).thenReturn(true); when(_connection.createTenantVDCAclPolicy(anyString(), anyString())).thenReturn(true); when(_connection.createTenantVDCAclPolicyRef(anyString(), anyString(), anyBoolean())).thenReturn(true); - when(_connection.deleteTenantVDCPFRule(anyString(), anyString(), anyString())).thenReturn(true); - when(_connection.deleteTenantVDCAclRule(anyString(), anyString(), anyString())).thenReturn(true); + when(_connection.deleteTenantVDCPFRule(anyString(), anyLong(), anyString())).thenReturn(true); + when(_connection.deleteTenantVDCAclRule(anyString(), anyLong(), anyString())).thenReturn(true); when(_connection.createTenantVDCPFIpPool(anyString(), anyString(), anyString())).thenReturn(true); when(_connection.createTenantVDCPFPortPool(anyString(), anyString(), anyString(), anyString())).thenReturn(true); when(_connection.createTenantVDCPFRule(anyString(), - anyString(), anyString(), anyString(), + anyLong(), anyString(), anyString(), anyString(), anyString(), anyString())).thenReturn(true); when(_connection.createTenantVDCAclRuleForPF(anyString(), - anyString(), anyString(), anyString(), + anyLong(), anyString(), anyString(), anyString(), anyString(), anyString())).thenReturn(true); when(_connection.associateAclPolicySet(anyString())).thenReturn(true); From 4e090796408152af5c06ce72f346ee9ea5d4d404 Mon Sep 17 00:00:00 2001 From: Hugo Trippaers Date: Fri, 17 May 2013 13:02:33 +0200 Subject: [PATCH 071/108] Update the Logical Router NatRules to be compatible with the NVP 3.x.x platform Use the Gson adapters to serialize/deserialize the NatRules Switch the NiciraNvpApi to a single gson Object with the proper adapters Fix missing order setting for static nat rules and portforwarding rules Return an error when a port range is passed in a portforwarding rule The serializer is not required Fix a bug where an ip address could be released even if it was still in use for SourceNat Throw a json parse exception when the type is unknown to the adapter --- .../network/nicira/DestinationNatRule.java | 89 +++++ .../src/com/cloud/network/nicira/Match.java | 148 +------ .../src/com/cloud/network/nicira/NatRule.java | 363 +++++------------- .../cloud/network/nicira/NiciraNvpApi.java | 67 +++- .../cloud/network/nicira/SourceNatRule.java | 107 ++++++ .../network/resource/NiciraNvpResource.java | 57 ++- .../com/cloud/network/nicira/NatRuleTest.java | 43 ++- .../resource/NiciraNvpResourceTest.java | 111 +++--- .../com/cloud/network/NetworkModelImpl.java | 8 +- 9 files changed, 493 insertions(+), 500 deletions(-) create mode 100644 plugins/network-elements/nicira-nvp/src/com/cloud/network/nicira/DestinationNatRule.java create mode 100644 plugins/network-elements/nicira-nvp/src/com/cloud/network/nicira/SourceNatRule.java diff --git a/plugins/network-elements/nicira-nvp/src/com/cloud/network/nicira/DestinationNatRule.java b/plugins/network-elements/nicira-nvp/src/com/cloud/network/nicira/DestinationNatRule.java new file mode 100644 index 00000000000..48310417b72 --- /dev/null +++ b/plugins/network-elements/nicira-nvp/src/com/cloud/network/nicira/DestinationNatRule.java @@ -0,0 +1,89 @@ +package com.cloud.network.nicira; + +public class DestinationNatRule extends NatRule { + private String toDestinationIpAddress; + private Integer toDestinationPort; + + public DestinationNatRule() { + setType("DestinationNatRule"); + } + + public String getToDestinationIpAddress() { + return toDestinationIpAddress; + } + + + public void setToDestinationIpAddress(String toDestinationIpAddress) { + this.toDestinationIpAddress = toDestinationIpAddress; + } + + + public Integer getToDestinationPort() { + return toDestinationPort; + } + + + public void setToDestinationPort(Integer toDestinationPort) { + this.toDestinationPort = toDestinationPort; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime + * result + + ((toDestinationIpAddress == null) ? 0 + : toDestinationIpAddress.hashCode()); + result = prime + * result + + ((toDestinationPort == null) ? 0 : toDestinationPort + .hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + DestinationNatRule other = (DestinationNatRule) obj; + if (toDestinationIpAddress == null) { + if (other.toDestinationIpAddress != null) + return false; + } else if (!toDestinationIpAddress.equals(other.toDestinationIpAddress)) + return false; + if (toDestinationPort == null) { + if (other.toDestinationPort != null) + return false; + } else if (!toDestinationPort.equals(other.toDestinationPort)) + return false; + return true; + } + + @Override + public boolean equalsIgnoreUuid(Object obj) { + if (this == obj) + return true; + if (!super.equalsIgnoreUuid(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + DestinationNatRule other = (DestinationNatRule) obj; + if (toDestinationIpAddress == null) { + if (other.toDestinationIpAddress != null) + return false; + } else if (!toDestinationIpAddress.equals(other.toDestinationIpAddress)) + return false; + if (toDestinationPort == null) { + if (other.toDestinationPort != null) + return false; + } else if (!toDestinationPort.equals(other.toDestinationPort)) + return false; + return true; + } + +} diff --git a/plugins/network-elements/nicira-nvp/src/com/cloud/network/nicira/Match.java b/plugins/network-elements/nicira-nvp/src/com/cloud/network/nicira/Match.java index 0c4e677536c..f7777822a06 100644 --- a/plugins/network-elements/nicira-nvp/src/com/cloud/network/nicira/Match.java +++ b/plugins/network-elements/nicira-nvp/src/com/cloud/network/nicira/Match.java @@ -22,15 +22,9 @@ package com.cloud.network.nicira; public class Match { private Integer protocol; private String source_ip_addresses; - private Boolean source_ip_addresses_not; private String destination_ip_addresses; - private Boolean destination_ip_addresses_not; - private Integer source_port_min; - private Integer source_port_max; - private Boolean source_port_not; - private Integer destination_port_min; - private Integer destination_port_max; - private Boolean destination_port_not; + private Integer source_port; + private Integer destination_port; private String ethertype = "IPv4"; public Integer getProtocol() { @@ -41,54 +35,22 @@ public class Match { this.protocol = protocol; } - public Integer getSourcePortMin() { - return source_port_min; + public Integer getSourcePort() { + return source_port; } - public void setSourcePortMin(Integer source_port_min) { - this.source_port_min = source_port_min; + public void setSourcePort(Integer source_port) { + this.source_port = source_port; + } + + public Integer getDestinationPort() { + return destination_port; } - public Integer getSourcePortMax() { - return source_port_max; + public void setDestinationPort(Integer destination_port) { + this.destination_port = destination_port; } - - public void setSourcePortMax(Integer source_port_max) { - this.source_port_max = source_port_max; - } - - public Boolean isSourcePortNot() { - return source_port_not; - } - - public void setSourcePortNot(Boolean source_port_not) { - this.source_port_not = source_port_not; - } - - public Integer getDestinationPortMin() { - return destination_port_min; - } - - public void setDestinationPortMin(Integer destination_port_min) { - this.destination_port_min = destination_port_min; - } - - public Integer getDestinationPortMax() { - return destination_port_max; - } - - public void setDestinationPortMax(Integer destination_port_max) { - this.destination_port_max = destination_port_max; - } - - public Boolean isDestinationPortNot() { - return destination_port_not; - } - - public void setDestinationPortNot(Boolean destination_port_not) { - this.destination_port_not = destination_port_not; - } - + public String getEthertype() { return ethertype; } @@ -105,14 +67,6 @@ public class Match { this.source_ip_addresses = source_ip_addresses; } - public boolean isSourceIpAddressesNot() { - return source_ip_addresses_not; - } - - public void setSourceIpAddresses_not(boolean source_ip_addresses_not) { - this.source_ip_addresses_not = source_ip_addresses_not; - } - public String getDestinationIpAddresses() { return destination_ip_addresses; } @@ -121,14 +75,6 @@ public class Match { this.destination_ip_addresses = destination_ip_addresses; } - public Boolean isDestinationIpAddressesNot() { - return destination_ip_addresses_not; - } - - public void setDestinationIpAddressesNot(Boolean destination_ip_addresses_not) { - this.destination_ip_addresses_not = destination_ip_addresses_not; - } - @Override public int hashCode() { final int prime = 31; @@ -139,19 +85,7 @@ public class Match { : destination_ip_addresses.hashCode()); result = prime * result - + ((destination_ip_addresses_not == null) ? 0 - : destination_ip_addresses_not.hashCode()); - result = prime - * result - + ((destination_port_max == null) ? 0 : destination_port_max - .hashCode()); - result = prime - * result - + ((destination_port_min == null) ? 0 : destination_port_min - .hashCode()); - result = prime - * result - + ((destination_port_not == null) ? 0 : destination_port_not + + ((destination_port == null) ? 0 : destination_port .hashCode()); result = prime * result + ((ethertype == null) ? 0 : ethertype.hashCode()); @@ -161,16 +95,8 @@ public class Match { * result + ((source_ip_addresses == null) ? 0 : source_ip_addresses .hashCode()); - result = prime - * result - + ((source_ip_addresses_not == null) ? 0 - : source_ip_addresses_not.hashCode()); result = prime * result - + ((source_port_max == null) ? 0 : source_port_max.hashCode()); - result = prime * result - + ((source_port_min == null) ? 0 : source_port_min.hashCode()); - result = prime * result - + ((source_port_not == null) ? 0 : source_port_not.hashCode()); + + ((source_port == null) ? 0 : source_port.hashCode()); return result; } @@ -189,26 +115,10 @@ public class Match { } else if (!destination_ip_addresses .equals(other.destination_ip_addresses)) return false; - if (destination_ip_addresses_not == null) { - if (other.destination_ip_addresses_not != null) + if (destination_port == null) { + if (other.destination_port != null) return false; - } else if (!destination_ip_addresses_not - .equals(other.destination_ip_addresses_not)) - return false; - if (destination_port_max == null) { - if (other.destination_port_max != null) - return false; - } else if (!destination_port_max.equals(other.destination_port_max)) - return false; - if (destination_port_min == null) { - if (other.destination_port_min != null) - return false; - } else if (!destination_port_min.equals(other.destination_port_min)) - return false; - if (destination_port_not == null) { - if (other.destination_port_not != null) - return false; - } else if (!destination_port_not.equals(other.destination_port_not)) + } else if (!destination_port.equals(other.destination_port)) return false; if (ethertype == null) { if (other.ethertype != null) @@ -225,26 +135,10 @@ public class Match { return false; } else if (!source_ip_addresses.equals(other.source_ip_addresses)) return false; - if (source_ip_addresses_not == null) { - if (other.source_ip_addresses_not != null) + if (source_port == null) { + if (other.source_port != null) return false; - } else if (!source_ip_addresses_not - .equals(other.source_ip_addresses_not)) - return false; - if (source_port_max == null) { - if (other.source_port_max != null) - return false; - } else if (!source_port_max.equals(other.source_port_max)) - return false; - if (source_port_min == null) { - if (other.source_port_min != null) - return false; - } else if (!source_port_min.equals(other.source_port_min)) - return false; - if (source_port_not == null) { - if (other.source_port_not != null) - return false; - } else if (!source_port_not.equals(other.source_port_not)) + } else if (!source_port.equals(other.source_port)) return false; return true; } diff --git a/plugins/network-elements/nicira-nvp/src/com/cloud/network/nicira/NatRule.java b/plugins/network-elements/nicira-nvp/src/com/cloud/network/nicira/NatRule.java index b66ffa89b77..93de51e45ac 100644 --- a/plugins/network-elements/nicira-nvp/src/com/cloud/network/nicira/NatRule.java +++ b/plugins/network-elements/nicira-nvp/src/com/cloud/network/nicira/NatRule.java @@ -16,267 +16,114 @@ // under the License. package com.cloud.network.nicira; +import java.util.UUID; + /** * */ -public class NatRule { - protected Match match; - protected String to_source_ip_address_min; - protected String to_source_ip_address_max; - protected Integer to_source_port_min; - protected Integer to_source_port_max; - protected String uuid; - protected String type; - protected String to_destination_ip_address_min; - protected String to_destination_ip_address_max; - protected Integer to_destination_port; - - public NatRule() {} - - public Match getMatch() { - return match; - } - - public void setMatch(Match match) { - this.match = match; - } - - public String getToSourceIpAddressMin() { - return to_source_ip_address_min; - } - - public void setToSourceIpAddressMin(String to_source_ip_address_min) { - this.to_source_ip_address_min = to_source_ip_address_min; - } - - public String getToSourceIpAddressMax() { - return to_source_ip_address_max; - } - - public void setToSourceIpAddressMax(String to_source_ip_address_max) { - this.to_source_ip_address_max = to_source_ip_address_max; - } - - public Integer getToSourcePortMin() { - return to_source_port_min; - } - - public void setToSourcePortMin(Integer to_source_port_min) { - this.to_source_port_min = to_source_port_min; - } - - public Integer getToSourcePortMax() { - return to_source_port_max; - } - - public void setToSourcePortMax(Integer to_source_port_max) { - this.to_source_port_max = to_source_port_max; - } - - public String getUuid() { - return uuid; - } - - public void setUuid(String uuid) { - this.uuid = uuid; - } - - public String getToDestinationIpAddressMin() { - return to_destination_ip_address_min; - } - - public void setToDestinationIpAddressMin( - String to_destination_ip_address_min) { - this.to_destination_ip_address_min = to_destination_ip_address_min; - } - - public String getToDestinationIpAddressMax() { - return to_destination_ip_address_max; - } - - public void setToDestinationIpAddressMax( - String to_destination_ip_address_max) { - this.to_destination_ip_address_max = to_destination_ip_address_max; - } - - public Integer getToDestinationPort() { - return to_destination_port; - } - - public void setToDestinationPort(Integer to_destination_port) { - this.to_destination_port = to_destination_port; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } +public abstract class NatRule { + protected Match match; + protected UUID uuid; + protected String type; + protected int order; - @Override - public int hashCode() { - final int prime = 42; - int result = 1; - result = prime * result + ((match == null) ? 0 : match.hashCode()); - result = prime - * result - + ((to_destination_ip_address_max == null) ? 0 - : to_destination_ip_address_max.hashCode()); - result = prime - * result - + ((to_destination_ip_address_min == null) ? 0 - : to_destination_ip_address_min.hashCode()); - result = prime - * result - + ((to_destination_port == null) ? 0 : to_destination_port - .hashCode()); - result = prime - * result - + ((to_source_ip_address_max == null) ? 0 - : to_source_ip_address_max.hashCode()); - result = prime - * result - + ((to_source_ip_address_min == null) ? 0 - : to_source_ip_address_min.hashCode()); - result = prime - * result - + ((to_source_port_max == null) ? 0 : to_source_port_max - .hashCode()); - result = prime - * result - + ((to_source_port_min == null) ? 0 : to_source_port_min - .hashCode()); - result = prime * result + ((type == null) ? 0 : type.hashCode()); - result = prime * result + ((uuid == null) ? 0 : uuid.hashCode()); - return result; - } + public NatRule() { + } + + public Match getMatch() { + return match; + } + + public void setMatch(Match match) { + this.match = match; + } + + public UUID getUuid() { + return uuid; + } + + public void setUuid(UUID uuid) { + this.uuid = uuid; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public int getOrder() { + return order; + } + + public void setOrder(int order) { + this.order = order; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((match == null) ? 0 : match.hashCode()); + result = prime * result + order; + result = prime * result + ((type == null) ? 0 : type.hashCode()); + result = prime * result + ((uuid == null) ? 0 : uuid.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + NatRule other = (NatRule) obj; + if (match == null) { + if (other.match != null) + return false; + } else if (!match.equals(other.match)) + return false; + if (order != other.order) + return false; + if (type == null) { + if (other.type != null) + return false; + } else if (!type.equals(other.type)) + return false; + if (uuid == null) { + if (other.uuid != null) + return false; + } else if (!uuid.equals(other.uuid)) + return false; + return true; + } + + public boolean equalsIgnoreUuid(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + NatRule other = (NatRule) obj; + if (match == null) { + if (other.match != null) + return false; + } else if (!match.equals(other.match)) + return false; + if (order != other.order) + return false; + if (type == null) { + if (other.type != null) + return false; + } else if (!type.equals(other.type)) + return false; + return true; + } + - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - NatRule other = (NatRule) obj; - if (match == null) { - if (other.match != null) - return false; - } else if (!match.equals(other.match)) - return false; - if (to_destination_ip_address_max == null) { - if (other.to_destination_ip_address_max != null) - return false; - } else if (!to_destination_ip_address_max - .equals(other.to_destination_ip_address_max)) - return false; - if (to_destination_ip_address_min == null) { - if (other.to_destination_ip_address_min != null) - return false; - } else if (!to_destination_ip_address_min - .equals(other.to_destination_ip_address_min)) - return false; - if (to_destination_port == null) { - if (other.to_destination_port != null) - return false; - } else if (!to_destination_port.equals(other.to_destination_port)) - return false; - if (to_source_ip_address_max == null) { - if (other.to_source_ip_address_max != null) - return false; - } else if (!to_source_ip_address_max - .equals(other.to_source_ip_address_max)) - return false; - if (to_source_ip_address_min == null) { - if (other.to_source_ip_address_min != null) - return false; - } else if (!to_source_ip_address_min - .equals(other.to_source_ip_address_min)) - return false; - if (to_source_port_max == null) { - if (other.to_source_port_max != null) - return false; - } else if (!to_source_port_max.equals(other.to_source_port_max)) - return false; - if (to_source_port_min == null) { - if (other.to_source_port_min != null) - return false; - } else if (!to_source_port_min.equals(other.to_source_port_min)) - return false; - if (type == null) { - if (other.type != null) - return false; - } else if (!type.equals(other.type)) - return false; - if (uuid == null) { - if (other.uuid != null) - return false; - } else if (!uuid.equals(other.uuid)) - return false; - return true; - } - - public boolean equalsIgnoreUuid(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - NatRule other = (NatRule) obj; - if (match == null) { - if (other.match != null) - return false; - } else if (!match.equals(other.match)) - return false; - if (to_destination_ip_address_max == null) { - if (other.to_destination_ip_address_max != null) - return false; - } else if (!to_destination_ip_address_max - .equals(other.to_destination_ip_address_max)) - return false; - if (to_destination_ip_address_min == null) { - if (other.to_destination_ip_address_min != null) - return false; - } else if (!to_destination_ip_address_min - .equals(other.to_destination_ip_address_min)) - return false; - if (to_destination_port == null) { - if (other.to_destination_port != null) - return false; - } else if (!to_destination_port.equals(other.to_destination_port)) - return false; - if (to_source_ip_address_max == null) { - if (other.to_source_ip_address_max != null) - return false; - } else if (!to_source_ip_address_max - .equals(other.to_source_ip_address_max)) - return false; - if (to_source_ip_address_min == null) { - if (other.to_source_ip_address_min != null) - return false; - } else if (!to_source_ip_address_min - .equals(other.to_source_ip_address_min)) - return false; - if (to_source_port_max == null) { - if (other.to_source_port_max != null) - return false; - } else if (!to_source_port_max.equals(other.to_source_port_max)) - return false; - if (to_source_port_min == null) { - if (other.to_source_port_min != null) - return false; - } else if (!to_source_port_min.equals(other.to_source_port_min)) - return false; - if (type == null) { - if (other.type != null) - return false; - } else if (!type.equals(other.type)) - return false; - return true; - } - } diff --git a/plugins/network-elements/nicira-nvp/src/com/cloud/network/nicira/NiciraNvpApi.java b/plugins/network-elements/nicira-nvp/src/com/cloud/network/nicira/NiciraNvpApi.java index 039c174be2d..12fa6c0b67c 100644 --- a/plugins/network-elements/nicira-nvp/src/com/cloud/network/nicira/NiciraNvpApi.java +++ b/plugins/network-elements/nicira-nvp/src/com/cloud/network/nicira/NiciraNvpApi.java @@ -34,6 +34,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.UUID; + import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; @@ -41,7 +43,6 @@ import javax.net.ssl.X509TrustManager; import org.apache.commons.httpclient.ConnectTimeoutException; import org.apache.commons.httpclient.HttpClient; -import org.apache.commons.httpclient.HttpConnectionManager; import org.apache.commons.httpclient.HttpException; import org.apache.commons.httpclient.HttpMethod; import org.apache.commons.httpclient.HttpMethodBase; @@ -59,7 +60,15 @@ import org.apache.commons.httpclient.protocol.Protocol; import org.apache.commons.httpclient.protocol.ProtocolSocketFactory; import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory; import org.apache.log4j.Logger; + +import com.google.gson.FieldNamingPolicy; import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; import com.google.gson.reflect.TypeToken; public class NiciraNvpApi { @@ -73,6 +82,9 @@ public class NiciraNvpApi { private String _adminpass; private HttpClient _client; + private String _nvpversion; + + private Gson _gson; /* This factory method is protected so we can extend this * in the unittests. @@ -118,6 +130,11 @@ public class NiciraNvpApi { s_logger.warn("Failed to register the TrustingProtocolSocketFactory, falling back to default SSLSocketFactory", e); } + _gson = new GsonBuilder() + .registerTypeAdapter(NatRule.class, new NatRuleAdapter()) + .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .create(); + } public void setControllerAddress(String address) { @@ -170,6 +187,12 @@ public class NiciraNvpApi { throw new NiciraNvpApiException("Nicira NVP API login failed " + pm.getStatusText()); } + // Extract the version for later use + if (pm.getResponseHeader("Server") != null) { + _nvpversion = pm.getResponseHeader("Server").getValue(); + s_logger.debug("NVP Controller reports version " + _nvpversion); + } + // Success; the cookie required for login is kept in _client } @@ -290,8 +313,8 @@ public class NiciraNvpApi { executeUpdateObject(natRule, uri, Collections.emptyMap()); } - public void deleteLogicalRouterNatRule(String logicalRouterUuid, String natRuleUuid) throws NiciraNvpApiException { - String uri = "/ws.v1/lrouter/" + logicalRouterUuid + "/nat/" + natRuleUuid; + public void deleteLogicalRouterNatRule(String logicalRouterUuid, UUID natRuleUuid) throws NiciraNvpApiException { + String uri = "/ws.v1/lrouter/" + logicalRouterUuid + "/nat/" + natRuleUuid.toString(); executeDeleteObject(uri); } @@ -342,13 +365,11 @@ public class NiciraNvpApi { throw new NiciraNvpApiException("Hostname/credentials are null or empty"); } - Gson gson = new Gson(); - PutMethod pm = (PutMethod) createMethod("put", uri); pm.setRequestHeader("Content-Type", "application/json"); try { pm.setRequestEntity(new StringRequestEntity( - gson.toJson(newObject),"application/json", null)); + _gson.toJson(newObject),"application/json", null)); } catch (UnsupportedEncodingException e) { throw new NiciraNvpApiException("Failed to encode json request body", e); } @@ -371,13 +392,11 @@ public class NiciraNvpApi { throw new NiciraNvpApiException("Hostname/credentials are null or empty"); } - Gson gson = new Gson(); - PostMethod pm = (PostMethod) createMethod("post", uri); pm.setRequestHeader("Content-Type", "application/json"); try { pm.setRequestEntity(new StringRequestEntity( - gson.toJson(newObject),"application/json", null)); + _gson.toJson(newObject),"application/json", null)); } catch (UnsupportedEncodingException e) { throw new NiciraNvpApiException("Failed to encode json request body", e); } @@ -393,7 +412,7 @@ public class NiciraNvpApi { T result; try { - result = (T)gson.fromJson(pm.getResponseBodyAsString(), TypeToken.get(newObject.getClass()).getType()); + result = (T)_gson.fromJson(pm.getResponseBodyAsString(), TypeToken.get(newObject.getClass()).getType()); } catch (IOException e) { throw new NiciraNvpApiException("Failed to decode json response body", e); } finally { @@ -450,10 +469,9 @@ public class NiciraNvpApi { throw new NiciraNvpApiException("Failed to retrieve object : " + errorMessage); } - Gson gson = new Gson(); T returnValue; try { - returnValue = (T)gson.fromJson(gm.getResponseBodyAsString(), returnObjectType); + returnValue = (T)_gson.fromJson(gm.getResponseBodyAsString(), returnObjectType); } catch (IOException e) { s_logger.error("IOException while retrieving response body",e); throw new NiciraNvpApiException(e); @@ -577,5 +595,28 @@ public class NiciraNvpApi { } - + public static class NatRuleAdapter implements JsonDeserializer { + + @Override + public NatRule deserialize(JsonElement jsonElement, Type type, + JsonDeserializationContext context) throws JsonParseException { + JsonObject jsonObject = jsonElement.getAsJsonObject(); + NatRule natRule = null; + + if (!jsonObject.has("type")) { + throw new JsonParseException("Deserializing as a NatRule, but no type present in the json object"); + } + + String natRuleType = jsonObject.get("type").getAsString(); + if ("SourceNatRule".equals(natRuleType)) { + return context.deserialize(jsonElement, SourceNatRule.class); + } + else if ("DestinationNatRule".equals(natRuleType)) { + return context.deserialize(jsonElement, DestinationNatRule.class); + } + + throw new JsonParseException("Failed to deserialize type \"" + natRuleType + "\""); + } + + } } diff --git a/plugins/network-elements/nicira-nvp/src/com/cloud/network/nicira/SourceNatRule.java b/plugins/network-elements/nicira-nvp/src/com/cloud/network/nicira/SourceNatRule.java new file mode 100644 index 00000000000..5f85ccbc579 --- /dev/null +++ b/plugins/network-elements/nicira-nvp/src/com/cloud/network/nicira/SourceNatRule.java @@ -0,0 +1,107 @@ +package com.cloud.network.nicira; + +public class SourceNatRule extends NatRule { + private String toSourceIpAddressMax; + private String toSourceIpAddressMin; + private Integer toSourcePort; + + public SourceNatRule() { + setType("SourceNatRule"); + } + + public String getToSourceIpAddressMax() { + return toSourceIpAddressMax; + } + + public void setToSourceIpAddressMax(String toSourceIpAddressMax) { + this.toSourceIpAddressMax = toSourceIpAddressMax; + } + + public String getToSourceIpAddressMin() { + return toSourceIpAddressMin; + } + + public void setToSourceIpAddressMin(String toSourceIpAddressMin) { + this.toSourceIpAddressMin = toSourceIpAddressMin; + } + + public Integer getToSourcePort() { + return toSourcePort; + } + + public void setToSourcePort(Integer toSourcePort) { + this.toSourcePort = toSourcePort; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime + * result + + ((toSourceIpAddressMax == null) ? 0 : toSourceIpAddressMax + .hashCode()); + result = prime + * result + + ((toSourceIpAddressMin == null) ? 0 : toSourceIpAddressMin + .hashCode()); + result = prime * result + + ((toSourcePort == null) ? 0 : toSourcePort.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + SourceNatRule other = (SourceNatRule) obj; + if (toSourceIpAddressMax == null) { + if (other.toSourceIpAddressMax != null) + return false; + } else if (!toSourceIpAddressMax.equals(other.toSourceIpAddressMax)) + return false; + if (toSourceIpAddressMin == null) { + if (other.toSourceIpAddressMin != null) + return false; + } else if (!toSourceIpAddressMin.equals(other.toSourceIpAddressMin)) + return false; + if (toSourcePort == null) { + if (other.toSourcePort != null) + return false; + } else if (!toSourcePort.equals(other.toSourcePort)) + return false; + return true; + } + + @Override + public boolean equalsIgnoreUuid(Object obj) { + if (this == obj) + return true; + if (!super.equalsIgnoreUuid(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + SourceNatRule other = (SourceNatRule) obj; + if (toSourceIpAddressMax == null) { + if (other.toSourceIpAddressMax != null) + return false; + } else if (!toSourceIpAddressMax.equals(other.toSourceIpAddressMax)) + return false; + if (toSourceIpAddressMin == null) { + if (other.toSourceIpAddressMin != null) + return false; + } else if (!toSourceIpAddressMin.equals(other.toSourceIpAddressMin)) + return false; + if (toSourcePort == null) { + if (other.toSourcePort != null) + return false; + } else if (!toSourcePort.equals(other.toSourcePort)) + return false; + return true; + } + +} diff --git a/plugins/network-elements/nicira-nvp/src/com/cloud/network/resource/NiciraNvpResource.java b/plugins/network-elements/nicira-nvp/src/com/cloud/network/resource/NiciraNvpResource.java index 6a9a0060387..114c9395d15 100644 --- a/plugins/network-elements/nicira-nvp/src/com/cloud/network/resource/NiciraNvpResource.java +++ b/plugins/network-elements/nicira-nvp/src/com/cloud/network/resource/NiciraNvpResource.java @@ -61,6 +61,7 @@ import com.cloud.agent.api.to.StaticNatRuleTO; import com.cloud.host.Host; import com.cloud.host.Host.Type; import com.cloud.network.nicira.ControlClusterStatus; +import com.cloud.network.nicira.DestinationNatRule; import com.cloud.network.nicira.L3GatewayAttachment; import com.cloud.network.nicira.LogicalRouterConfig; import com.cloud.network.nicira.LogicalRouterPort; @@ -75,6 +76,7 @@ import com.cloud.network.nicira.NiciraNvpTag; import com.cloud.network.nicira.PatchAttachment; import com.cloud.network.nicira.RouterNextHop; import com.cloud.network.nicira.SingleDefaultRouteImplictRoutingConfig; +import com.cloud.network.nicira.SourceNatRule; import com.cloud.network.nicira.TransportZoneBinding; import com.cloud.network.nicira.VifAttachment; import com.cloud.resource.ServerResource; @@ -449,13 +451,13 @@ public class NiciraNvpResource implements ServerResource { new PatchAttachment(lrpi.getUuid())); // Setup the source nat rule - NatRule snr = new NatRule(); - snr.setType("SourceNatRule"); + SourceNatRule snr = new SourceNatRule(); snr.setToSourceIpAddressMin(publicNetworkIpAddress.split("/")[0]); snr.setToSourceIpAddressMax(publicNetworkIpAddress.split("/")[0]); Match match = new Match(); match.setSourceIpAddresses(internalNetworkAddress); snr.setMatch(match); + snr.setOrder(200); _niciraNvpApi.createLogicalRouterNatRule(lrc.getUuid(), snr); } catch (NiciraNvpApiException e) { // We need to destroy the router if we already created it @@ -604,7 +606,8 @@ public class NiciraNvpResource implements ServerResource { continue; } - if (rule.getDstPortRange()[0] != rule.getDstPortRange()[1]) { + if (rule.getDstPortRange()[0] != rule.getDstPortRange()[1] || + rule.getSrcPortRange()[0] != rule.getSrcPortRange()[1] ) { return new ConfigurePortForwardingRulesOnLogicalRouterAnswer(cmd, false, "Nicira NVP doesn't support port ranges for port forwarding"); } @@ -700,32 +703,24 @@ public class NiciraNvpResource implements ServerResource { natRuleStr.append(" "); natRuleStr.append(m.getSourceIpAddresses()); natRuleStr.append(" ["); - natRuleStr.append(m.getSourcePortMin()); - natRuleStr.append("-"); - natRuleStr.append(m.getSourcePortMax()); + natRuleStr.append(m.getSourcePort()); natRuleStr.append(" ] -> "); natRuleStr.append(m.getDestinationIpAddresses()); natRuleStr.append(" ["); - natRuleStr.append(m.getDestinationPortMin()); - natRuleStr.append("-"); - natRuleStr.append(m.getDestinationPortMax()); + natRuleStr.append(m.getDestinationPort()); natRuleStr.append(" ]) -->"); if ("SourceNatRule".equals(rule.getType())) { - natRuleStr.append(rule.getToSourceIpAddressMin()); + natRuleStr.append(((SourceNatRule)rule).getToSourceIpAddressMin()); natRuleStr.append("-"); - natRuleStr.append(rule.getToSourceIpAddressMax()); + natRuleStr.append(((SourceNatRule)rule).getToSourceIpAddressMax()); natRuleStr.append(" ["); - natRuleStr.append(rule.getToSourcePortMin()); - natRuleStr.append("-"); - natRuleStr.append(rule.getToSourcePortMax()); + natRuleStr.append(((SourceNatRule)rule).getToSourcePort()); natRuleStr.append(" ])"); } else { - natRuleStr.append(rule.getToDestinationIpAddressMin()); - natRuleStr.append("-"); - natRuleStr.append(rule.getToDestinationIpAddressMax()); + natRuleStr.append(((DestinationNatRule)rule).getToDestinationIpAddress()); natRuleStr.append(" ["); - natRuleStr.append(rule.getToDestinationPort()); + natRuleStr.append(((DestinationNatRule)rule).getToDestinationPort()); natRuleStr.append(" ])"); } return natRuleStr.toString(); @@ -742,23 +737,24 @@ public class NiciraNvpResource implements ServerResource { protected NatRule[] generateStaticNatRulePair(String insideIp, String outsideIp) { NatRule[] rulepair = new NatRule[2]; - rulepair[0] = new NatRule(); + rulepair[0] = new DestinationNatRule(); rulepair[0].setType("DestinationNatRule"); - rulepair[1] = new NatRule(); + rulepair[0].setOrder(100); + rulepair[1] = new SourceNatRule(); rulepair[1].setType("SourceNatRule"); + rulepair[1].setOrder(100); Match m = new Match(); m.setDestinationIpAddresses(outsideIp); rulepair[0].setMatch(m); - rulepair[0].setToDestinationIpAddressMin(insideIp); - rulepair[0].setToDestinationIpAddressMax(insideIp); + ((DestinationNatRule)rulepair[0]).setToDestinationIpAddress(insideIp); // create matching snat rule m = new Match(); m.setSourceIpAddresses(insideIp); rulepair[1].setMatch(m); - rulepair[1].setToSourceIpAddressMin(outsideIp); - rulepair[1].setToSourceIpAddressMax(outsideIp); + ((SourceNatRule)rulepair[1]).setToSourceIpAddressMin(outsideIp); + ((SourceNatRule)rulepair[1]).setToSourceIpAddressMax(outsideIp); return rulepair; @@ -768,9 +764,9 @@ public class NiciraNvpResource implements ServerResource { // Start with a basic static nat rule, then add port and protocol details NatRule[] rulepair = generateStaticNatRulePair(insideIp, outsideIp); - rulepair[0].setToDestinationPort(insidePorts[0]); - rulepair[0].getMatch().setDestinationPortMin(outsidePorts[0]); - rulepair[0].getMatch().setDestinationPortMax(outsidePorts[1]); + ((DestinationNatRule)rulepair[0]).setToDestinationPort(insidePorts[0]); + rulepair[0].getMatch().setDestinationPort(outsidePorts[0]); + rulepair[0].setOrder(50); rulepair[0].getMatch().setEthertype("IPv4"); if ("tcp".equals(protocol)) { rulepair[0].getMatch().setProtocol(6); @@ -779,10 +775,9 @@ public class NiciraNvpResource implements ServerResource { rulepair[0].getMatch().setProtocol(17); } - rulepair[1].setToSourcePortMin(outsidePorts[0]); - rulepair[1].setToSourcePortMax(outsidePorts[1]); - rulepair[1].getMatch().setSourcePortMin(insidePorts[0]); - rulepair[1].getMatch().setSourcePortMax(insidePorts[1]); + ((SourceNatRule)rulepair[1]).setToSourcePort(outsidePorts[0]); + rulepair[1].getMatch().setSourcePort(insidePorts[0]); + rulepair[1].setOrder(50); rulepair[1].getMatch().setEthertype("IPv4"); if ("tcp".equals(protocol)) { rulepair[1].getMatch().setProtocol(6); diff --git a/plugins/network-elements/nicira-nvp/test/com/cloud/network/nicira/NatRuleTest.java b/plugins/network-elements/nicira-nvp/test/com/cloud/network/nicira/NatRuleTest.java index 88c5402822d..8e7245ebbdf 100644 --- a/plugins/network-elements/nicira-nvp/test/com/cloud/network/nicira/NatRuleTest.java +++ b/plugins/network-elements/nicira-nvp/test/com/cloud/network/nicira/NatRuleTest.java @@ -20,28 +20,33 @@ import static org.junit.Assert.*; import org.junit.Test; +import com.google.gson.FieldNamingPolicy; import com.google.gson.Gson; +import com.google.gson.GsonBuilder; public class NatRuleTest { - Gson gson = new Gson(); + @Test + public void testNatRuleEncoding() { + Gson gson = new GsonBuilder() + .registerTypeAdapter(NatRule.class, new com.cloud.network.nicira.NiciraNvpApi.NatRuleAdapter()) + .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .create(); + + DestinationNatRule rn1 = new DestinationNatRule(); + rn1.setToDestinationIpAddress("10.10.10.10"); + rn1.setToDestinationPort(80); + Match mr1 = new Match(); + mr1.setSourceIpAddresses("11.11.11.11/24"); + mr1.setEthertype("IPv4"); + mr1.setProtocol(6); + rn1.setMatch(mr1); + + String jsonString = gson.toJson(rn1); + NatRule dnr = gson.fromJson(jsonString, NatRule.class); + + assertTrue(dnr instanceof DestinationNatRule); + assertTrue(rn1.equals(dnr)); + } - @Test - public void testNatRuleEncoding() { - NatRule rn1 = new NatRule(); - rn1.setToDestinationIpAddressMax("10.10.10.10"); - rn1.setToDestinationIpAddressMin("10.10.10.10"); - rn1.setToDestinationPort(80); - Match mr1 = new Match(); - mr1.setSourceIpAddresses("11.11.11.11/24"); - mr1.setEthertype("IPv4"); - mr1.setProtocol(6); - rn1.setMatch(mr1); - - - String jsonString = gson.toJson(rn1); - NatRule dnr = gson.fromJson(jsonString, NatRule.class); - - assertTrue(rn1.equals(dnr)); - } } diff --git a/plugins/network-elements/nicira-nvp/test/com/cloud/network/resource/NiciraNvpResourceTest.java b/plugins/network-elements/nicira-nvp/test/com/cloud/network/resource/NiciraNvpResourceTest.java index e3789108f34..8b5af3cc12d 100644 --- a/plugins/network-elements/nicira-nvp/test/com/cloud/network/resource/NiciraNvpResourceTest.java +++ b/plugins/network-elements/nicira-nvp/test/com/cloud/network/resource/NiciraNvpResourceTest.java @@ -25,6 +25,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.UUID; import javax.naming.ConfigurationException; @@ -61,6 +62,7 @@ import com.cloud.agent.api.to.StaticNatRuleTO; import com.cloud.host.Host; import com.cloud.network.nicira.Attachment; import com.cloud.network.nicira.ControlClusterStatus; +import com.cloud.network.nicira.DestinationNatRule; import com.cloud.network.nicira.LogicalRouterConfig; import com.cloud.network.nicira.LogicalRouterPort; import com.cloud.network.nicira.LogicalSwitch; @@ -69,6 +71,7 @@ import com.cloud.network.nicira.NatRule; import com.cloud.network.nicira.NiciraNvpApi; import com.cloud.network.nicira.NiciraNvpApiException; import com.cloud.network.nicira.NiciraNvpList; +import com.cloud.network.nicira.SourceNatRule; public class NiciraNvpResourceTest { NiciraNvpApi _nvpApi = mock(NiciraNvpApi.class); @@ -440,8 +443,8 @@ public class NiciraNvpResourceTest { // Mock the api create calls NatRule[] rulepair = _resource.generateStaticNatRulePair("10.10.10.10", "11.11.11.11"); - rulepair[0].setUuid("bbbbb"); - rulepair[1].setUuid("ccccc"); + rulepair[0].setUuid(UUID.randomUUID()); + rulepair[1].setUuid(UUID.randomUUID()); when(_nvpApi.createLogicalRouterNatRule(eq("aaaaa"), (NatRule)any())).thenReturn(rulepair[0]).thenReturn(rulepair[1]); ConfigureStaticNatRulesOnLogicalRouterAnswer a = (ConfigureStaticNatRulesOnLogicalRouterAnswer) _resource.executeRequest(cmd); @@ -452,11 +455,11 @@ public class NiciraNvpResourceTest { public boolean matches(Object argument) { NatRule rule = (NatRule) argument; if (rule.getType().equals("DestinationNatRule") && - rule.getToDestinationIpAddressMin().equals("10.10.10.10")) { + ((DestinationNatRule)rule).getToDestinationIpAddress().equals("10.10.10.10")) { return true; } if (rule.getType().equals("SourceNatRule") && - rule.getToSourceIpAddressMin().equals("11.11.11.11")) { + ((SourceNatRule)rule).getToSourceIpAddressMin().equals("11.11.11.11")) { return true; } return false; @@ -481,8 +484,8 @@ public class NiciraNvpResourceTest { // Mock the api create calls NatRule[] rulepair = _resource.generateStaticNatRulePair("10.10.10.10", "11.11.11.11"); - rulepair[0].setUuid("bbbbb"); - rulepair[1].setUuid("ccccc"); + rulepair[0].setUuid(UUID.randomUUID()); + rulepair[1].setUuid(UUID.randomUUID()); when(_nvpApi.createLogicalRouterNatRule(eq("aaaaa"), (NatRule)any())).thenReturn(rulepair[0]).thenReturn(rulepair[1]); // Mock the api find call @@ -500,11 +503,11 @@ public class NiciraNvpResourceTest { public boolean matches(Object argument) { NatRule rule = (NatRule) argument; if (rule.getType().equals("DestinationNatRule") && - rule.getToDestinationIpAddressMin().equals("10.10.10.10")) { + ((DestinationNatRule)rule).getToDestinationIpAddress().equals("10.10.10.10")) { return true; } if (rule.getType().equals("SourceNatRule") && - rule.getToSourceIpAddressMin().equals("11.11.11.11")) { + ((SourceNatRule)rule).getToSourceIpAddressMin().equals("11.11.11.11")) { return true; } return false; @@ -529,8 +532,10 @@ public class NiciraNvpResourceTest { // Mock the api create calls NatRule[] rulepair = _resource.generateStaticNatRulePair("10.10.10.10", "11.11.11.11"); - rulepair[0].setUuid("bbbbb"); - rulepair[1].setUuid("ccccc"); + final UUID rule0Uuid = UUID.randomUUID(); + final UUID rule1Uuid = UUID.randomUUID(); + rulepair[0].setUuid(rule0Uuid); + rulepair[1].setUuid(rule1Uuid); when(_nvpApi.createLogicalRouterNatRule(eq("aaaaa"), (NatRule)any())).thenReturn(rulepair[0]).thenReturn(rulepair[1]); // Mock the api find call @@ -543,11 +548,11 @@ public class NiciraNvpResourceTest { ConfigureStaticNatRulesOnLogicalRouterAnswer a = (ConfigureStaticNatRulesOnLogicalRouterAnswer) _resource.executeRequest(cmd); assertTrue(a.getResult()); - verify(_nvpApi, atLeast(2)).deleteLogicalRouterNatRule(eq("aaaaa"), argThat(new ArgumentMatcher() { + verify(_nvpApi, atLeast(2)).deleteLogicalRouterNatRule(eq("aaaaa"), argThat(new ArgumentMatcher() { @Override public boolean matches(Object argument) { - String uuid = (String) argument; - if ("bbbbb".equals(uuid) || "ccccc".equals(uuid)) { + UUID uuid = (UUID) argument; + if (rule0Uuid.equals(uuid) || rule1Uuid.equals(uuid)) { return true; } return false; @@ -572,8 +577,8 @@ public class NiciraNvpResourceTest { // Mock the api create calls NatRule[] rulepair = _resource.generateStaticNatRulePair("10.10.10.10", "11.11.11.11"); - rulepair[0].setUuid("bbbbb"); - rulepair[1].setUuid("ccccc"); + rulepair[0].setUuid(UUID.randomUUID()); + rulepair[1].setUuid(UUID.randomUUID()); when(_nvpApi.createLogicalRouterNatRule(eq("aaaaa"), (NatRule)any())).thenReturn(rulepair[0]).thenThrow(new NiciraNvpApiException()); // Mock the api find call @@ -585,7 +590,7 @@ public class NiciraNvpResourceTest { ConfigureStaticNatRulesOnLogicalRouterAnswer a = (ConfigureStaticNatRulesOnLogicalRouterAnswer) _resource.executeRequest(cmd); assertFalse(a.getResult()); - verify(_nvpApi, atLeastOnce()).deleteLogicalRouterNatRule(eq("aaaaa"), eq("bbbbb")); + verify(_nvpApi, atLeastOnce()).deleteLogicalRouterNatRule(eq("aaaaa"), eq(rulepair[0].getUuid())); } @Test @@ -611,8 +616,8 @@ public class NiciraNvpResourceTest { // Mock the api create calls NatRule[] rulepair = _resource.generatePortForwardingRulePair("10.10.10.10", new int[] { 8080, 8080 }, "11.11.11.11", new int[] { 80, 80}, "tcp"); - rulepair[0].setUuid("bbbbb"); - rulepair[1].setUuid("ccccc"); + rulepair[0].setUuid(UUID.randomUUID()); + rulepair[1].setUuid(UUID.randomUUID()); when(_nvpApi.createLogicalRouterNatRule(eq("aaaaa"), (NatRule)any())).thenReturn(rulepair[0]).thenReturn(rulepair[1]); ConfigurePortForwardingRulesOnLogicalRouterAnswer a = (ConfigurePortForwardingRulesOnLogicalRouterAnswer) _resource.executeRequest(cmd); @@ -623,11 +628,11 @@ public class NiciraNvpResourceTest { public boolean matches(Object argument) { NatRule rule = (NatRule) argument; if (rule.getType().equals("DestinationNatRule") && - rule.getToDestinationIpAddressMin().equals("10.10.10.10")) { + ((DestinationNatRule)rule).getToDestinationIpAddress().equals("10.10.10.10")) { return true; } if (rule.getType().equals("SourceNatRule") && - rule.getToSourceIpAddressMin().equals("11.11.11.11")) { + ((SourceNatRule)rule).getToSourceIpAddressMin().equals("11.11.11.11")) { return true; } return false; @@ -652,8 +657,8 @@ public class NiciraNvpResourceTest { // Mock the api create calls NatRule[] rulepair = _resource.generatePortForwardingRulePair("10.10.10.10", new int[] { 8080, 8080 }, "11.11.11.11", new int[] { 80, 80}, "tcp"); - rulepair[0].setUuid("bbbbb"); - rulepair[1].setUuid("ccccc"); + rulepair[0].setUuid(UUID.randomUUID()); + rulepair[1].setUuid(UUID.randomUUID()); when(_nvpApi.createLogicalRouterNatRule(eq("aaaaa"), (NatRule)any())).thenReturn(rulepair[0]).thenReturn(rulepair[1]); // Mock the api find call @@ -671,11 +676,11 @@ public class NiciraNvpResourceTest { public boolean matches(Object argument) { NatRule rule = (NatRule) argument; if (rule.getType().equals("DestinationNatRule") && - rule.getToDestinationIpAddressMin().equals("10.10.10.10")) { + ((DestinationNatRule)rule).getToDestinationIpAddress().equals("10.10.10.10")) { return true; } if (rule.getType().equals("SourceNatRule") && - rule.getToSourceIpAddressMin().equals("11.11.11.11")) { + ((SourceNatRule)rule).getToSourceIpAddressMin().equals("11.11.11.11")) { return true; } return false; @@ -700,8 +705,10 @@ public class NiciraNvpResourceTest { // Mock the api create calls NatRule[] rulepair = _resource.generatePortForwardingRulePair("10.10.10.10", new int[] { 8080, 8080 }, "11.11.11.11", new int[] { 80, 80}, "tcp"); - rulepair[0].setUuid("bbbbb"); - rulepair[1].setUuid("ccccc"); + final UUID rule0Uuid = UUID.randomUUID(); + final UUID rule1Uuid = UUID.randomUUID(); + rulepair[0].setUuid(rule0Uuid); + rulepair[1].setUuid(rule1Uuid); when(_nvpApi.createLogicalRouterNatRule(eq("aaaaa"), (NatRule)any())).thenReturn(rulepair[0]).thenReturn(rulepair[1]); // Mock the api find call @@ -714,11 +721,11 @@ public class NiciraNvpResourceTest { ConfigurePortForwardingRulesOnLogicalRouterAnswer a = (ConfigurePortForwardingRulesOnLogicalRouterAnswer) _resource.executeRequest(cmd); assertTrue(a.getResult()); - verify(_nvpApi, atLeast(2)).deleteLogicalRouterNatRule(eq("aaaaa"), argThat(new ArgumentMatcher() { + verify(_nvpApi, atLeast(2)).deleteLogicalRouterNatRule(eq("aaaaa"), argThat(new ArgumentMatcher() { @Override public boolean matches(Object argument) { - String uuid = (String) argument; - if ("bbbbb".equals(uuid) || "ccccc".equals(uuid)) { + UUID uuid = (UUID) argument; + if (rule0Uuid.equals(uuid) || rule1Uuid.equals(uuid)) { return true; } return false; @@ -743,8 +750,8 @@ public class NiciraNvpResourceTest { // Mock the api create calls NatRule[] rulepair = _resource.generatePortForwardingRulePair("10.10.10.10", new int[] { 8080, 8080 }, "11.11.11.11", new int[] { 80, 80}, "tcp"); - rulepair[0].setUuid("bbbbb"); - rulepair[1].setUuid("ccccc"); + rulepair[0].setUuid(UUID.randomUUID()); + rulepair[1].setUuid(UUID.randomUUID()); when(_nvpApi.createLogicalRouterNatRule(eq("aaaaa"), (NatRule)any())).thenReturn(rulepair[0]).thenThrow(new NiciraNvpApiException()); // Mock the api find call @@ -756,7 +763,7 @@ public class NiciraNvpResourceTest { ConfigurePortForwardingRulesOnLogicalRouterAnswer a = (ConfigurePortForwardingRulesOnLogicalRouterAnswer) _resource.executeRequest(cmd); assertFalse(a.getResult()); - verify(_nvpApi, atLeastOnce()).deleteLogicalRouterNatRule(eq("aaaaa"), eq("bbbbb")); + verify(_nvpApi, atLeastOnce()).deleteLogicalRouterNatRule(eq("aaaaa"), eq(rulepair[0].getUuid())); } @Test @@ -782,8 +789,8 @@ public class NiciraNvpResourceTest { // Mock the api create calls NatRule[] rulepair = _resource.generatePortForwardingRulePair("10.10.10.10", new int[] { 80, 85 }, "11.11.11.11", new int[] { 80, 85}, "tcp"); - rulepair[0].setUuid("bbbbb"); - rulepair[1].setUuid("ccccc"); + rulepair[0].setUuid(UUID.randomUUID()); + rulepair[1].setUuid(UUID.randomUUID()); when(_nvpApi.createLogicalRouterNatRule(eq("aaaaa"), (NatRule)any())).thenReturn(rulepair[0]).thenReturn(rulepair[1]); ConfigurePortForwardingRulesOnLogicalRouterAnswer a = (ConfigurePortForwardingRulesOnLogicalRouterAnswer) _resource.executeRequest(cmd); @@ -799,13 +806,15 @@ public class NiciraNvpResourceTest { assertTrue("DestinationNatRule".equals(rules[0].getType())); assertTrue("SourceNatRule".equals(rules[1].getType())); - assertTrue(rules[0].getToDestinationIpAddressMin().equals("10.10.10.10") && rules[0].getToDestinationIpAddressMax().equals("10.10.10.10")); - assertTrue(rules[0].getToDestinationPort() == null); - assertTrue(rules[0].getMatch().getDestinationIpAddresses().equals("11.11.11.11")); + DestinationNatRule dnr = (DestinationNatRule) rules[0]; + assertTrue(dnr.getToDestinationIpAddress().equals("10.10.10.10")); + assertTrue(dnr.getToDestinationPort() == null); + assertTrue(dnr.getMatch().getDestinationIpAddresses().equals("11.11.11.11")); - assertTrue(rules[1].getToSourceIpAddressMin().equals("11.11.11.11") && rules[1].getToSourceIpAddressMax().equals("11.11.11.11")); - assertTrue(rules[1].getToSourcePortMin() == null && rules[1].getToSourcePortMax() == null); - assertTrue(rules[1].getMatch().getSourceIpAddresses().equals("10.10.10.10")); + SourceNatRule snr = (SourceNatRule) rules[1]; + assertTrue(snr.getToSourceIpAddressMin().equals("11.11.11.11") && snr.getToSourceIpAddressMax().equals("11.11.11.11")); + assertTrue(snr.getToSourcePort() == null); + assertTrue(snr.getMatch().getSourceIpAddresses().equals("10.10.10.10")); } @Test @@ -814,17 +823,19 @@ public class NiciraNvpResourceTest { assertTrue("DestinationNatRule".equals(rules[0].getType())); assertTrue("SourceNatRule".equals(rules[1].getType())); - assertTrue(rules[0].getToDestinationIpAddressMin().equals("10.10.10.10") && rules[0].getToDestinationIpAddressMax().equals("10.10.10.10")); - assertTrue(rules[0].getToDestinationPort() == 8080); - assertTrue(rules[0].getMatch().getDestinationIpAddresses().equals("11.11.11.11")); - assertTrue(rules[0].getMatch().getDestinationPortMin() == 80 && rules[0].getMatch().getDestinationPortMax() == 80); - assertTrue(rules[0].getMatch().getEthertype().equals("IPv4") && rules[0].getMatch().getProtocol() == 6); + DestinationNatRule dnr = (DestinationNatRule) rules[0]; + assertTrue(dnr.getToDestinationIpAddress().equals("10.10.10.10")); + assertTrue(dnr.getToDestinationPort() == 8080); + assertTrue(dnr.getMatch().getDestinationIpAddresses().equals("11.11.11.11")); + assertTrue(dnr.getMatch().getDestinationPort() == 80 ); + assertTrue(dnr.getMatch().getEthertype().equals("IPv4") && dnr.getMatch().getProtocol() == 6); - assertTrue(rules[1].getToSourceIpAddressMin().equals("11.11.11.11") && rules[1].getToSourceIpAddressMax().equals("11.11.11.11")); - assertTrue(rules[1].getToSourcePortMin() == 80 && rules[1].getToSourcePortMax() == 80); - assertTrue(rules[1].getMatch().getSourceIpAddresses().equals("10.10.10.10")); - assertTrue(rules[1].getMatch().getSourcePortMin() == 8080 && rules[1].getMatch().getSourcePortMax() == 8080); - assertTrue(rules[1].getMatch().getEthertype().equals("IPv4") && rules[1].getMatch().getProtocol() == 6); + SourceNatRule snr = (SourceNatRule) rules[1]; + assertTrue(snr.getToSourceIpAddressMin().equals("11.11.11.11") && snr.getToSourceIpAddressMax().equals("11.11.11.11")); + assertTrue(snr.getToSourcePort() == 80); + assertTrue(snr.getMatch().getSourceIpAddresses().equals("10.10.10.10")); + assertTrue(snr.getMatch().getSourcePort() == 8080); + assertTrue(snr.getMatch().getEthertype().equals("IPv4") && rules[1].getMatch().getProtocol() == 6); } } diff --git a/server/src/com/cloud/network/NetworkModelImpl.java b/server/src/com/cloud/network/NetworkModelImpl.java index 135fd290535..8971f8c163b 100755 --- a/server/src/com/cloud/network/NetworkModelImpl.java +++ b/server/src/com/cloud/network/NetworkModelImpl.java @@ -320,8 +320,12 @@ public class NetworkModelImpl extends ManagerBase implements NetworkModel { } else { if (rulesRevoked) { // no active rules/revoked rules are associated with this public IP, so remove the - // association with the provider - ip.setState(State.Releasing); + // association with the provider + if (ip.isSourceNat()) { + s_logger.debug("Not releasing ip " + ip.getAddress().addr() + " as it is in use for SourceNat"); + } else { + ip.setState(State.Releasing); + } } else { if (ip.getState() == State.Releasing) { // rules are not revoked yet, so don't let the network service provider revoke the IP From a67728b312fbf3a4e6e7f7fb3914a45d22997dbf Mon Sep 17 00:00:00 2001 From: Nitin Mehta Date: Wed, 22 May 2013 15:30:53 +0530 Subject: [PATCH 072/108] CLOUDSTACK-2567 - check for ! dmcEnabled. --- .../cloud/hypervisor/xen/resource/CitrixResourceBase.java | 4 ++-- .../hypervisor/xen/resource/XenServer56FP1Resource.java | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java index d08aaecb171..4680fde9980 100644 --- a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java +++ b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java @@ -661,8 +661,8 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe Set vms = VM.getByNameLabel(conn, vmName); Host host = Host.getByUuid(conn, _host.uuid); - // If DMC is not enable then dont execute this command. - if (isDmcEnabled(conn, host)) { + // If DMC is not enable then don't execute this command. + if (!isDmcEnabled(conn, host)) { String msg = "Unable to scale the vm: " + vmName + " as DMC - Dynamic memory control is not enabled for the XenServer:" + _host.uuid + " ,check your license and hypervisor version."; s_logger.info(msg); return new ScaleVmAnswer(cmd, false, msg); diff --git a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XenServer56FP1Resource.java b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XenServer56FP1Resource.java index 78ad2361b07..54aeef5a022 100644 --- a/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XenServer56FP1Resource.java +++ b/plugins/hypervisors/xen/src/com/cloud/hypervisor/xen/resource/XenServer56FP1Resource.java @@ -244,6 +244,9 @@ public class XenServer56FP1Resource extends XenServer56Resource { protected boolean isDmcEnabled(Connection conn, Host host) throws XenAPIException, XmlRpcException { Map hostParams = new HashMap(); hostParams = host.getLicenseParams(conn); - return hostParams.get("restrict_dmc").equalsIgnoreCase("false"); + + Boolean isDmcEnabled = hostParams.get("restrict_dmc").equalsIgnoreCase("false"); + + return isDmcEnabled; } } From 698da2a279747f85ca6db5558677286c9dc5338b Mon Sep 17 00:00:00 2001 From: Prasanna Santhanam Date: Wed, 22 May 2013 15:40:01 +0530 Subject: [PATCH 073/108] Minor tagging and comments in tests Adding tags to the deployvm test from the marvin tutorial Adding docstrings to the vm snapshot tests Add tag to the pvlan test Signed-off-by: Prasanna Santhanam --- test/integration/smoke/test_deploy_vm.py | 3 + test/integration/smoke/test_pvlan.py | 1 + test/integration/smoke/test_vm_snapshots.py | 618 ++++++++++---------- 3 files changed, 314 insertions(+), 308 deletions(-) diff --git a/test/integration/smoke/test_deploy_vm.py b/test/integration/smoke/test_deploy_vm.py index 5c8e0636cff..425aeb749d2 100644 --- a/test/integration/smoke/test_deploy_vm.py +++ b/test/integration/smoke/test_deploy_vm.py @@ -31,6 +31,8 @@ from marvin.integration.lib.utils import cleanup_resources #common - commonly used methods for all tests are listed here from marvin.integration.lib.common import get_zone, get_domain, get_template +from nose.plugins.attrib import attr + class TestData(object): """Test data object that is required to create resources """ @@ -94,6 +96,7 @@ class TestDeployVM(cloudstackTestCase): self.account ] + @attr(tags = ['advanced', 'simulator', 'basic', 'sg']) def test_deploy_vm(self): """Test Deploy Virtual Machine diff --git a/test/integration/smoke/test_pvlan.py b/test/integration/smoke/test_pvlan.py index 4eb76e1cdb7..0a427ba229d 100644 --- a/test/integration/smoke/test_pvlan.py +++ b/test/integration/smoke/test_pvlan.py @@ -41,6 +41,7 @@ class TestPVLAN(cloudstackTestCase): def setUp(self): self.apiClient = self.testClient.getApiClient() + @attr(tags = ["advanced"]) def test_create_pvlan_network(self): self.debug("Test create pvlan network") createNetworkCmd = createNetwork.createNetworkCmd() diff --git a/test/integration/smoke/test_vm_snapshots.py b/test/integration/smoke/test_vm_snapshots.py index 353d499f7a1..cca4cfb767f 100644 --- a/test/integration/smoke/test_vm_snapshots.py +++ b/test/integration/smoke/test_vm_snapshots.py @@ -1,308 +1,310 @@ -# 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. - -# Import Local Modules -import marvin -from nose.plugins.attrib import attr -from marvin.cloudstackTestCase import * -from marvin.cloudstackAPI import * -from marvin.integration.lib.utils import * -from marvin.integration.lib.base import * -from marvin.integration.lib.common import * -from marvin.remoteSSHClient import remoteSSHClient - -class Services: - """Test Snapshots Services - """ - - def __init__(self): - self.services = { - "account": { - "email": "test@test.com", - "firstname": "Test", - "lastname": "User", - "username": "test", - # Random characters are appended for unique - # username - "password": "password", - }, - "service_offering": { - "name": "Tiny Instance", - "displaytext": "Tiny Instance", - "cpunumber": 1, - "cpuspeed": 200, # in MHz - "memory": 256, # In MBs - }, - "server": { - "displayname": "TestVM", - "username": "root", - "password": "password", - "ssh_port": 22, - "hypervisor": 'XenServer', - "privateport": 22, - "publicport": 22, - "protocol": 'TCP', - }, - "mgmt_server": { - "ipaddress": '1.2.2.152', - "username": "root", - "password": "password", - "port": 22, - }, - "templates": { - "displaytext": 'Template', - "name": 'Template', - "ostype": "CentOS 5.3 (64-bit)", - "templatefilter": 'self', - }, - "test_dir": "/tmp", - "random_data": "random.data", - "snapshot_name":"TestSnapshot", - "snapshot_displaytext":"Test", - "ostype": "CentOS 5.3 (64-bit)", - "sleep": 60, - "timeout": 10, - "mode": 'advanced', # Networking mode: Advanced, Basic - } - -class TestVmSnapshot(cloudstackTestCase): - @classmethod - def setUpClass(cls): - cls.api_client = super(TestVmSnapshot, cls).getClsTestClient().getApiClient() - cls.services = Services().services - # Get Zone, Domain and templates - cls.domain = get_domain(cls.api_client, cls.services) - cls.zone = get_zone(cls.api_client, cls.services) - - template = get_template( - cls.api_client, - cls.zone.id, - cls.services["ostype"] - ) - cls.services["domainid"] = cls.domain.id - cls.services["server"]["zoneid"] = cls.zone.id - cls.services["templates"]["ostypeid"] = template.ostypeid - cls.services["zoneid"] = cls.zone.id - - # Create VMs, NAT Rules etc - cls.account = Account.create( - cls.api_client, - cls.services["account"], - domainid=cls.domain.id - ) - - cls.services["account"] = cls.account.name - - cls.service_offering = ServiceOffering.create( - cls.api_client, - cls.services["service_offering"] - ) - cls.virtual_machine = VirtualMachine.create( - cls.api_client, - cls.services["server"], - templateid=template.id, - accountid=cls.account.name, - domainid=cls.account.domainid, - serviceofferingid=cls.service_offering.id, - mode=cls.services["mode"] - ) - cls.random_data_0 = random_gen(100) - cls._cleanup = [ - cls.service_offering, - cls.account, - ] - return - - @classmethod - def tearDownClass(cls): - try: - # Cleanup resources used - cleanup_resources(cls.api_client, cls._cleanup) - except Exception as e: - raise Exception("Warning: Exception during cleanup : %s" % e) - return - - def setUp(self): - self.apiclient = self.testClient.getApiClient() - self.dbclient = self.testClient.getDbConnection() - self.cleanup = [] - return - - def tearDown(self): - try: - # Clean up, terminate the created instance, volumes and snapshots - cleanup_resources(self.apiclient, self.cleanup) - except Exception as e: - raise Exception("Warning: Exception during cleanup : %s" % e) - return - - @attr(tags=["advanced", "advancedns", "smoke"]) - def test_01_create_vm_snapshots(self): - - try: - # Login to VM and write data to file system - ssh_client = self.virtual_machine.get_ssh_client() - - cmds = [ - "echo %s > %s/%s" % (self.random_data_0, self.services["test_dir"], self.services["random_data"]), - "cat %s/%s" % (self.services["test_dir"], self.services["random_data"]) - ] - - for c in cmds: - self.debug(c) - result = ssh_client.execute(c) - self.debug(result) - - except Exception: - self.fail("SSH failed for Virtual machine: %s" % - self.virtual_machine.ipaddress) - self.assertEqual( - self.random_data_0, - result[0], - "Check the random data has be write into temp file!" - ) - - time.sleep(self.services["sleep"]) - - vm_snapshot = VmSnapshot.create( - self.apiclient, - self.virtual_machine.id, - "false", - self.services["snapshot_name"], - self.services["snapshot_displaytext"] - ) - self.assertEqual( - vm_snapshot.state, - "Ready", - "Check the snapshot of vm is ready!" - ) - return - - @attr(tags=["advanced", "advancedns", "smoke"]) - def test_02_revert_vm_snapshots(self): - try: - ssh_client = self.virtual_machine.get_ssh_client() - - cmds = [ - "rm -rf %s/%s" % (self.services["test_dir"], self.services["random_data"]), - "ls %s/%s" % (self.services["test_dir"], self.services["random_data"]) - ] - - for c in cmds: - self.debug(c) - result = ssh_client.execute(c) - self.debug(result) - - except Exception: - self.fail("SSH failed for Virtual machine: %s" % - self.virtual_machine.ipaddress) - - if str(result[0]).index("No such file or directory") == -1: - self.fail("Check the random data has be delete from temp file!") - - time.sleep(self.services["sleep"]) - - list_snapshot_response = VmSnapshot.list(self.apiclient,vmid=self.virtual_machine.id,listall=True) - - self.assertEqual( - isinstance(list_snapshot_response, list), - True, - "Check list response returns a valid list" - ) - self.assertNotEqual( - list_snapshot_response, - None, - "Check if snapshot exists in ListSnapshot" - ) - - self.assertEqual( - list_snapshot_response[0].state, - "Ready", - "Check the snapshot of vm is ready!" - ) - - VmSnapshot.revertToSnapshot(self.apiclient,list_snapshot_response[0].id) - - list_vm_response = list_virtual_machines( - self.apiclient, - id=self.virtual_machine.id - ) - - self.assertEqual( - list_vm_response[0].state, - "Stopped", - "Check the state of vm is Stopped!" - ) - - cmd = startVirtualMachine.startVirtualMachineCmd() - cmd.id = list_vm_response[0].id - self.apiclient.startVirtualMachine(cmd) - - time.sleep(self.services["sleep"]) - - try: - ssh_client = self.virtual_machine.get_ssh_client(reconnect=True) - - cmds = [ - "cat %s/%s" % (self.services["test_dir"], self.services["random_data"]) - ] - - for c in cmds: - self.debug(c) - result = ssh_client.execute(c) - self.debug(result) - - except Exception: - self.fail("SSH failed for Virtual machine: %s" % - self.virtual_machine.ipaddress) - - self.assertEqual( - self.random_data_0, - result[0], - "Check the random data is equal with the ramdom file!" - ) - @attr(tags=["advanced", "advancedns", "smoke"]) - def test_03_delete_vm_snapshots(self): - - list_snapshot_response = VmSnapshot.list(self.apiclient,vmid=self.virtual_machine.id,listall=True) - - self.assertEqual( - isinstance(list_snapshot_response, list), - True, - "Check list response returns a valid list" - ) - self.assertNotEqual( - list_snapshot_response, - None, - "Check if snapshot exists in ListSnapshot" - ) - """ - cmd = deleteVMSnapshot.deleteVMSnapshotCmd() - cmd.vmsnapshotid = list_snapshot_response[0].id - self.apiclient.deleteVMSnapshot(cmd) - """ - VmSnapshot.deleteVMSnapshot(self.apiclient,list_snapshot_response[0].id) - - time.sleep(self.services["sleep"]*3) - - list_snapshot_response = VmSnapshot.list(self.apiclient,vmid=self.virtual_machine.id,listall=True) - - self.assertEqual( - list_snapshot_response, - None, - "Check list vm snapshot has be deleted" - ) +# 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. + +# Import Local Modules +import marvin +from nose.plugins.attrib import attr +from marvin.cloudstackTestCase import * +from marvin.cloudstackAPI import * +from marvin.integration.lib.utils import * +from marvin.integration.lib.base import * +from marvin.integration.lib.common import * + +class Services: + """Test Snapshots Services + """ + + def __init__(self): + self.services = { + "account": { + "email": "test@test.com", + "firstname": "Test", + "lastname": "User", + "username": "test", + # Random characters are appended for unique + # username + "password": "password", + }, + "service_offering": { + "name": "Tiny Instance", + "displaytext": "Tiny Instance", + "cpunumber": 1, + "cpuspeed": 200, # in MHz + "memory": 256, # In MBs + }, + "server": { + "displayname": "TestVM", + "username": "root", + "password": "password", + "ssh_port": 22, + "hypervisor": 'XenServer', + "privateport": 22, + "publicport": 22, + "protocol": 'TCP', + }, + "mgmt_server": { + "ipaddress": '1.2.2.152', + "username": "root", + "password": "password", + "port": 22, + }, + "templates": { + "displaytext": 'Template', + "name": 'Template', + "ostype": "CentOS 5.3 (64-bit)", + "templatefilter": 'self', + }, + "test_dir": "/tmp", + "random_data": "random.data", + "snapshot_name":"TestSnapshot", + "snapshot_displaytext":"Test", + "ostype": "CentOS 5.3 (64-bit)", + "sleep": 60, + "timeout": 10, + "mode": 'advanced', # Networking mode: Advanced, Basic + } + +class TestVmSnapshot(cloudstackTestCase): + @classmethod + def setUpClass(cls): + cls.api_client = super(TestVmSnapshot, cls).getClsTestClient().getApiClient() + cls.services = Services().services + # Get Zone, Domain and templates + cls.domain = get_domain(cls.api_client, cls.services) + cls.zone = get_zone(cls.api_client, cls.services) + + template = get_template( + cls.api_client, + cls.zone.id, + cls.services["ostype"] + ) + cls.services["domainid"] = cls.domain.id + cls.services["server"]["zoneid"] = cls.zone.id + cls.services["templates"]["ostypeid"] = template.ostypeid + cls.services["zoneid"] = cls.zone.id + + # Create VMs, NAT Rules etc + cls.account = Account.create( + cls.api_client, + cls.services["account"], + domainid=cls.domain.id + ) + + cls.services["account"] = cls.account.name + + cls.service_offering = ServiceOffering.create( + cls.api_client, + cls.services["service_offering"] + ) + cls.virtual_machine = VirtualMachine.create( + cls.api_client, + cls.services["server"], + templateid=template.id, + accountid=cls.account.name, + domainid=cls.account.domainid, + serviceofferingid=cls.service_offering.id, + mode=cls.services["mode"] + ) + cls.random_data_0 = random_gen(100) + cls._cleanup = [ + cls.service_offering, + cls.account, + ] + return + + @classmethod + def tearDownClass(cls): + try: + # Cleanup resources used + cleanup_resources(cls.api_client, cls._cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + return + + def tearDown(self): + try: + # Clean up, terminate the created instance, volumes and snapshots + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + @attr(tags=["advanced", "advancedns", "smoke"]) + def test_01_create_vm_snapshots(self): + """Test to create VM snapshots + """ + + try: + # Login to VM and write data to file system + ssh_client = self.virtual_machine.get_ssh_client() + + cmds = [ + "echo %s > %s/%s" % (self.random_data_0, self.services["test_dir"], self.services["random_data"]), + "cat %s/%s" % (self.services["test_dir"], self.services["random_data"]) + ] + + for c in cmds: + self.debug(c) + result = ssh_client.execute(c) + self.debug(result) + + except Exception: + self.fail("SSH failed for Virtual machine: %s" % + self.virtual_machine.ipaddress) + self.assertEqual( + self.random_data_0, + result[0], + "Check the random data has be write into temp file!" + ) + + time.sleep(self.services["sleep"]) + + vm_snapshot = VmSnapshot.create( + self.apiclient, + self.virtual_machine.id, + "false", + self.services["snapshot_name"], + self.services["snapshot_displaytext"] + ) + self.assertEqual( + vm_snapshot.state, + "Ready", + "Check the snapshot of vm is ready!" + ) + return + + @attr(tags=["advanced", "advancedns", "smoke"]) + def test_02_revert_vm_snapshots(self): + """Test to revert VM snapshots + """ + + try: + ssh_client = self.virtual_machine.get_ssh_client() + + cmds = [ + "rm -rf %s/%s" % (self.services["test_dir"], self.services["random_data"]), + "ls %s/%s" % (self.services["test_dir"], self.services["random_data"]) + ] + + for c in cmds: + self.debug(c) + result = ssh_client.execute(c) + self.debug(result) + + except Exception: + self.fail("SSH failed for Virtual machine: %s" % + self.virtual_machine.ipaddress) + + if str(result[0]).index("No such file or directory") == -1: + self.fail("Check the random data has be delete from temp file!") + + time.sleep(self.services["sleep"]) + + list_snapshot_response = VmSnapshot.list(self.apiclient,vmid=self.virtual_machine.id,listall=True) + + self.assertEqual( + isinstance(list_snapshot_response, list), + True, + "Check list response returns a valid list" + ) + self.assertNotEqual( + list_snapshot_response, + None, + "Check if snapshot exists in ListSnapshot" + ) + + self.assertEqual( + list_snapshot_response[0].state, + "Ready", + "Check the snapshot of vm is ready!" + ) + + VmSnapshot.revertToSnapshot(self.apiclient,list_snapshot_response[0].id) + + list_vm_response = list_virtual_machines( + self.apiclient, + id=self.virtual_machine.id + ) + + self.assertEqual( + list_vm_response[0].state, + "Stopped", + "Check the state of vm is Stopped!" + ) + + cmd = startVirtualMachine.startVirtualMachineCmd() + cmd.id = list_vm_response[0].id + self.apiclient.startVirtualMachine(cmd) + + time.sleep(self.services["sleep"]) + + try: + ssh_client = self.virtual_machine.get_ssh_client(reconnect=True) + + cmds = [ + "cat %s/%s" % (self.services["test_dir"], self.services["random_data"]) + ] + + for c in cmds: + self.debug(c) + result = ssh_client.execute(c) + self.debug(result) + + except Exception: + self.fail("SSH failed for Virtual machine: %s" % + self.virtual_machine.ipaddress) + + self.assertEqual( + self.random_data_0, + result[0], + "Check the random data is equal with the ramdom file!" + ) + + @attr(tags=["advanced", "advancedns", "smoke"]) + def test_03_delete_vm_snapshots(self): + """Test to delete vm snapshots + """ + + list_snapshot_response = VmSnapshot.list(self.apiclient,vmid=self.virtual_machine.id,listall=True) + + self.assertEqual( + isinstance(list_snapshot_response, list), + True, + "Check list response returns a valid list" + ) + self.assertNotEqual( + list_snapshot_response, + None, + "Check if snapshot exists in ListSnapshot" + ) + VmSnapshot.deleteVMSnapshot(self.apiclient,list_snapshot_response[0].id) + + time.sleep(self.services["sleep"]*3) + + list_snapshot_response = VmSnapshot.list(self.apiclient,vmid=self.virtual_machine.id,listall=True) + + self.assertEqual( + list_snapshot_response, + None, + "Check list vm snapshot has be deleted" + ) From 616011020b0bfeaa0d54cee666cf486974246cf7 Mon Sep 17 00:00:00 2001 From: Prasanna Santhanam Date: Wed, 22 May 2013 15:41:35 +0530 Subject: [PATCH 074/108] Refactoring the planner tests 1. Planner tests rewritten to use marvin integration libraries 2. Included verification of multi VM deployment for user dispersion 3. Included a test for user concentrated planner TODO: firstfit planner test doesn't actually test the planner strategy. It only deploys a VM using the offering. Signed-off-by: Prasanna Santhanam --- ...ploy_vms_with_varied_deploymentplanners.py | 414 +++++++++++------- 1 file changed, 250 insertions(+), 164 deletions(-) diff --git a/test/integration/smoke/test_deploy_vms_with_varied_deploymentplanners.py b/test/integration/smoke/test_deploy_vms_with_varied_deploymentplanners.py index d904a4cb7d8..c557682b108 100644 --- a/test/integration/smoke/test_deploy_vms_with_varied_deploymentplanners.py +++ b/test/integration/smoke/test_deploy_vms_with_varied_deploymentplanners.py @@ -1,164 +1,250 @@ -# 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. - -#!/usr/bin/env python - -import marvin -from marvin import cloudstackTestCase -from marvin.cloudstackTestCase import * - -import unittest -import hashlib -import random - -class TestDeployVmWithVariedPlanners(cloudstackTestCase): - """ - This test tests that we can create serviceOfferings with different deployment Planners and deploy virtual machines into a user account - using these service offerings and builtin template - """ - def setUp(self): - """ - CloudStack internally saves its passwords in md5 form and that is how we - specify it in the API. Python's hashlib library helps us to quickly hash - strings as follows - """ - mdf = hashlib.md5() - mdf.update('password') - mdf_pass = mdf.hexdigest() - - self.apiClient = self.testClient.getApiClient() #Get ourselves an API client - - self.acct = createAccount.createAccountCmd() #The createAccount command - self.acct.accounttype = 0 #We need a regular user. admins have accounttype=1 - self.acct.firstname = 'test' - self.acct.lastname = 'user' #What's up doc? - self.acct.username = 'testuser' - self.acct.password = mdf_pass #The md5 hashed password string - self.acct.email = 'test@domain.com' - self.acct.account = 'testacct' - self.acct.domainid = 1 #The default ROOT domain - self.acctResponse = self.apiClient.createAccount(self.acct) - # And upon successful creation we'll log a helpful message in our logs - # using the default debug logger of the test framework - self.debug("successfully created account: %s, id: \ - %s"%(self.acctResponse.name, \ - self.acctResponse.id)) - - #Create service offerings with varied planners - self.svcOfferingFirstFit = createServiceOffering.createServiceOfferingCmd() - self.svcOfferingFirstFit.name = 'Tiny Instance FirstFit' - self.svcOfferingFirstFit.displaytext = 'Tiny Instance with FirstFitPlanner' - self.svcOfferingFirstFit.cpuspeed = 100 - self.svcOfferingFirstFit.cpunumber = 1 - self.svcOfferingFirstFit.memory = 256 - self.svcOfferingFirstFit.deploymentplanner = 'FirstFitPlanner' - self.svcOfferingFirstFitResponse = self.apiClient.createServiceOffering(self.svcOfferingFirstFit) - - self.debug("successfully created serviceofferring name: %s, id: \ - %s, deploymentPlanner: %s"%(self.svcOfferingFirstFitResponse.name, \ - self.svcOfferingFirstFitResponse.id,self.svcOfferingFirstFitResponse.deploymentplanner)) - - #Create service offerings with varied planners - self.svcOfferingUserDispersing = createServiceOffering.createServiceOfferingCmd() - self.svcOfferingUserDispersing.name = 'Tiny Instance UserDispersing' - self.svcOfferingUserDispersing.displaytext = 'Tiny Instance with UserDispersingPlanner' - self.svcOfferingUserDispersing.cpuspeed = 100 - self.svcOfferingUserDispersing.cpunumber = 1 - self.svcOfferingUserDispersing.memory = 256 - self.svcOfferingUserDispersing.deploymentplanner = 'FirstFitPlanner' - self.svcOfferingUserDispersingResponse = self.apiClient.createServiceOffering(self.svcOfferingUserDispersing) - - self.debug("successfully created serviceofferring name: %s, id: \ - %s, deploymentPlanner: %s"%(self.svcOfferingUserDispersingResponse.name, \ - self.svcOfferingUserDispersingResponse.id,self.svcOfferingUserDispersingResponse.deploymentplanner)) - - def test_DeployVm(self): - """ - Let's start by defining the attributes of our VM that we will be - deploying on CloudStack. We will be assuming a single zone is available - and is configured and all templates are Ready - - The hardcoded values are used only for brevity. - """ - deployVmCmd = deployVirtualMachine.deployVirtualMachineCmd() - deployVmCmd.zoneid = 1 - deployVmCmd.account = self.acct.account - deployVmCmd.domainid = self.acct.domainid - deployVmCmd.templateid = 5 #For default template- CentOS 5.6(64 bit) - deployVmCmd.serviceofferingid = self.svcOfferingFirstFitResponse.id - - deployVmResponse = self.apiClient.deployVirtualMachine(deployVmCmd) - self.debug("VM %s was deployed in the job %s"%(deployVmResponse.id, deployVmResponse.jobid)) - - # At this point our VM is expected to be Running. Let's find out what - # listVirtualMachines tells us about VMs in this account - - listVmCmd = listVirtualMachines.listVirtualMachinesCmd() - listVmCmd.id = deployVmResponse.id - listVmResponse = self.apiClient.listVirtualMachines(listVmCmd) - - self.assertNotEqual(len(listVmResponse), 0, "Check if the list API \ - returns a non-empty response") - - vm1 = listVmResponse[0] - - self.assertEqual(vm1.id, deployVmResponse.id, "Check if the VM returned \ - is the same as the one we deployed") - self.assertEqual(vm1.state, "Running", "Check if VM has reached \ - a state of running") - - - deployVm2Cmd = deployVirtualMachine.deployVirtualMachineCmd() - deployVm2Cmd.zoneid = 1 - deployVm2Cmd.account = self.acct.account - deployVm2Cmd.domainid = self.acct.domainid - deployVm2Cmd.templateid = 5 #For default template- CentOS 5.6(64 bit) - deployVm2Cmd.serviceofferingid = self.svcOfferingFirstFitResponse.id - - deployVm2Response = self.apiClient.deployVirtualMachine(deployVm2Cmd) - self.debug("VM %s was deployed in the job %s"%(deployVm2Response.id, deployVm2Response.jobid)) - - # At this point our VM is expected to be Running. Let's find out what - # listVirtualMachines tells us about VMs in this account - - listVm2Cmd = listVirtualMachines.listVirtualMachinesCmd() - listVm2Cmd.id = deployVm2Response.id - listVm2Response = self.apiClient.listVirtualMachines(listVm2Cmd) - self.assertNotEqual(len(listVm2Response), 0, "Check if the list API \ - returns a non-empty response") - vm2 = listVm2Response[0] - self.assertEqual(vm2.id, deployVm2Response.id, "Check if the VM returned \ - is the same as the one we deployed") - self.assertEqual(vm2.state, "Running", "Check if VM has reached \ - a state of running") - - - def tearDown(self): # Teardown will delete the Account as well as the VM once the VM reaches "Running" state - """ - And finally let us cleanup the resources we created by deleting the - account. All good unittests are atomic and rerunnable this way - """ - deleteAcct = deleteAccount.deleteAccountCmd() - deleteAcct.id = self.acctResponse.id - self.apiClient.deleteAccount(deleteAcct) - deleteSvcOfferingFirstFit = deleteServiceOffering.deleteServiceOfferingCmd() - deleteSvcOfferingFirstFit.id = self.svcOfferingFirstFitResponse.id - self.apiClient.deleteServiceOffering(deleteSvcOfferingFirstFit); - deleteSvcOfferingUserDispersing = deleteServiceOffering.deleteServiceOfferingCmd() - deleteSvcOfferingUserDispersing.id = self.svcOfferingUserDispersingResponse.id - self.apiClient.deleteServiceOffering(deleteSvcOfferingUserDispersing); - \ No newline at end of file +# 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. + +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.integration.lib.base import Account, VirtualMachine, ServiceOffering +from marvin.integration.lib.common import get_zone, get_domain, get_template, cleanup_resources + +from nose.plugins.attrib import attr + +class Services: + def __init__(self): + self.services = { + "account": { + "email": "test@test.com", + "firstname": "Test", + "lastname": "User", + "username": "test", + # Random characters are appended for unique + # username + "password": "password", + }, + "service_offering": { + "name": "Planner Service Offering", + "displaytext": "Planner Service Offering", + "cpunumber": 1, + "cpuspeed": 100, + # in MHz + "memory": 128, + # In MBs + }, + "ostype": 'CentOS 5.3 (64-bit)', + "virtual_machine": { + "hypervisor": "XenServer", + } + } + + +class TestDeployVmWithVariedPlanners(cloudstackTestCase): + """ Test to create services offerings for deployment planners + - firstfit, userdispersing + """ + + @classmethod + def setUpClass(cls): + cls.apiclient = super(TestDeployVmWithVariedPlanners, cls).getClsTestClient().getApiClient() + cls.services = Services().services + # Get Zone, Domain and templates + cls.domain = get_domain(cls.apiclient, cls.services) + cls.zone = get_zone(cls.apiclient, cls.services) + cls.template = get_template( + cls.apiclient, + cls.zone.id, + cls.services["ostype"] + ) + cls.services["virtual_machine"]["zoneid"] = cls.zone.id + cls.services["template"] = cls.template.id + cls.services["zoneid"] = cls.zone.id + + cls.account = Account.create( + cls.apiclient, + cls.services["account"], + domainid=cls.domain.id + ) + cls.services["account"] = cls.account.name + cls.cleanup = [ + cls.account + ] + + @attr(tags=["simulator", "advanced", "basic", "sg"]) + def test_deployvm_firstfit(self): + """Test to deploy vm with a first fit offering + """ + #FIXME: How do we know that first fit actually happened? + self.service_offering_firstfit = ServiceOffering.create( + self.apiclient, + self.services["service_offering"], + deploymentplanner='FirstFitPlanner' + ) + + self.virtual_machine = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + accountid=self.account.name, + zoneid=self.zone.id, + domainid=self.account.domainid, + serviceofferingid=self.service_offering_firstfit.id, + templateid=self.template.id + ) + + list_vms = VirtualMachine.list(self.apiclient, id=self.virtual_machine.id) + self.debug( + "Verify listVirtualMachines response for virtual machine: %s"\ + % self.virtual_machine.id + ) + self.assertEqual( + isinstance(list_vms, list), + True, + "List VM response was not a valid list" + ) + self.assertNotEqual( + len(list_vms), + 0, + "List VM response was empty" + ) + + vm = list_vms[0] + self.assertEqual( + vm.state, + "Running", + msg="VM is not in Running state" + ) + + @attr(tags=["simulator", "advanced", "basic", "sg"]) + def test_deployvm_userdispersing(self): + """Test deploy VMs using user dispersion planner + """ + self.service_offering_userdispersing = ServiceOffering.create( + self.apiclient, + self.services["service_offering"], + deploymentplanner='UserDispersingPlanner' + ) + + self.virtual_machine_1 = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + accountid=self.account.name, + zoneid=self.zone.id, + domainid=self.account.domainid, + serviceofferingid=self.service_offering_userdispersing.id, + templateid=self.template.id + ) + self.virtual_machine_2 = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + accountid=self.account.name, + zoneid=self.zone.id, + domainid=self.account.domainid, + serviceofferingid=self.service_offering_userdispersing.id, + templateid=self.template.id + ) + + list_vm_1 = VirtualMachine.list(self.apiclient, id=self.virtual_machine_1.id) + list_vm_2 = VirtualMachine.list(self.apiclient, id=self.virtual_machine_2.id) + self.assertEqual( + isinstance(list_vm_1, list), + True, + "List VM response was not a valid list" + ) + self.assertEqual( + isinstance(list_vm_2, list), + True, + "List VM response was not a valid list" + ) + vm1 = list_vm_1[0] + vm2 = list_vm_2[0] + self.assertEqual( + vm1.state, + "Running", + msg="VM is not in Running state" + ) + self.assertEqual( + vm2.state, + "Running", + msg="VM is not in Running state" + ) + self.assertNotEqual( + vm1.hostid, + vm2.hostid, + msg="VMs meant to be dispersed are deployed on the same host" + ) + + @attr(tags=["simulator", "advanced", "basic", "sg"]) + def test_deployvm_userconcentrated(self): + """Test deploy VMs using user concentrated planner + """ + self.service_offering_userconcentrated = ServiceOffering.create( + self.apiclient, + self.services["service_offering"], + deploymentplanner='UserConcentratedPodPlanner' + ) + + self.virtual_machine_1 = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + accountid=self.account.name, + zoneid=self.zone.id, + domainid=self.account.domainid, + serviceofferingid=self.service_offering_userconcentrated.id, + templateid=self.template.id + ) + self.virtual_machine_2 = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + accountid=self.account.name, + zoneid=self.zone.id, + domainid=self.account.domainid, + serviceofferingid=self.service_offering_userconcentrated.id, + templateid=self.template.id + ) + + list_vm_1 = VirtualMachine.list(self.apiclient, id=self.virtual_machine_1.id) + list_vm_2 = VirtualMachine.list(self.apiclient, id=self.virtual_machine_2.id) + self.assertEqual( + isinstance(list_vm_1, list), + True, + "List VM response was not a valid list" + ) + self.assertEqual( + isinstance(list_vm_2, list), + True, + "List VM response was not a valid list" + ) + vm1 = list_vm_1[0] + vm2 = list_vm_2[0] + self.assertEqual( + vm1.state, + "Running", + msg="VM is not in Running state" + ) + self.assertEqual( + vm2.state, + "Running", + msg="VM is not in Running state" + ) + self.assertNotEqual( + vm1.hostid, + vm2.hostid, + msg="VMs meant to be concentrated are deployed on the different hosts" + ) + + @classmethod + def tearDownClass(cls): + try: + cleanup_resources(cls.apiclient, cls.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) From 44a81f0b6f2d4305d8e92058abb88c1a6334d814 Mon Sep 17 00:00:00 2001 From: Girish Shilamkar Date: Wed, 22 May 2013 15:53:27 +0530 Subject: [PATCH 075/108] CLOUDSTACK-2577: Fix exception handling for listPortForwarding API listPortForwarding API returns an exception if the PF is deleted. Changed testcases to handle this exception. Signed-off-by: Girish Shilamkar Signed-off-by: Prasanna Santhanam --- test/integration/smoke/test_network.py | 69 +++++++++----------------- 1 file changed, 24 insertions(+), 45 deletions(-) diff --git a/test/integration/smoke/test_network.py b/test/integration/smoke/test_network.py index 4a7bb44da2c..61ddf46e9ef 100644 --- a/test/integration/smoke/test_network.py +++ b/test/integration/smoke/test_network.py @@ -18,6 +18,7 @@ """ #Import Local Modules import marvin +from marvin.cloudstackException import cloudstackAPIException from marvin.cloudstackTestCase import * from marvin.cloudstackAPI import * from marvin import remoteSSHClient @@ -447,16 +448,14 @@ class TestPortForwarding(cloudstackTestCase): nat_rule.delete(self.apiclient) - list_nat_rule_response = list_nat_rules( + try: + list_nat_rule_response = list_nat_rules( self.apiclient, id=nat_rule.id ) + except cloudstackAPIException: + self.debug("Nat Rule is deleted") - self.assertEqual( - list_nat_rule_response, - None, - "Check Port Forwarding Rule is deleted" - ) # Check if the Public SSH port is inaccessible with self.assertRaises(Exception): self.debug( @@ -565,15 +564,14 @@ class TestPortForwarding(cloudstackTestCase): nat_rule.delete(self.apiclient) - list_nat_rule_response = list_nat_rules( + try: + list_nat_rule_response = list_nat_rules( self.apiclient, id=nat_rule.id ) - self.assertEqual( - list_nat_rule_response, - None, - "Check Port Forwarding Rule is deleted" - ) + except cloudstackAPIException: + self.debug("Nat Rule is deleted") + # Check if the Public SSH port is inaccessible with self.assertRaises(Exception): self.debug( @@ -785,7 +783,6 @@ class TestLoadBalancingRule(cloudstackTestCase): "SSH into VM (IPaddress: %s) & NAT Rule (Public IP: %s)" % (self.vm_1.ipaddress, src_nat_ip_addr.ipaddress) ) - ssh_1 = remoteSSHClient( src_nat_ip_addr.ipaddress, self.services['lbrule']["publicport"], @@ -1582,32 +1579,28 @@ class TestReleaseIP(cloudstackTestCase): "Check if disassociated IP Address is no longer available" ) + self.debug("List NAT Rule response" + str(list_nat_rule)) # ListPortForwardingRules should not list # associated rules with Public IP address - list_nat_rule = list_nat_rules( + try: + list_nat_rule = list_nat_rules( self.apiclient, id=self.nat_rule.id ) - self.debug("List NAT Rule response" + str(list_nat_rule)) - self.assertEqual( - list_nat_rule, - None, - "Check if PF rules are no longer available for IP address" - ) + except cloudstackAPIException: + self.debug("Port Forwarding Rule is deleted") # listLoadBalancerRules should not list # associated rules with Public IP address - list_lb_rule = list_lb_rules( + self.debug("List LB Rule response" + str(list_lb_rule)) + try: + list_lb_rule = list_lb_rules( self.apiclient, id=self.lb_rule.id ) - self.debug("List LB Rule response" + str(list_lb_rule)) - self.assertEqual( - list_lb_rule, - None, - "Check if LB rules for IP Address are no longer available" - ) + except cloudstackAPIException: + self.debug("Port Forwarding Rule is deleted") # SSH Attempt though public IP should fail with self.assertRaises(Exception): @@ -1720,16 +1713,9 @@ class TestDeleteAccount(cloudstackTestCase): account=self.account.name, domainid=self.account.domainid ) - self.assertEqual( - list_lb_reponse, - None, - "Check load balancing rule is properly deleted." - ) - except Exception as e: + except cloudstackAPIException: + self.debug("Port Forwarding Rule is deleted") - raise Exception( - "Exception raised while fetching LB rules for account: %s" % - self.account.name) # ListPortForwardingRules should not # list associated rules with deleted account try: @@ -1738,16 +1724,9 @@ class TestDeleteAccount(cloudstackTestCase): account=self.account.name, domainid=self.account.domainid ) - self.assertEqual( - list_nat_reponse, - None, - "Check load balancing rule is properly deleted." - ) - except Exception as e: + except cloudstackAPIException: + self.debug("NATRule is deleted") - raise Exception( - "Exception raised while fetching NAT rules for account: %s" % - self.account.name) #Retrieve router for the user account try: routers = list_routers( From b3096db600d433cf3ddbae83f8ad82ca4609cc6f Mon Sep 17 00:00:00 2001 From: Prasanna Santhanam Date: Wed, 22 May 2013 16:22:26 +0530 Subject: [PATCH 076/108] Bad indentation corrected For some reason test got unindented wrongly by Eclipse. Signed-off-by: Prasanna Santhanam --- ...ploy_vms_with_varied_deploymentplanners.py | 344 +++++++++--------- 1 file changed, 172 insertions(+), 172 deletions(-) diff --git a/test/integration/smoke/test_deploy_vms_with_varied_deploymentplanners.py b/test/integration/smoke/test_deploy_vms_with_varied_deploymentplanners.py index c557682b108..67532c78d0f 100644 --- a/test/integration/smoke/test_deploy_vms_with_varied_deploymentplanners.py +++ b/test/integration/smoke/test_deploy_vms_with_varied_deploymentplanners.py @@ -56,195 +56,195 @@ class TestDeployVmWithVariedPlanners(cloudstackTestCase): @classmethod def setUpClass(cls): - cls.apiclient = super(TestDeployVmWithVariedPlanners, cls).getClsTestClient().getApiClient() - cls.services = Services().services - # Get Zone, Domain and templates - cls.domain = get_domain(cls.apiclient, cls.services) - cls.zone = get_zone(cls.apiclient, cls.services) - cls.template = get_template( - cls.apiclient, - cls.zone.id, - cls.services["ostype"] - ) - cls.services["virtual_machine"]["zoneid"] = cls.zone.id - cls.services["template"] = cls.template.id - cls.services["zoneid"] = cls.zone.id + cls.apiclient = super(TestDeployVmWithVariedPlanners, cls).getClsTestClient().getApiClient() + cls.services = Services().services + # Get Zone, Domain and templates + cls.domain = get_domain(cls.apiclient, cls.services) + cls.zone = get_zone(cls.apiclient, cls.services) + cls.template = get_template( + cls.apiclient, + cls.zone.id, + cls.services["ostype"] + ) + cls.services["virtual_machine"]["zoneid"] = cls.zone.id + cls.services["template"] = cls.template.id + cls.services["zoneid"] = cls.zone.id - cls.account = Account.create( - cls.apiclient, - cls.services["account"], - domainid=cls.domain.id - ) - cls.services["account"] = cls.account.name - cls.cleanup = [ - cls.account - ] + cls.account = Account.create( + cls.apiclient, + cls.services["account"], + domainid=cls.domain.id + ) + cls.services["account"] = cls.account.name + cls.cleanup = [ + cls.account + ] @attr(tags=["simulator", "advanced", "basic", "sg"]) def test_deployvm_firstfit(self): - """Test to deploy vm with a first fit offering - """ - #FIXME: How do we know that first fit actually happened? - self.service_offering_firstfit = ServiceOffering.create( - self.apiclient, - self.services["service_offering"], - deploymentplanner='FirstFitPlanner' - ) + """Test to deploy vm with a first fit offering + """ + #FIXME: How do we know that first fit actually happened? + self.service_offering_firstfit = ServiceOffering.create( + self.apiclient, + self.services["service_offering"], + deploymentplanner='FirstFitPlanner' + ) - self.virtual_machine = VirtualMachine.create( - self.apiclient, - self.services["virtual_machine"], - accountid=self.account.name, - zoneid=self.zone.id, - domainid=self.account.domainid, - serviceofferingid=self.service_offering_firstfit.id, - templateid=self.template.id - ) + self.virtual_machine = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + accountid=self.account.name, + zoneid=self.zone.id, + domainid=self.account.domainid, + serviceofferingid=self.service_offering_firstfit.id, + templateid=self.template.id + ) - list_vms = VirtualMachine.list(self.apiclient, id=self.virtual_machine.id) - self.debug( - "Verify listVirtualMachines response for virtual machine: %s"\ - % self.virtual_machine.id - ) - self.assertEqual( - isinstance(list_vms, list), - True, - "List VM response was not a valid list" - ) - self.assertNotEqual( - len(list_vms), - 0, - "List VM response was empty" - ) + list_vms = VirtualMachine.list(self.apiclient, id=self.virtual_machine.id) + self.debug( + "Verify listVirtualMachines response for virtual machine: %s"\ + % self.virtual_machine.id + ) + self.assertEqual( + isinstance(list_vms, list), + True, + "List VM response was not a valid list" + ) + self.assertNotEqual( + len(list_vms), + 0, + "List VM response was empty" + ) - vm = list_vms[0] - self.assertEqual( - vm.state, - "Running", - msg="VM is not in Running state" - ) + vm = list_vms[0] + self.assertEqual( + vm.state, + "Running", + msg="VM is not in Running state" + ) @attr(tags=["simulator", "advanced", "basic", "sg"]) def test_deployvm_userdispersing(self): - """Test deploy VMs using user dispersion planner - """ - self.service_offering_userdispersing = ServiceOffering.create( - self.apiclient, - self.services["service_offering"], - deploymentplanner='UserDispersingPlanner' - ) + """Test deploy VMs using user dispersion planner + """ + self.service_offering_userdispersing = ServiceOffering.create( + self.apiclient, + self.services["service_offering"], + deploymentplanner='UserDispersingPlanner' + ) - self.virtual_machine_1 = VirtualMachine.create( - self.apiclient, - self.services["virtual_machine"], - accountid=self.account.name, - zoneid=self.zone.id, - domainid=self.account.domainid, - serviceofferingid=self.service_offering_userdispersing.id, - templateid=self.template.id - ) - self.virtual_machine_2 = VirtualMachine.create( - self.apiclient, - self.services["virtual_machine"], - accountid=self.account.name, - zoneid=self.zone.id, - domainid=self.account.domainid, - serviceofferingid=self.service_offering_userdispersing.id, - templateid=self.template.id - ) + self.virtual_machine_1 = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + accountid=self.account.name, + zoneid=self.zone.id, + domainid=self.account.domainid, + serviceofferingid=self.service_offering_userdispersing.id, + templateid=self.template.id + ) + self.virtual_machine_2 = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + accountid=self.account.name, + zoneid=self.zone.id, + domainid=self.account.domainid, + serviceofferingid=self.service_offering_userdispersing.id, + templateid=self.template.id + ) - list_vm_1 = VirtualMachine.list(self.apiclient, id=self.virtual_machine_1.id) - list_vm_2 = VirtualMachine.list(self.apiclient, id=self.virtual_machine_2.id) - self.assertEqual( - isinstance(list_vm_1, list), - True, - "List VM response was not a valid list" - ) - self.assertEqual( - isinstance(list_vm_2, list), - True, - "List VM response was not a valid list" - ) - vm1 = list_vm_1[0] - vm2 = list_vm_2[0] - self.assertEqual( - vm1.state, - "Running", - msg="VM is not in Running state" - ) - self.assertEqual( - vm2.state, - "Running", - msg="VM is not in Running state" - ) - self.assertNotEqual( - vm1.hostid, - vm2.hostid, - msg="VMs meant to be dispersed are deployed on the same host" - ) + list_vm_1 = VirtualMachine.list(self.apiclient, id=self.virtual_machine_1.id) + list_vm_2 = VirtualMachine.list(self.apiclient, id=self.virtual_machine_2.id) + self.assertEqual( + isinstance(list_vm_1, list), + True, + "List VM response was not a valid list" + ) + self.assertEqual( + isinstance(list_vm_2, list), + True, + "List VM response was not a valid list" + ) + vm1 = list_vm_1[0] + vm2 = list_vm_2[0] + self.assertEqual( + vm1.state, + "Running", + msg="VM is not in Running state" + ) + self.assertEqual( + vm2.state, + "Running", + msg="VM is not in Running state" + ) + self.assertNotEqual( + vm1.hostid, + vm2.hostid, + msg="VMs meant to be dispersed are deployed on the same host" + ) @attr(tags=["simulator", "advanced", "basic", "sg"]) def test_deployvm_userconcentrated(self): - """Test deploy VMs using user concentrated planner - """ - self.service_offering_userconcentrated = ServiceOffering.create( - self.apiclient, - self.services["service_offering"], - deploymentplanner='UserConcentratedPodPlanner' - ) + """Test deploy VMs using user concentrated planner + """ + self.service_offering_userconcentrated = ServiceOffering.create( + self.apiclient, + self.services["service_offering"], + deploymentplanner='UserConcentratedPodPlanner' + ) - self.virtual_machine_1 = VirtualMachine.create( - self.apiclient, - self.services["virtual_machine"], - accountid=self.account.name, - zoneid=self.zone.id, - domainid=self.account.domainid, - serviceofferingid=self.service_offering_userconcentrated.id, - templateid=self.template.id - ) - self.virtual_machine_2 = VirtualMachine.create( - self.apiclient, - self.services["virtual_machine"], - accountid=self.account.name, - zoneid=self.zone.id, - domainid=self.account.domainid, - serviceofferingid=self.service_offering_userconcentrated.id, - templateid=self.template.id - ) + self.virtual_machine_1 = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + accountid=self.account.name, + zoneid=self.zone.id, + domainid=self.account.domainid, + serviceofferingid=self.service_offering_userconcentrated.id, + templateid=self.template.id + ) + self.virtual_machine_2 = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + accountid=self.account.name, + zoneid=self.zone.id, + domainid=self.account.domainid, + serviceofferingid=self.service_offering_userconcentrated.id, + templateid=self.template.id + ) - list_vm_1 = VirtualMachine.list(self.apiclient, id=self.virtual_machine_1.id) - list_vm_2 = VirtualMachine.list(self.apiclient, id=self.virtual_machine_2.id) - self.assertEqual( - isinstance(list_vm_1, list), - True, - "List VM response was not a valid list" - ) - self.assertEqual( - isinstance(list_vm_2, list), - True, - "List VM response was not a valid list" - ) - vm1 = list_vm_1[0] - vm2 = list_vm_2[0] - self.assertEqual( - vm1.state, - "Running", - msg="VM is not in Running state" - ) - self.assertEqual( - vm2.state, - "Running", - msg="VM is not in Running state" - ) - self.assertNotEqual( - vm1.hostid, - vm2.hostid, - msg="VMs meant to be concentrated are deployed on the different hosts" - ) + list_vm_1 = VirtualMachine.list(self.apiclient, id=self.virtual_machine_1.id) + list_vm_2 = VirtualMachine.list(self.apiclient, id=self.virtual_machine_2.id) + self.assertEqual( + isinstance(list_vm_1, list), + True, + "List VM response was not a valid list" + ) + self.assertEqual( + isinstance(list_vm_2, list), + True, + "List VM response was not a valid list" + ) + vm1 = list_vm_1[0] + vm2 = list_vm_2[0] + self.assertEqual( + vm1.state, + "Running", + msg="VM is not in Running state" + ) + self.assertEqual( + vm2.state, + "Running", + msg="VM is not in Running state" + ) + self.assertNotEqual( + vm1.hostid, + vm2.hostid, + msg="VMs meant to be concentrated are deployed on the different hosts" + ) @classmethod def tearDownClass(cls): - try: - cleanup_resources(cls.apiclient, cls.cleanup) - except Exception as e: - raise Exception("Warning: Exception during cleanup : %s" % e) + try: + cleanup_resources(cls.apiclient, cls.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) From 53499c085873859dca00ee1180720656b137e374 Mon Sep 17 00:00:00 2001 From: Murali Reddy Date: Wed, 22 May 2013 14:05:53 +0530 Subject: [PATCH 077/108] changing default 'GSLB provider' check box value to unchecked in UI --- ui/scripts/system.js | 4 ++-- ui/scripts/zoneWizard.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/scripts/system.js b/ui/scripts/system.js index 11365c1603d..31e47e4a87d 100644 --- a/ui/scripts/system.js +++ b/ui/scripts/system.js @@ -3317,7 +3317,7 @@ gslbprovider: { label: 'GSLB service', isBoolean: true, - isChecked: true + isChecked: false }, gslbproviderpublicip: { label: 'GSLB service Public IP' @@ -7009,7 +7009,7 @@ gslbprovider: { label: 'GSLB service', isBoolean: true, - isChecked: true + isChecked: false }, gslbproviderpublicip: { label: 'GSLB service Public IP' diff --git a/ui/scripts/zoneWizard.js b/ui/scripts/zoneWizard.js index 9b28c327b78..5a89cb38ee8 100755 --- a/ui/scripts/zoneWizard.js +++ b/ui/scripts/zoneWizard.js @@ -664,7 +664,7 @@ gslbprovider: { label: 'GSLB service', isBoolean: true, - isChecked: true + isChecked: false }, gslbproviderpublicip: { label: 'GSLB service Public IP' From 62d320454a5487dac27631d380cd0cc0e2492e22 Mon Sep 17 00:00:00 2001 From: Murali Reddy Date: Wed, 22 May 2013 16:25:02 +0530 Subject: [PATCH 078/108] CLOUDSTACK-2360: listnetscalerloadbalancerresponse is not including any information about GSLB status adds the infomration if NetScaler is provisioned as GSLB service provider --- .../NetscalerLoadBalancerResponse.java | 21 +++++++++++++++++++ .../network/element/NetscalerElement.java | 4 ++++ 2 files changed, 25 insertions(+) diff --git a/plugins/network-elements/netscaler/src/com/cloud/api/response/NetscalerLoadBalancerResponse.java b/plugins/network-elements/netscaler/src/com/cloud/api/response/NetscalerLoadBalancerResponse.java index bd2588054d2..1348788da25 100644 --- a/plugins/network-elements/netscaler/src/com/cloud/api/response/NetscalerLoadBalancerResponse.java +++ b/plugins/network-elements/netscaler/src/com/cloud/api/response/NetscalerLoadBalancerResponse.java @@ -60,6 +60,15 @@ public class NetscalerLoadBalancerResponse extends BaseResponse { @SerializedName(ApiConstants.IP_ADDRESS) @Param(description="the management IP address of the external load balancer") private String ipAddress; + @SerializedName(ApiConstants.GSLB_PROVIDER) @Param(description="true if NetScaler device is provisioned to be a GSLB service provider") + private Boolean isGslbProvider; + + @SerializedName(ApiConstants.GSLB_PROVIDER_PUBLIC_IP) @Param(description="public IP of the NetScaler representing GSLB site") + private String gslbSitePublicIp; + + @SerializedName(ApiConstants.GSLB_PROVIDER_PRIVATE_IP) @Param(description="private IP of the NetScaler representing GSLB site") + private String gslbSitePrivateIp; + @SerializedName(ApiConstants.POD_IDS) @Param(description="Used when NetScaler device is provider of EIP service." + " This parameter represents the list of pod's, for which there exists a policy based route on datacenter L3 router to " + "route pod's subnet IP to a NetScaler device.") @@ -108,4 +117,16 @@ public class NetscalerLoadBalancerResponse extends BaseResponse { public void setAssociatedPods(List pods) { this.podIds = pods; } + + public void setGslbProvider(boolean isGslbProvider) { + this.isGslbProvider = isGslbProvider; + } + + public void setGslbSitePublicIp(String publicIP) { + this.gslbSitePublicIp = publicIP; + } + + public void setGslbSitePrivateIp(String privateIp) { + this.gslbSitePrivateIp = privateIp; + } } diff --git a/plugins/network-elements/netscaler/src/com/cloud/network/element/NetscalerElement.java b/plugins/network-elements/netscaler/src/com/cloud/network/element/NetscalerElement.java index 850962d05ee..13a6900f2a6 100644 --- a/plugins/network-elements/netscaler/src/com/cloud/network/element/NetscalerElement.java +++ b/plugins/network-elements/netscaler/src/com/cloud/network/element/NetscalerElement.java @@ -589,6 +589,10 @@ public class NetscalerElement extends ExternalLoadBalancerDeviceManagerImpl impl response.setDeviceState(lbDeviceVO.getState().name()); response.setObjectName("netscalerloadbalancer"); + response.setGslbProvider(lbDeviceVO.getGslbProvider()); + response.setGslbSitePublicIp(lbDeviceVO.getGslbSitePublicIP()); + response.setGslbSitePrivateIp(lbDeviceVO.getGslbSitePrivateIP()); + List associatedPods = new ArrayList(); List currentPodVOs = _netscalerPodDao.listByNetScalerDeviceId(lbDeviceVO.getId()); if (currentPodVOs != null && currentPodVOs.size() > 0) { From f7eb139ce2cf85f59aa43f4f4da78fa5668fe034 Mon Sep 17 00:00:00 2001 From: Radhika PC Date: Wed, 22 May 2013 17:27:28 +0530 Subject: [PATCH 079/108] portable ip and api changes --- docs/en-US/added-API-commands-4.2.xml | 16 +++ docs/en-US/elastic-ip.xml | 161 ++++++++++++-------------- docs/en-US/portable-ip.xml | 30 +++++ 3 files changed, 120 insertions(+), 87 deletions(-) create mode 100644 docs/en-US/portable-ip.xml diff --git a/docs/en-US/added-API-commands-4.2.xml b/docs/en-US/added-API-commands-4.2.xml index 3abb780663e..177c9a0e6ea 100644 --- a/docs/en-US/added-API-commands-4.2.xml +++ b/docs/en-US/added-API-commands-4.2.xml @@ -117,5 +117,21 @@ pagesize; projectid (lists objects by project); regionid; tags (lists resources by tags: key/value pairs) + + createPortableIpAddressRange + Creates portable IP addresses from the portable public IP address pool. + The request parameters are region id, start ip, end ip, netmask, gateway, and + vlan. + + + deletePortableIpAddressRange + Deletes portable IP addresses from the portable public IP address pool. + The request parameters is portable ip address range id. + + + createPortableIpAddressRange + Lists portable IP addresses in the portable public IP address pool. + The request parameters are elastic ip id and region id. + diff --git a/docs/en-US/elastic-ip.xml b/docs/en-US/elastic-ip.xml index 672fc5aef0c..8ecbd75be70 100644 --- a/docs/en-US/elastic-ip.xml +++ b/docs/en-US/elastic-ip.xml @@ -26,91 +26,78 @@ choice from the EIP pool of your account. Later if required you can reassign the IP address to a different VM. This feature is extremely helpful during VM failure. Instead of replacing the VM which is down, the IP address can be reassigned to a new VM in your account. -
- Elastic IPs in Basic Zone - Similar to the public IP address, Elastic IP addresses are mapped to their associated - private IP addresses by using StaticNAT. The EIP service is equipped with StaticNAT (1:1) - service in an EIP-enabled basic zone. The default network offering, - DefaultSharedNetscalerEIPandELBNetworkOffering, provides your network with EIP and ELB network - services if a NetScaler device is deployed in your zone. Consider the following illustration - for more details. - - - - - - eip-ns-basiczone.png: Elastic IP in a NetScaler-enabled Basic Zone. - - - In the illustration, a NetScaler appliance is the default entry or exit point for the - &PRODUCT; instances, and firewall is the default entry or exit point for the rest of the data - center. Netscaler provides LB services and staticNAT service to the guest networks. The guest - traffic in the pods and the Management Server are on different subnets / VLANs. The - policy-based routing in the data center core switch sends the public traffic through the - NetScaler, whereas the rest of the data center goes through the firewall. - The EIP work flow is as follows: - - - When a user VM is deployed, a public IP is automatically acquired from the pool of - public IPs configured in the zone. This IP is owned by the VM's account. - - - Each VM will have its own private IP. When the user VM starts, Static NAT is - provisioned on the NetScaler device by using the Inbound Network Address Translation - (INAT) and Reverse NAT (RNAT) rules between the public IP and the private IP. - - Inbound NAT (INAT) is a type of NAT supported by NetScaler, in which the destination - IP address is replaced in the packets from the public network, such as the Internet, - with the private IP address of a VM in the private network. Reverse NAT (RNAT) is a type - of NAT supported by NetScaler, in which the source IP address is replaced in the packets - generated by a VM in the private network with the public IP address. - - - - This default public IP will be released in two cases: - - - When the VM is stopped. When the VM starts, it again receives a new public IP, not - necessarily the same one allocated initially, from the pool of Public IPs. - - - The user acquires a public IP (Elastic IP). This public IP is associated with the - account, but will not be mapped to any private IP. However, the user can enable Static - NAT to associate this IP to the private IP of a VM in the account. The Static NAT rule - for the public IP can be disabled at any time. When Static NAT is disabled, a new - public IP is allocated from the pool, which is not necessarily be the same one - allocated initially. - - - - - For the deployments where public IPs are limited resources, you have the flexibility to - choose not to allocate a public IP by default. You can use the Associate Public IP option to - turn on or off the automatic public IP assignment in the EIP-enabled Basic zones. If you turn - off the automatic public IP assignment while creating a network offering, only a private IP is - assigned to a VM when the VM is deployed with that network offering. Later, the user can - acquire an IP for the VM and enable static NAT. - For more information on the Associate Public IP option, see . - For more information on the Associate Public IP option, see the - Administration Guide. - - The Associate Public IP feature is designed only for use with user VMs. The System VMs - continue to get both public IP and private by default, irrespective of the network offering - configuration. - - New deployments which use the default shared network offering with EIP and ELB services to - create a shared network in the Basic zone will continue allocating public IPs to each user - VM. -
-
- About Portable IP - Portable IPs in &PRODUCT; are nothing but elastic IPs that can be transferred across - geographically separated zones. As an administrator, you can provision a pool of portable IPs - at region level and are available for user consumption. The users can acquire portable IPs if - admin has provisioned portable public IPs at the region level they are part of. These IPs can - be use for any service within an advanced zone. You can also use portable IPs for EIP service - in basic zones. Additionally, a portable IP can be transferred from one network to another - network. -
+ Similar to the public IP address, Elastic IP addresses are mapped to their associated + private IP addresses by using StaticNAT. The EIP service is equipped with StaticNAT (1:1) + service in an EIP-enabled basic zone. The default network offering, + DefaultSharedNetscalerEIPandELBNetworkOffering, provides your network with EIP and ELB network + services if a NetScaler device is deployed in your zone. Consider the following illustration for + more details. + + + + + + eip-ns-basiczone.png: Elastic IP in a NetScaler-enabled Basic Zone. + + + In the illustration, a NetScaler appliance is the default entry or exit point for the + &PRODUCT; instances, and firewall is the default entry or exit point for the rest of the data + center. Netscaler provides LB services and staticNAT service to the guest networks. The guest + traffic in the pods and the Management Server are on different subnets / VLANs. The policy-based + routing in the data center core switch sends the public traffic through the NetScaler, whereas + the rest of the data center goes through the firewall. + The EIP work flow is as follows: + + + When a user VM is deployed, a public IP is automatically acquired from the pool of + public IPs configured in the zone. This IP is owned by the VM's account. + + + Each VM will have its own private IP. When the user VM starts, Static NAT is provisioned + on the NetScaler device by using the Inbound Network Address Translation (INAT) and Reverse + NAT (RNAT) rules between the public IP and the private IP. + + Inbound NAT (INAT) is a type of NAT supported by NetScaler, in which the destination + IP address is replaced in the packets from the public network, such as the Internet, with + the private IP address of a VM in the private network. Reverse NAT (RNAT) is a type of NAT + supported by NetScaler, in which the source IP address is replaced in the packets + generated by a VM in the private network with the public IP address. + + + + This default public IP will be released in two cases: + + + When the VM is stopped. When the VM starts, it again receives a new public IP, not + necessarily the same one allocated initially, from the pool of Public IPs. + + + The user acquires a public IP (Elastic IP). This public IP is associated with the + account, but will not be mapped to any private IP. However, the user can enable Static + NAT to associate this IP to the private IP of a VM in the account. The Static NAT rule + for the public IP can be disabled at any time. When Static NAT is disabled, a new public + IP is allocated from the pool, which is not necessarily be the same one allocated + initially. + + + + + For the deployments where public IPs are limited resources, you have the flexibility to + choose not to allocate a public IP by default. You can use the Associate Public IP option to + turn on or off the automatic public IP assignment in the EIP-enabled Basic zones. If you turn + off the automatic public IP assignment while creating a network offering, only a private IP is + assigned to a VM when the VM is deployed with that network offering. Later, the user can acquire + an IP for the VM and enable static NAT. + For more information on the Associate Public IP option, see . + For more information on the Associate Public IP option, see the + Administration Guide. + + The Associate Public IP feature is designed only for use with user VMs. The System VMs + continue to get both public IP and private by default, irrespective of the network offering + configuration. + + New deployments which use the default shared network offering with EIP and ELB services to + create a shared network in the Basic zone will continue allocating public IPs to each user + VM. diff --git a/docs/en-US/portable-ip.xml b/docs/en-US/portable-ip.xml new file mode 100644 index 00000000000..ec1035ec204 --- /dev/null +++ b/docs/en-US/portable-ip.xml @@ -0,0 +1,30 @@ + + +%BOOK_ENTITIES; +]> + +
+ About Portable IP + Portable IPs in &PRODUCT; are nothing but elastic IPs that can be transferred across + geographically separated zones. As an administrator, you can provision a pool of portable IPs at + region level and are available for user consumption. The users can acquire portable IPs if admin + has provisioned portable public IPs at the region level they are part of. These IPs can be use + for any service within an advanced zone. You can also use portable IPs for EIP service in basic + zones. Additionally, a portable IP can be transferred from one network to another + network. +
From 85ff50709424dc726b934d2d29f046d47b610d75 Mon Sep 17 00:00:00 2001 From: Devdeep Singh Date: Wed, 22 May 2013 17:25:01 +0530 Subject: [PATCH 080/108] CLOUDSTACK-2602. XenServer storage motion strategy returns true for canHandle even though hosts are of different hypervisor type. Fixed the canHandle routine to return true only if source and destination hosts are of type XenServer. --- .../storage/motion/XenServerStorageMotionStrategy.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/plugins/hypervisors/xen/src/org/apache/cloudstack/storage/motion/XenServerStorageMotionStrategy.java b/plugins/hypervisors/xen/src/org/apache/cloudstack/storage/motion/XenServerStorageMotionStrategy.java index 353f2b58d08..42cb68d1213 100644 --- a/plugins/hypervisors/xen/src/org/apache/cloudstack/storage/motion/XenServerStorageMotionStrategy.java +++ b/plugins/hypervisors/xen/src/org/apache/cloudstack/storage/motion/XenServerStorageMotionStrategy.java @@ -19,8 +19,8 @@ package org.apache.cloudstack.storage.motion; import java.util.HashMap; -import java.util.Map; import java.util.List; +import java.util.Map; import javax.inject.Inject; @@ -50,6 +50,7 @@ import com.cloud.agent.api.to.VolumeTO; import com.cloud.exception.AgentUnavailableException; import com.cloud.exception.OperationTimedoutException; import com.cloud.host.Host; +import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.storage.StoragePool; import com.cloud.storage.VolumeVO; import com.cloud.storage.dao.VolumeDao; @@ -73,7 +74,12 @@ public class XenServerStorageMotionStrategy implements DataMotionStrategy { @Override public boolean canHandle(Map volumeMap, Host srcHost, Host destHost) { - return true; + boolean canHandle = false; + if (srcHost.getHypervisorType() == HypervisorType.XenServer && + destHost.getHypervisorType() == HypervisorType.XenServer) { + canHandle = true; + } + return canHandle; } @Override From 9a33fd181ffb9d3220edefd1f8d928915b09028a Mon Sep 17 00:00:00 2001 From: Likitha Shetty Date: Wed, 22 May 2013 16:33:08 +0530 Subject: [PATCH 081/108] CLOUDSTACK-2623. Provide appropriate AWS EC2 error codes in error thrown by CS AWSAPI. Since CS has very few generic errorcode groups, in AWSAPI parse the response message and translate the CS error to AWS EC2 error code. Provide support for the following error codes - AuthFailure, DependencyViolation, IncorrectState, InvalidAMIID.NotFound, InvalidAttachment.NotFound, InvalidDevice.InUse, InvalidFilter, InvalidGroup.Duplicate, InvalidGroup.InUse, InvalidGroup.NotFound InvalidInstanceID.NotFound, InvalidKeyPair.Duplicate, InvalidKeyPair.Format, InvalidKeyPair.NotFound, InvalidParameterCombinatio, InvalidParameterValue, InvalidPermission.Duplicate, InvalidPermission.Malformed InvalidSnapshot.NotFound, InvalidVolume.NotFound, InvalidVolumeID.Duplicate, InvalidZone.NotFound, MissingParameter, UnsupportedOperation, SignatureDoesNotMatch, InternalError, AddressLimitExceeded, InstanceLimitExceeded VolumeLimitExceeded, Unavailable, ResourceLimitExceeded CLOUDSTACK-2624. Support ModifyInstanceAttribute API in AWSAPI. 2 AWS instance attributes will be supported, 'InstanceType' and 'UserData' As per AWS EC2, to modify both the attributes, the instance must be stopped. If not throw 'IncorrectInstanceState' error --- .../cloud/bridge/service/EC2MainServlet.java | 2 +- .../cloud/bridge/service/EC2RestServlet.java | 338 +++++---- .../bridge/service/EC2SoapServiceImpl.java | 87 ++- .../service/core/ec2/EC2AddressFilterSet.java | 16 +- .../ec2/EC2AvailabilityZonesFilterSet.java | 9 +- .../bridge/service/core/ec2/EC2Engine.java | 717 ++++++++++++------ .../service/core/ec2/EC2GroupFilterSet.java | 9 +- .../service/core/ec2/EC2ImageFilterSet.java | 8 +- .../core/ec2/EC2InstanceFilterSet.java | 8 +- .../service/core/ec2/EC2KeyPairFilterSet.java | 16 +- .../core/ec2/EC2ModifyInstanceAttribute.java | 64 ++ .../service/core/ec2/EC2RegisterImage.java | 5 +- .../core/ec2/EC2SnapshotFilterSet.java | 10 +- .../service/core/ec2/EC2TagsFilterSet.java | 9 +- .../service/core/ec2/EC2VolumeFilterSet.java | 9 +- .../exception/EC2ServiceException.java | 120 +-- .../src/com/cloud/stack/CloudStackClient.java | 5 +- 17 files changed, 928 insertions(+), 504 deletions(-) create mode 100644 awsapi/src/com/cloud/bridge/service/core/ec2/EC2ModifyInstanceAttribute.java diff --git a/awsapi/src/com/cloud/bridge/service/EC2MainServlet.java b/awsapi/src/com/cloud/bridge/service/EC2MainServlet.java index 8361f6ede8a..4cf5f88232d 100644 --- a/awsapi/src/com/cloud/bridge/service/EC2MainServlet.java +++ b/awsapi/src/com/cloud/bridge/service/EC2MainServlet.java @@ -94,7 +94,7 @@ public class EC2MainServlet extends HttpServlet{ if(!isEC2APIEnabled){ //response.sendError(404, "EC2 API is disabled."); response.setStatus(404); - faultResponse(response, "404" , "EC2 API is disabled."); + faultResponse(response, "Unavailable" , "EC2 API is disabled"); return; } diff --git a/awsapi/src/com/cloud/bridge/service/EC2RestServlet.java b/awsapi/src/com/cloud/bridge/service/EC2RestServlet.java index 83645a38de8..40cabb66b68 100644 --- a/awsapi/src/com/cloud/bridge/service/EC2RestServlet.java +++ b/awsapi/src/com/cloud/bridge/service/EC2RestServlet.java @@ -39,7 +39,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; -import java.util.Set; import java.util.UUID; import javax.inject.Inject; @@ -92,10 +91,9 @@ import com.amazon.ec2.DescribeVolumesResponse; import com.amazon.ec2.DetachVolumeResponse; import com.amazon.ec2.DisassociateAddressResponse; import com.amazon.ec2.GetPasswordDataResponse; -import com.amazon.ec2.GroupItemType; import com.amazon.ec2.ImportKeyPairResponse; -import com.amazon.ec2.LaunchPermissionItemType; import com.amazon.ec2.ModifyImageAttributeResponse; +import com.amazon.ec2.ModifyInstanceAttributeResponse; import com.amazon.ec2.RebootInstancesResponse; import com.amazon.ec2.RegisterImageResponse; import com.amazon.ec2.ReleaseAddressResponse; @@ -105,9 +103,7 @@ import com.amazon.ec2.RunInstancesResponse; import com.amazon.ec2.StartInstancesResponse; import com.amazon.ec2.StopInstancesResponse; import com.amazon.ec2.TerminateInstancesResponse; -import com.cloud.bridge.model.CloudStackUserVO; import com.cloud.bridge.model.UserCredentialsVO; -import com.cloud.bridge.persist.dao.CloudStackConfigurationDao; import com.cloud.bridge.persist.dao.CloudStackUserDaoImpl; import com.cloud.bridge.persist.dao.OfferingDaoImpl; import com.cloud.bridge.persist.dao.UserCredentialsDaoImpl; @@ -143,6 +139,7 @@ import com.cloud.bridge.service.core.ec2.EC2InstanceFilterSet; import com.cloud.bridge.service.core.ec2.EC2IpPermission; import com.cloud.bridge.service.core.ec2.EC2KeyPairFilterSet; import com.cloud.bridge.service.core.ec2.EC2ModifyImageAttribute; +import com.cloud.bridge.service.core.ec2.EC2ModifyInstanceAttribute; import com.cloud.bridge.service.core.ec2.EC2RebootInstances; import com.cloud.bridge.service.core.ec2.EC2RegisterImage; import com.cloud.bridge.service.core.ec2.EC2ReleaseAddress; @@ -151,8 +148,6 @@ import com.cloud.bridge.service.core.ec2.EC2SecurityGroup; import com.cloud.bridge.service.core.ec2.EC2SnapshotFilterSet; import com.cloud.bridge.service.core.ec2.EC2StartInstances; import com.cloud.bridge.service.core.ec2.EC2StopInstances; -import com.cloud.bridge.service.core.ec2.EC2TagKeyValue; -import com.cloud.bridge.service.core.ec2.EC2TagTypeId; import com.cloud.bridge.service.core.ec2.EC2Tags; import com.cloud.bridge.service.core.ec2.EC2TagsFilterSet; import com.cloud.bridge.service.core.ec2.EC2Volume; @@ -289,6 +284,7 @@ public class EC2RestServlet extends HttpServlet { else if (action.equalsIgnoreCase( "DetachVolume" )) detachVolume(request, response); else if (action.equalsIgnoreCase( "DisassociateAddress" )) disassociateAddress(request, response); else if (action.equalsIgnoreCase( "ModifyImageAttribute" )) modifyImageAttribute(request, response); + else if (action.equalsIgnoreCase( "ModifyInstanceAttribute" )) modifyInstanceAttribute(request, response); else if (action.equalsIgnoreCase( "RebootInstances" )) rebootInstances(request, response); else if (action.equalsIgnoreCase( "RegisterImage" )) registerImage(request, response); else if (action.equalsIgnoreCase( "ReleaseAddress" )) releaseAddress(request, response); @@ -318,8 +314,14 @@ public class EC2RestServlet extends HttpServlet { } catch( EC2ServiceException e ) { response.setStatus(e.getErrorCode()); - if (e.getCause() != null && e.getCause() instanceof AxisFault) - faultResponse(response, ((AxisFault)e.getCause()).getFaultCode().getLocalPart(), e.getMessage()); + if (e.getCause() != null && e.getCause() instanceof AxisFault) { + String errorCode = ((AxisFault)e.getCause()).getFaultCode().getLocalPart(); + if (errorCode.startsWith("Client.")) // only in a SOAP API client error code is prefixed with Client. + errorCode = errorCode.split("Client.")[1]; + else if (errorCode.startsWith("Server.")) // only in a SOAP API server error code is prefixed with Server. + errorCode = errorCode.split("Server.")[1]; + faultResponse(response, errorCode, e.getMessage()); + } else { logger.error("EC2ServiceException: " + e.getMessage(), e); endResponse(response, e.toString()); @@ -395,11 +397,6 @@ public class EC2RestServlet extends HttpServlet { endResponse(response, "SetUserKeys exception " + e.getMessage()); return; } - - // prime UserContext here -// logger.debug("initializing context"); - UserContext context = UserContext.current(); - try { txn = Transaction.open(Transaction.AWSAPI_DB); // -> use the keys to see if the account actually exists @@ -681,17 +678,23 @@ public class EC2RestServlet extends HttpServlet { String[] volumeId = request.getParameterValues( "VolumeId" ); if ( null != volumeId && 0 < volumeId.length ) EC2request.setId( volumeId[0] ); - else { response.sendError(530, "Missing VolumeId parameter" ); return; } + else { + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - VolumeId"); + } String[] instanceId = request.getParameterValues( "InstanceId" ); if ( null != instanceId && 0 < instanceId.length ) EC2request.setInstanceId( instanceId[0] ); - else { response.sendError(530, "Missing InstanceId parameter" ); return; } + else { + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - InstanceId"); + } String[] device = request.getParameterValues( "Device" ); if ( null != device && 0 < device.length ) EC2request.setDevice( device[0] ); - else { response.sendError(530, "Missing Device parameter" ); return; } + else { + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - Device"); + } // -> execute the request AttachVolumeResponse EC2response = EC2SoapServiceImpl.toAttachVolumeResponse( ServiceProvider.getInstance().getEC2Engine().attachVolume( EC2request )); @@ -709,7 +712,9 @@ public class EC2RestServlet extends HttpServlet { String[] groupName = request.getParameterValues( "GroupName" ); if ( null != groupName && 0 < groupName.length ) EC2request.setName( groupName[0] ); - else { response.sendError(530, "Missing GroupName parameter" ); return; } + else { + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - GroupName"); + } // -> not clear how many parameters there are until we fail to get IpPermissions.n.IpProtocol int nCount = 1, mCount; @@ -772,8 +777,7 @@ public class EC2RestServlet extends HttpServlet { } while( true ); if (1 == nCount) { - response.sendError(530, "At least one IpPermissions required" ); - return; + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - IpPermissions"); } // -> execute the request @@ -790,7 +794,9 @@ public class EC2RestServlet extends HttpServlet { String[] groupName = request.getParameterValues( "GroupName" ); if ( null != groupName && 0 < groupName.length ) EC2request.setName( groupName[0] ); - else { response.sendError(530, "Missing GroupName parameter" ); return; } + else { + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter 'Groupname'"); + } // -> not clear how many parameters there are until we fail to get IpPermissions.n.IpProtocol int nCount = 1; @@ -852,8 +858,9 @@ public class EC2RestServlet extends HttpServlet { } while( true ); - if (1 == nCount) { response.sendError(530, "At least one IpPermissions required" ); return; } - + if (1 == nCount) { + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - IpPermissions"); + } // -> execute the request AuthorizeSecurityGroupIngressResponse EC2response = EC2SoapServiceImpl.toAuthorizeSecurityGroupIngressResponse( @@ -868,7 +875,9 @@ public class EC2RestServlet extends HttpServlet { String[] volumeId = request.getParameterValues( "VolumeId" ); if ( null != volumeId && 0 < volumeId.length ) EC2request.setId(volumeId[0]); - else { response.sendError(530, "Missing VolumeId parameter" ); return; } + else { + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter 'VolumeId'"); + } String[] instanceId = request.getParameterValues( "InstanceId" ); if ( null != instanceId && 0 < instanceId.length ) @@ -890,7 +899,9 @@ public class EC2RestServlet extends HttpServlet { String[] volumeId = request.getParameterValues( "VolumeId" ); if ( null != volumeId && 0 < volumeId.length ) EC2request.setId(volumeId[0]); - else { response.sendError(530, "Missing VolumeId parameter" ); return; } + else { + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - VolumeId"); + } // -> execute the request DeleteVolumeResponse EC2response = EC2SoapServiceImpl.toDeleteVolumeResponse( ServiceProvider.getInstance().getEC2Engine().deleteVolume( EC2request )); @@ -904,7 +915,9 @@ public class EC2RestServlet extends HttpServlet { String[] zoneName = request.getParameterValues( "AvailabilityZone" ); if ( null != zoneName && 0 < zoneName.length ) EC2request.setZoneName( zoneName[0] ); - else { response.sendError(530, "Missing AvailabilityZone parameter" ); return; } + else { + throw new EC2ServiceException( ClientError.MissingParamter, "Missing parameter - AvailabilityZone"); + } String[] size = request.getParameterValues( "Size" ); String[] snapshotId = request.getParameterValues("SnapshotId"); @@ -922,9 +935,9 @@ public class EC2RestServlet extends HttpServlet { } else if (useSnapshot && !useSize) { EC2request.setSnapshotId(snapshotId[0]); } else if (useSize && useSnapshot) { - response.sendError(530, "Size and SnapshotId parameters are mutually exclusive" ); return; + throw new EC2ServiceException( ClientError.InvalidParameterCombination, "Parameters 'Size' and 'SnapshotId' are mutually exclusive"); } else { - response.sendError(530, "Size or SnapshotId has to be specified" ); return; + throw new EC2ServiceException( ClientError.MissingParamter, "Parameter 'Size' or 'SnapshotId' has to be specified"); } @@ -941,12 +954,15 @@ public class EC2RestServlet extends HttpServlet { String[] name = request.getParameterValues( "GroupName" ); if ( null != name && 0 < name.length ) groupName = name[0]; - else { response.sendError(530, "Missing GroupName parameter" ); return; } - + else { + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - GroupName"); + } String[] desc = request.getParameterValues( "GroupDescription" ); if ( null != desc && 0 < desc.length ) groupDescription = desc[0]; - else { response.sendError(530, "Missing GroupDescription parameter" ); return; } + else { + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - GroupDescription"); + } // -> execute the request CreateSecurityGroupResponse EC2response = EC2SoapServiceImpl.toCreateSecurityGroupResponse( ServiceProvider.getInstance().getEC2Engine().createSecurityGroup( groupName, groupDescription )); @@ -960,7 +976,9 @@ public class EC2RestServlet extends HttpServlet { String[] name = request.getParameterValues( "GroupName" ); if ( null != name && 0 < name.length ) groupName = name[0]; - else { response.sendError(530, "Missing GroupName parameter" ); return; } + else { + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - GroupName"); + } // -> execute the request DeleteSecurityGroupResponse EC2response = EC2SoapServiceImpl.toDeleteSecurityGroupResponse( ServiceProvider.getInstance().getEC2Engine().deleteSecurityGroup( groupName )); @@ -974,7 +992,9 @@ public class EC2RestServlet extends HttpServlet { String[] snapSet = request.getParameterValues( "SnapshotId" ); if ( null != snapSet && 0 < snapSet.length ) snapshotId = snapSet[0]; - else { response.sendError(530, "Missing SnapshotId parameter" ); return; } + else { + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - SnapshotId"); + } // -> execute the request DeleteSnapshotResponse EC2response = EC2SoapServiceImpl.toDeleteSnapshotResponse( ServiceProvider.getInstance().getEC2Engine().deleteSnapshot( snapshotId )); @@ -988,7 +1008,9 @@ public class EC2RestServlet extends HttpServlet { String[] volSet = request.getParameterValues( "VolumeId" ); if ( null != volSet && 0 < volSet.length ) volumeId = volSet[0]; - else { response.sendError(530, "Missing VolumeId parameter" ); return; } + else { + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - VolumeId"); + } // -> execute the request EC2Engine engine = ServiceProvider.getInstance().getEC2Engine(); @@ -1003,7 +1025,9 @@ public class EC2RestServlet extends HttpServlet { String[] imageId = request.getParameterValues( "ImageId" ); if ( null != imageId && 0 < imageId.length ) image.setId( imageId[0] ); - else { response.sendError(530, "Missing ImageId parameter" ); return; } + else { + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - ImageId"); + } // -> execute the request DeregisterImageResponse EC2response = EC2SoapServiceImpl.toDeregisterImageResponse( ServiceProvider.getInstance().getEC2Engine().deregisterImage( image )); @@ -1017,16 +1041,25 @@ public class EC2RestServlet extends HttpServlet { String[] instanceId = request.getParameterValues( "InstanceId" ); if ( null != instanceId && 0 < instanceId.length ) EC2request.setInstanceId( instanceId[0] ); - else { response.sendError(530, "Missing InstanceId parameter" ); return; } + else { + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - InstanceId"); + } String[] name = request.getParameterValues( "Name" ); if ( null != name && 0 < name.length ) EC2request.setName( name[0] ); - else { response.sendError(530, "Missing Name parameter" ); return; } + else { + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - Name"); + } String[] description = request.getParameterValues( "Description" ); - if ( null != description && 0 < description.length ) + if ( null != description && 0 < description.length ) { + if (description[0].length() > 255) + throw new EC2ServiceException( ClientError.InvalidParameterValue, + "Length of the value of parameter Description should be less than 255"); EC2request.setDescription( description[0] ); + } + // -> execute the request CreateImageResponse EC2response = EC2SoapServiceImpl.toCreateImageResponse( ServiceProvider.getInstance().getEC2Engine().createImage( EC2request )); @@ -1040,16 +1073,23 @@ public class EC2RestServlet extends HttpServlet { String[] location = request.getParameterValues( "ImageLocation" ); if ( null != location && 0 < location.length ) EC2request.setLocation( location[0] ); - else { response.sendError(530, "Missing ImageLocation parameter" ); return; } + else { + throw new EC2ServiceException( ClientError.MissingParamter, "Missing parameter - ImageLocation"); + } String[] cloudRedfined = request.getParameterValues( "Architecture" ); if ( null != cloudRedfined && 0 < cloudRedfined.length ) EC2request.setArchitecture( cloudRedfined[0] ); - else { response.sendError(530, "Missing Architecture parameter" ); return; } + else { + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - Architecture"); + } String[] name = request.getParameterValues( "Name" ); if ( null != name && 0 < name.length ) EC2request.setName( name[0] ); + else { + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - Name"); + } String[] description = request.getParameterValues( "Description" ); if ( null != description && 0 < description.length ) @@ -1068,9 +1108,8 @@ public class EC2RestServlet extends HttpServlet { if ( imageId != null && imageId.length > 0 ) ec2request.setImageId( imageId[0]); else { - response.sendError(530, "Missing ImageId parameter" ); - return; - } + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - ImageId"); + } String[] description = request.getParameterValues( "Description.Value" ); if ( description != null && description.length > 0 ) { @@ -1082,8 +1121,8 @@ public class EC2RestServlet extends HttpServlet { if (ec2request.getLaunchPermissionSet().length > 0) ec2request.setAttribute(ImageAttribute.launchPermission); else { - response.sendError(530, "Missing Attribute parameter - Description/LaunchPermission should be provided" ); - return; + throw new EC2ServiceException( ClientError.MissingParamter, + "Missing required parameter - Description/LaunchPermission should be provided"); } } @@ -1135,8 +1174,7 @@ public class EC2RestServlet extends HttpServlet { if ( imageId != null && imageId.length > 0) ec2request.setImageId(imageId[0]); else { - response.sendError(530, "Missing ImageId parameter" ); - return; + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - ImageId"); } String[] attribute = request.getParameterValues( "Attribute" ); @@ -1144,12 +1182,11 @@ public class EC2RestServlet extends HttpServlet { if (attribute[0].equalsIgnoreCase("launchPermission")) ec2request.setAttribute(ImageAttribute.launchPermission); else { - response.sendError(501, "Unsupported Attribute - only launchPermission supported" ); - return; + throw new EC2ServiceException( ClientError.MissingParamter, + "Missing required parameter - Description/LaunchPermission should be provided"); } } else { - response.sendError(530, "Missing Attribute parameter" ); - return; + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - Attribute"); } EC2ImageLaunchPermission launchPermission = new EC2ImageLaunchPermission(); @@ -1170,12 +1207,13 @@ public class EC2RestServlet extends HttpServlet { String[] imageId = request.getParameterValues( "ImageId" ); if ( null != imageId && 0 < imageId.length ) EC2request.setTemplateId( imageId[0] ); - else { response.sendError(530, "Missing ImageId parameter" ); return; } + else { + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - ImageId"); + } String[] minCount = request.getParameterValues( "MinCount" ); if ( minCount == null || minCount.length < 1) { - response.sendError(530, "Missing MinCount parameter" ); - return; + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - MinCount"); } else if ( Integer.parseInt(minCount[0]) < 1) { throw new EC2ServiceException(ClientError.InvalidParameterValue, "Value of parameter MinCount should be greater than 0"); @@ -1185,8 +1223,7 @@ public class EC2RestServlet extends HttpServlet { String[] maxCount = request.getParameterValues( "MaxCount" ); if ( maxCount == null || maxCount.length < 1) { - response.sendError(530, "Missing MaxCount parameter" ); - return; + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - MaxCount"); } else if ( Integer.parseInt(maxCount[0]) < 1) { throw new EC2ServiceException(ClientError.InvalidParameterValue, "Value of parameter MaxCount should be greater than 0"); @@ -1250,7 +1287,9 @@ public class EC2RestServlet extends HttpServlet { } } } - if (0 == count) { response.sendError(530, "Missing InstanceId parameter" ); return; } + if (0 == count) { + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - InstanceId"); + } // -> execute the request RebootInstancesResponse EC2response = EC2SoapServiceImpl.toRebootInstancesResponse( ServiceProvider.getInstance().getEC2Engine().rebootInstances(EC2request)); @@ -1274,7 +1313,9 @@ public class EC2RestServlet extends HttpServlet { } } } - if (0 == count) { response.sendError(530, "Missing InstanceId parameter" ); return; } + if (0 == count) { + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - InstanceId"); + } // -> execute the request StartInstancesResponse EC2response = EC2SoapServiceImpl.toStartInstancesResponse( ServiceProvider.getInstance().getEC2Engine().startInstances(EC2request)); @@ -1298,7 +1339,9 @@ public class EC2RestServlet extends HttpServlet { } } } - if (0 == count) { response.sendError(530, "Missing InstanceId parameter" ); return; } + if (0 == count) { + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - InstanceId"); + } String[] force = request.getParameterValues("Force"); if ( force != null) { @@ -1327,7 +1370,9 @@ public class EC2RestServlet extends HttpServlet { } } } - if (0 == count) { response.sendError(530, "Missing InstanceId parameter" ); return; } + if (0 == count) { + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - InstanceId"); + } // -> execute the request EC2request.setDestroyInstances( true ); @@ -1364,7 +1409,8 @@ public class EC2RestServlet extends HttpServlet { } // -> execute the request - DescribeAvailabilityZonesResponse EC2response = EC2SoapServiceImpl.toDescribeAvailabilityZonesResponse( ServiceProvider.getInstance().getEC2Engine().handleRequest( EC2request )); + DescribeAvailabilityZonesResponse EC2response = EC2SoapServiceImpl.toDescribeAvailabilityZonesResponse( + ServiceProvider.getInstance().getEC2Engine().describeAvailabilityZones( EC2request )); serializeResponse(response, EC2response); } @@ -1404,8 +1450,7 @@ public class EC2RestServlet extends HttpServlet { if (imageId != null && imageId.length > 0) ec2request.setImageId(imageId[0]); else { - response.sendError(530, "Missing ImageId parameter"); - return; + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - ImageId"); } String[] attribute = request.getParameterValues( "Attribute" ); @@ -1415,12 +1460,11 @@ public class EC2RestServlet extends HttpServlet { else if (attribute[0].equalsIgnoreCase("launchPermission")) ec2request.setAttribute(ImageAttribute.launchPermission); else { - response.sendError(501, "Unsupported Attribute - description and launchPermission supported" ); - return; + throw new EC2ServiceException( ClientError.InvalidParameterValue, + "Only values supported for paramter Attribute are - Description/LaunchPermission"); } } else { - response.sendError(530, "Missing Attribute parameter"); - return; + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - Attribute"); } DescribeImageAttributeResponse EC2response = EC2SoapServiceImpl.toDescribeImageAttributeResponse( ServiceProvider.getInstance().getEC2Engine().describeImageAttribute( ec2request )); @@ -1501,9 +1545,8 @@ public class EC2RestServlet extends HttpServlet { EC2Engine engine = ServiceProvider.getInstance().getEC2Engine(); String publicIp = request.getParameter( "PublicIp" ); - if (publicIp == null) { - response.sendError(530, "Missing PublicIp parameter"); - return; + if (publicIp == null) { + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - PublicIp"); } EC2ReleaseAddress ec2Request = new EC2ReleaseAddress(); @@ -1522,13 +1565,11 @@ public class EC2RestServlet extends HttpServlet { String publicIp = request.getParameter( "PublicIp" ); if (null == publicIp) { - response.sendError(530, "Missing PublicIp parameter" ); - return; + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - PublicIp"); } String instanceId = request.getParameter( "InstanceId" ); if (null == instanceId) { - response.sendError(530, "Missing InstanceId parameter" ); - return; + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - InstanceId"); } EC2AssociateAddress ec2Request = new EC2AssociateAddress(); @@ -1548,8 +1589,7 @@ public class EC2RestServlet extends HttpServlet { String publicIp = request.getParameter( "PublicIp" ); if (null == publicIp) { - response.sendError(530, "Missing PublicIp parameter" ); - return; + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - PublicIp"); } EC2DisassociateAddress ec2Request = new EC2DisassociateAddress(); @@ -1597,27 +1637,20 @@ public class EC2RestServlet extends HttpServlet { private void describeInstanceAttribute( HttpServletRequest request, HttpServletResponse response ) throws ADBException, XMLStreamException, IOException { EC2DescribeInstances EC2request = new EC2DescribeInstances(); - String instanceType = null; + String[] instanceId = request.getParameterValues( "InstanceId" ); + if ( instanceId != null && instanceId.length > 0) + EC2request.addInstanceId( instanceId[0] ); + else + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - InstanceId"); - // -> we are only handling queries about the "Attribute=instanceType" - Enumeration names = request.getParameterNames(); - while( names.hasMoreElements()) { - String key = (String)names.nextElement(); - if (key.startsWith("Attribute")) { - String[] value = request.getParameterValues( key ); - if (null != value && 0 < value.length && value[0].equalsIgnoreCase( "instanceType" )) { - instanceType = value[0]; - break; - } + String[] attribute = request.getParameterValues( "Attribute" ); + if (attribute != null && attribute.length > 0) { + if ( !attribute[0].equalsIgnoreCase("instanceType") ) { + throw new EC2ServiceException( ClientError.InvalidParameterValue, + "Only value supported for paramter Attribute is 'instanceType'"); } - } - if ( null != instanceType ) { - String[] value = request.getParameterValues( "InstanceId" ); - EC2request.addInstanceId( value[0] ); - } - else { - response.sendError(501, "Unsupported - only instanceType supported" ); - return; + } else { + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - Attribute"); } // -> execute the request @@ -1625,6 +1658,38 @@ public class EC2RestServlet extends HttpServlet { serializeResponse(response, EC2response); } + private void modifyInstanceAttribute(HttpServletRequest request, HttpServletResponse response) + throws ADBException, XMLStreamException, IOException { + EC2ModifyInstanceAttribute ec2Request = new EC2ModifyInstanceAttribute(); + + String[] instanceId = request.getParameterValues( "InstanceId" ); + if ( instanceId != null && instanceId.length > 0 ) + ec2Request.setInstanceId(instanceId[0]); + else { + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - InstanceId"); + } + + String[] instanceType = request.getParameterValues( "InstanceType.Value" ); + String[] userData = request.getParameterValues( "UserData.Value" ); + + if ( instanceType != null && userData != null ) { + throw new EC2ServiceException( ClientError.InvalidParameterCombination, "Only one attribute can be" + + " specified at a time"); + } + if ( instanceType != null && instanceType.length > 0 ) { + ec2Request.setInstanceType(instanceType[0]); + } else if ( userData != null && userData.length > 0 ) { + ec2Request.setUserData(userData[0]); + } else { + throw new EC2ServiceException( ClientError.MissingParamter, + "Missing parameter - InstanceType/UserData should be provided"); + } + + // -> execute the request + ModifyInstanceAttributeResponse EC2response = EC2SoapServiceImpl.toModifyInstanceAttributeResponse( + ServiceProvider.getInstance().getEC2Engine().modifyInstanceAttribute( ec2Request )); + serializeResponse(response, EC2response); + } private void describeSnapshots( HttpServletRequest request, HttpServletResponse response ) throws ADBException, XMLStreamException, IOException @@ -1653,7 +1718,8 @@ public class EC2RestServlet extends HttpServlet { // -> execute the request EC2Engine engine = ServiceProvider.getInstance().getEC2Engine(); - DescribeSnapshotsResponse EC2response = EC2SoapServiceImpl.toDescribeSnapshotsResponse( engine.handleRequest( EC2request )); + DescribeSnapshotsResponse EC2response = EC2SoapServiceImpl.toDescribeSnapshotsResponse( + engine.describeSnapshots( EC2request )); serializeResponse(response, EC2response); } @@ -1685,7 +1751,9 @@ public class EC2RestServlet extends HttpServlet { } // -> execute the request - DescribeVolumesResponse EC2response = EC2SoapServiceImpl.toDescribeVolumesResponse( ServiceProvider.getInstance().getEC2Engine().handleRequest( EC2request )); + EC2Engine engine = ServiceProvider.getInstance().getEC2Engine(); + DescribeVolumesResponse EC2response = EC2SoapServiceImpl.toDescribeVolumesResponse( + ServiceProvider.getInstance().getEC2Engine().describeVolumes( EC2request ), engine); serializeResponse(response, EC2response); } @@ -1779,10 +1847,13 @@ public class EC2RestServlet extends HttpServlet { throws ADBException, XMLStreamException, IOException { String keyName = request.getParameter("KeyName"); + if ( keyName == null ) { + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - KeyName"); + } + String publicKeyMaterial = request.getParameter("PublicKeyMaterial"); - if (keyName==null && publicKeyMaterial==null) { - response.sendError(530, "Missing parameter"); - return; + if ( publicKeyMaterial == null ) { + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - PublicKeyMaterial"); } if (!publicKeyMaterial.contains(" ")) @@ -1804,9 +1875,8 @@ public class EC2RestServlet extends HttpServlet { private void createKeyPair(HttpServletRequest request, HttpServletResponse response) throws ADBException, XMLStreamException, IOException { String keyName = request.getParameter("KeyName"); - if (keyName==null) { - response.sendError(530, "Missing KeyName parameter"); - return; + if (keyName==null) { + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - KeyName"); } EC2CreateKeyPair ec2Request = new EC2CreateKeyPair(); @@ -1823,8 +1893,7 @@ public class EC2RestServlet extends HttpServlet { throws ADBException, XMLStreamException, IOException { String keyName = request.getParameter("KeyName"); if (keyName==null) { - response.sendError(530, "Missing KeyName parameter"); - return; + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - KeyName"); } EC2DeleteKeyPair ec2Request = new EC2DeleteKeyPair(); @@ -1839,8 +1908,7 @@ public class EC2RestServlet extends HttpServlet { throws ADBException, XMLStreamException, IOException { String instanceId = request.getParameter("InstanceId"); if (instanceId==null) { - response.sendError(530, "Missing InstanceId parameter"); - return; + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - InstanceId"); } GetPasswordDataResponse EC2Response = EC2SoapServiceImpl.toGetPasswordData( @@ -1881,8 +1949,7 @@ public class EC2RestServlet extends HttpServlet { nCount++; } while (true); if ( resourceIdList.isEmpty() ) { - response.sendError(530, "At least one Resource is required" ); - return null; + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - ResourceId"); } ec2Request = EC2SoapServiceImpl.toResourceTypeAndIds(ec2Request, resourceIdList); @@ -1899,8 +1966,7 @@ public class EC2RestServlet extends HttpServlet { nCount++; } while (true); if ( resourceTagList.isEmpty() ) { - response.sendError(530, "At least one Tag is required" ); - return null; + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - ResourceTag"); } ec2Request = EC2SoapServiceImpl.toResourceTag(ec2Request, resourceTagList); @@ -1944,69 +2010,76 @@ public class EC2RestServlet extends HttpServlet { String[] awsAccess = request.getParameterValues( "AWSAccessKeyId" ); if ( null != awsAccess && 0 < awsAccess.length ) cloudAccessKey = awsAccess[0]; - else { response.sendError(530, "Missing AWSAccessKeyId parameter" ); return false; } + else { + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - AWSAccessKeyId"); + } String[] clientSig = request.getParameterValues( "Signature" ); if ( null != clientSig && 0 < clientSig.length ) signature = clientSig[0]; - else { response.sendError(530, "Missing Signature parameter" ); return false; } + else { + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - Signature"); + } String[] method = request.getParameterValues( "SignatureMethod" ); if ( null != method && 0 < method.length ) { sigMethod = method[0]; if (!sigMethod.equals( "HmacSHA256" ) && !sigMethod.equals( "HmacSHA1" )) { - response.sendError(531, "Unsupported SignatureMethod value: " + sigMethod + " expecting: HmacSHA256 or HmacSHA1" ); - return false; + throw new EC2ServiceException( ClientError.InvalidParameterValue, + "Unsupported SignatureMethod value: " + sigMethod + " expecting: HmacSHA256 or HmacSHA1"); } + } else { + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - SignatureMethod"); } - else { response.sendError(530, "Missing SignatureMethod parameter" ); return false; } String[] version = request.getParameterValues( "Version" ); if ( null != version && 0 < version.length ) { if (!version[0].equals( wsdlVersion )) { - response.sendError(531, "Unsupported Version value: " + version[0] + " expecting: " + wsdlVersion ); - return false; + throw new EC2ServiceException( ClientError.InvalidParameterValue, + "Unsupported Version value: " + version[0] + " expecting: " + wsdlVersion); } + } else { + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - Version"); } - else { response.sendError(530, "Missing Version parameter" ); return false; } String[] sigVersion = request.getParameterValues( "SignatureVersion" ); if ( null != sigVersion && 0 < sigVersion.length ) { if (!sigVersion[0].equals( "2" )) { - response.sendError(531, "Unsupported SignatureVersion value: " + sigVersion[0] + " expecting: 2" ); - return false; + throw new EC2ServiceException( ClientError.InvalidParameterValue, + "Unsupported SignatureVersion value: " + sigVersion[0] + " expecting: 2"); } } - else { response.sendError(530, "Missing SignatureVersion parameter" ); return false; } + else { + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter - SignatureVersion"); + } // -> can have only one but not both { Expires | Timestamp } headers String[] expires = request.getParameterValues( "Expires" ); if ( null != expires && 0 < expires.length ) { // -> contains the date and time at which the signature included in the request EXPIRES - if (hasSignatureExpired( expires[0] )) { - response.sendError(531, "Expires parameter indicates signature has expired: " + expires[0] ); - return false; + if (hasSignatureExpired( expires[0] )) { //InvalidSecurity.RequestHasExpired + throw new EC2ServiceException( ClientError.InvalidSecurity_RequestHasExpired, + "Expires parameter indicates signature has expired: " + expires[0]); } } else { // -> contains the date and time at which the request is SIGNED String[] time = request.getParameterValues( "Timestamp" ); if ( null == time || 0 == time.length ) { - response.sendError(530, "Missing Timestamp and Expires parameter, one is required" ); - return false; + throw new EC2ServiceException( ClientError.MissingParamter, "Missing required parameter -" + + " Timestamp/Expires"); } } // [B] Use the access key to get the users secret key from the cloud DB cloudSecretKey = userDao.getSecretKeyByAccessKey( cloudAccessKey ); if ( cloudSecretKey == null ) { - logger.debug("No Secret key found for Access key '" + cloudAccessKey + "' in the the EC2 service"); - throw new EC2ServiceException( ClientError.AuthFailure, "No Secret key found for Access key '" + cloudAccessKey + - "' in the the EC2 service" ); + logger.debug( "Access key '" + cloudAccessKey + "' not found in the the EC2 service "); + throw new EC2ServiceException( ClientError.AuthFailure, "Access key '" + cloudAccessKey + "' not found in the the EC2 service "); } // [C] Verify the signature @@ -2050,8 +2123,9 @@ public class EC2RestServlet extends HttpServlet { UserContext.current().initContext( cloudAccessKey, cloudSecretKey, cloudAccessKey, "REST request", null ); return true; } - else throw new PermissionDeniedException("Invalid signature"); - } + else throw new EC2ServiceException( ClientError.SignatureDoesNotMatch, + "The request signature calculated does not match the signature provided by the user."); + } /** * We check this to reduce replay attacks. diff --git a/awsapi/src/com/cloud/bridge/service/EC2SoapServiceImpl.java b/awsapi/src/com/cloud/bridge/service/EC2SoapServiceImpl.java index dc0d993e0b8..f6ac9711a0d 100644 --- a/awsapi/src/com/cloud/bridge/service/EC2SoapServiceImpl.java +++ b/awsapi/src/com/cloud/bridge/service/EC2SoapServiceImpl.java @@ -53,6 +53,7 @@ import com.cloud.bridge.service.core.ec2.EC2DescribeKeyPairs; import com.cloud.bridge.service.core.ec2.EC2DescribeKeyPairsResponse; import com.cloud.bridge.service.core.ec2.EC2ImageFilterSet; import com.cloud.bridge.service.core.ec2.EC2ImageLaunchPermission; +import com.cloud.bridge.service.core.ec2.EC2ModifyInstanceAttribute; import com.cloud.bridge.service.core.ec2.EC2ResourceTag; import com.cloud.bridge.service.core.ec2.EC2DescribeSecurityGroups; import com.cloud.bridge.service.core.ec2.EC2DescribeSecurityGroupsResponse; @@ -96,7 +97,6 @@ import com.cloud.bridge.service.core.ec2.EC2Volume; import com.cloud.bridge.service.core.ec2.EC2VolumeFilterSet; import com.cloud.bridge.service.exception.EC2ServiceException; import com.cloud.bridge.service.exception.EC2ServiceException.ClientError; -import com.cloud.bridge.service.exception.EC2ServiceException.ServerError; import com.cloud.bridge.util.EC2RestAuth; @@ -273,8 +273,8 @@ public class EC2SoapServiceImpl implements AmazonEC2SkeletonInterface { List resourceTypeList = new ArrayList(); for (String resourceId : resourceIdList) { if (!resourceId.contains(":") || resourceId.split(":").length != 2) { - throw new EC2ServiceException( ClientError.InvalidResourceId_Format, - "Invalid Format. ResourceId format is resource-type:resource-uuid"); + throw new EC2ServiceException( ClientError.InvalidParameterValue, + "Invalid usage. ResourceId format is resource-type:resource-uuid"); } String resourceType = resourceId.split(":")[0]; if (resourceTypeList.isEmpty()) @@ -356,7 +356,7 @@ public class EC2SoapServiceImpl implements AmazonEC2SkeletonInterface { request.setFilterSet( toAvailabiltyZonesFilterSet(fst)); } - return toDescribeAvailabilityZonesResponse( engine.handleRequest( request )); + return toDescribeAvailabilityZonesResponse( engine.describeAvailabilityZones( request )); } /** @@ -422,11 +422,11 @@ public class EC2SoapServiceImpl implements AmazonEC2SkeletonInterface { EmptyElementType instanceType = diag.getInstanceType(); // -> toEC2DescribeInstances - if (null != instanceType) { - request.addInstanceId( diat.getInstanceId()); + if (null != instanceType) { + request.addInstanceId( diat.getInstanceId()); return toDescribeInstanceAttributeResponse( engine.describeInstances( request )); } - throw new EC2ServiceException( ClientError.Unsupported, "Unsupported - only instanceType supported"); + throw new EC2ServiceException( ClientError.Unsupported, "Unsupported - only instanceType supported"); } @@ -549,7 +549,7 @@ public class EC2SoapServiceImpl implements AmazonEC2SkeletonInterface { request = toSnapshotFilterSet( request, fst, timeFilters ); } - return toDescribeSnapshotsResponse(engine.handleRequest(request)); + return toDescribeSnapshotsResponse(engine.describeSnapshots(request)); } public DescribeTagsResponse describeTags(DescribeTags decsribeTags) { @@ -588,7 +588,7 @@ public class EC2SoapServiceImpl implements AmazonEC2SkeletonInterface { request = toVolumeFilterSet( request, fst, timeFilters ); } - return toDescribeVolumesResponse( engine.handleRequest( request )); + return toDescribeVolumesResponse( engine.describeVolumes( request ), engine); } public DetachVolumeResponse detachVolume(DetachVolume detachVolume) { @@ -629,6 +629,26 @@ public class EC2SoapServiceImpl implements AmazonEC2SkeletonInterface { throw new EC2ServiceException( ClientError.Unsupported, "Unsupported - can only modify image description or launchPermission"); } + public ModifyInstanceAttributeResponse modifyInstanceAttribute(ModifyInstanceAttribute modifyInstanceAttribute) { + EC2ModifyInstanceAttribute request = new EC2ModifyInstanceAttribute(); + + ModifyInstanceAttributeType modifyInstanceAttribute2 = modifyInstanceAttribute.getModifyInstanceAttribute(); + ModifyInstanceAttributeTypeChoice_type0 mia = modifyInstanceAttribute2.getModifyInstanceAttributeTypeChoice_type0(); + + request.setInstanceId(modifyInstanceAttribute2.getInstanceId()); + + // we only support instanceType and userData + if (mia.getInstanceType() != null) { + request.setInstanceType(mia.getInstanceType().getValue()); + } else if (mia.getUserData() != null) { + request.setUserData(mia.getUserData().getValue()); + } else { + throw new EC2ServiceException( ClientError.MissingParamter, + "Missing required parameter - InstanceType/UserData should be provided"); + } + return toModifyInstanceAttributeResponse(engine.modifyInstanceAttribute(request)); + } + private void setAccountOrGroupList(LaunchPermissionItemType[] items, EC2ModifyImageAttribute request, String operation){ EC2ImageLaunchPermission launchPermission = new EC2ImageLaunchPermission(); @@ -858,26 +878,20 @@ public class EC2SoapServiceImpl implements AmazonEC2SkeletonInterface { response.setUnmonitorInstancesResponse( param1 ); return response; } - - - public static DescribeImageAttributeResponse toDescribeImageAttributeResponse(EC2DescribeImagesResponse engineResponse) { - DescribeImageAttributeResponse response = new DescribeImageAttributeResponse(); - DescribeImageAttributeResponseType param1 = new DescribeImageAttributeResponseType(); - - EC2Image[] imageSet = engineResponse.getImageSet(); - if ( 0 < imageSet.length ) { - DescribeImageAttributeResponseTypeChoice_type0 param2 = new DescribeImageAttributeResponseTypeChoice_type0(); - NullableAttributeValueType param3 = new NullableAttributeValueType(); - param3.setValue( imageSet[0].getDescription()); - param2.setDescription( param3 ); - param1.setDescribeImageAttributeResponseTypeChoice_type0( param2 ); - param1.setImageId( imageSet[0].getId()); - } - - param1.setRequestId( UUID.randomUUID().toString()); - response.setDescribeImageAttributeResponse( param1 ); - return response; - } + + /** + * @param modifyInstanceAttribute + * @return + */ + public static ModifyInstanceAttributeResponse toModifyInstanceAttributeResponse(Boolean status) { + ModifyInstanceAttributeResponse miat = new ModifyInstanceAttributeResponse(); + + ModifyInstanceAttributeResponseType param = new ModifyInstanceAttributeResponseType(); + param.set_return(status); + param.setRequestId(UUID.randomUUID().toString()); + miat.setModifyInstanceAttributeResponse(param); + return miat; + } public static DescribeImageAttributeResponse toDescribeImageAttributeResponse(EC2ImageAttributes engineResponse) { DescribeImageAttributeResponse response = new DescribeImageAttributeResponse(); @@ -1311,7 +1325,7 @@ public class EC2SoapServiceImpl implements AmazonEC2SkeletonInterface { } // toMethods - public static DescribeVolumesResponse toDescribeVolumesResponse( EC2DescribeVolumesResponse engineResponse ) + public static DescribeVolumesResponse toDescribeVolumesResponse( EC2DescribeVolumesResponse engineResponse, EC2Engine engine ) { DescribeVolumesResponse response = new DescribeVolumesResponse(); DescribeVolumesResponseType param1 = new DescribeVolumesResponseType(); @@ -1342,8 +1356,8 @@ public class EC2SoapServiceImpl implements AmazonEC2SkeletonInterface { AttachmentSetItemResponseType param5 = new AttachmentSetItemResponseType(); param5.setVolumeId(vol.getId().toString()); param5.setInstanceId(vol.getInstanceId().toString()); - String devicePath = engine.cloudDeviceIdToDevicePath( vol.getHypervisor(), vol.getDeviceId()); - param5.setDevice( devicePath ); + String devicePath = engine.cloudDeviceIdToDevicePath( vol.getHypervisor(), vol.getDeviceId()); + param5.setDevice( devicePath ); param5.setStatus(vol.getAttachmentState()); if (vol.getAttached() == null) { param5.setAttachTime( cal ); @@ -1876,7 +1890,10 @@ public class EC2SoapServiceImpl implements AmazonEC2SkeletonInterface { param1.setVolumeId( engineResponse.getId().toString()); Long volSize = new Long( engineResponse.getSize()); param1.setSize( volSize.toString()); - param1.setSnapshotId( "" ); + if (engineResponse.getSnapshotId() != null) + param1.setSnapshotId( engineResponse.getSnapshotId() ); + else + param1.setSnapshotId( "" ); param1.setAvailabilityZone( engineResponse.getZoneName()); if ( null != engineResponse.getState()) param1.setStatus( engineResponse.getState()); @@ -2503,10 +2520,6 @@ public class EC2SoapServiceImpl implements AmazonEC2SkeletonInterface { public ImportVolumeResponse importVolume(ImportVolume importVolume) { throw new EC2ServiceException(ClientError.Unsupported, "This operation is not available"); } - - public ModifyInstanceAttributeResponse modifyInstanceAttribute(ModifyInstanceAttribute modifyInstanceAttribute) { - throw new EC2ServiceException(ClientError.Unsupported, "This operation is not available"); - } public ModifySnapshotAttributeResponse modifySnapshotAttribute(ModifySnapshotAttribute modifySnapshotAttribute) { throw new EC2ServiceException(ClientError.Unsupported, "This operation is not available"); diff --git a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2AddressFilterSet.java b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2AddressFilterSet.java index 1823b26218c..153fdde86df 100644 --- a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2AddressFilterSet.java +++ b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2AddressFilterSet.java @@ -26,6 +26,9 @@ import java.util.Map; import org.apache.log4j.Logger; +import com.cloud.bridge.service.exception.EC2ServiceException; +import com.cloud.bridge.service.exception.EC2ServiceException.ClientError; + public class EC2AddressFilterSet { protected final static Logger logger = Logger.getLogger(EC2KeyPairFilterSet.class); @@ -43,16 +46,9 @@ public class EC2AddressFilterSet { String filterName = param.getName(); String value = (String) filterTypes.get( filterName ); - if (null == value) { - // Changing this to silently ignore - logger.error("Unsupported filter [" + filterName + "] - 1"); - return; - } - - if (null != value && value.equalsIgnoreCase( "null" )) { - logger.error("Unsupported filter [" + filterName + "] - 2"); - return; - } + if ( value == null || value.equalsIgnoreCase("null") ) { + throw new EC2ServiceException( ClientError.InvalidFilter, "Filter '" + filterName + "' is invalid"); + } // ToDo we could add checks to make sure the type of a filters value is correct (e.g., an integer) filterSet.add( param ); diff --git a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2AvailabilityZonesFilterSet.java b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2AvailabilityZonesFilterSet.java index aa3897a4bf6..7dba8ab009e 100644 --- a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2AvailabilityZonesFilterSet.java +++ b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2AvailabilityZonesFilterSet.java @@ -26,6 +26,7 @@ import java.util.Map; import com.cloud.bridge.service.exception.EC2ServiceException; +import com.cloud.bridge.service.exception.EC2ServiceException.ClientError; public class EC2AvailabilityZonesFilterSet { @@ -43,11 +44,9 @@ public class EC2AvailabilityZonesFilterSet { String filterName = param.getName(); String value = (String) filterTypes.get( filterName ); - if (null == value) - throw new EC2ServiceException( "Unsupported filter [" + filterName + "]", 501 ); - - if (null != value && value.equalsIgnoreCase( "null" )) - throw new EC2ServiceException( "Unsupported filter [" + filterName + "]", 501 ); + if ( value == null || value.equalsIgnoreCase("null") ) { + throw new EC2ServiceException( ClientError.InvalidFilter, "Filter '" + filterName + "' is invalid"); + } filterSet.add( param ); } diff --git a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2Engine.java b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2Engine.java index 9ac2bc68a88..1859bb807e4 100644 --- a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2Engine.java +++ b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2Engine.java @@ -25,12 +25,11 @@ import java.security.SignatureException; import java.sql.SQLException; import java.text.ParseException; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; -import java.util.Set; import java.util.UUID; import javax.inject.Inject; @@ -67,7 +66,6 @@ import com.cloud.stack.models.CloudStackPasswordData; import com.cloud.stack.models.CloudStackResourceLimit; import com.cloud.stack.models.CloudStackResourceTag; import com.cloud.stack.models.CloudStackSecurityGroup; -import com.cloud.stack.models.CloudStackSecurityGroupIngress; import com.cloud.stack.models.CloudStackSnapshot; import com.cloud.stack.models.CloudStackTemplate; import com.cloud.stack.models.CloudStackTemplatePermission; @@ -227,7 +225,7 @@ public class EC2Engine extends ManagerBase { return true; } catch(Exception e) { logger.error("Validate account failed!"); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage()); + throw new EC2ServiceException(ClientError.AuthFailure, "User not authorised"); } } @@ -247,8 +245,9 @@ public class EC2Engine extends ManagerBase { return false; } catch( Exception e ) { logger.error( "EC2 CreateSecurityGroup - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage()); + handleException(e); } + return false; } /** @@ -266,8 +265,9 @@ public class EC2Engine extends ManagerBase { return false; } catch( Exception e ) { logger.error( "EC2 DeleteSecurityGroup - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage()); + handleException(e); } + return false; } /** @@ -276,19 +276,19 @@ public class EC2Engine extends ManagerBase { * @param request * @return */ - public EC2DescribeSecurityGroupsResponse describeSecurityGroups(EC2DescribeSecurityGroups request) - { + public EC2DescribeSecurityGroupsResponse describeSecurityGroups(EC2DescribeSecurityGroups request) { + EC2DescribeSecurityGroupsResponse response = new EC2DescribeSecurityGroupsResponse(); try { - EC2DescribeSecurityGroupsResponse response = listSecurityGroups( request.getGroupSet()); + response = listSecurityGroups( request.getGroupSet()); EC2GroupFilterSet gfs = request.getFilterSet(); - - if ( null == gfs ) - return response; - else return gfs.evaluate( response ); + if ( gfs != null ) { + response = gfs.evaluate( response ); + } } catch( Exception e ) { logger.error( "EC2 DescribeSecurityGroups - ", e); - throw new EC2ServiceException(ServerError.InternalError, "An unexpected error occurred."); + handleException(e); } + return response; } /** @@ -300,7 +300,6 @@ public class EC2Engine extends ManagerBase { */ public boolean revokeSecurityGroup( EC2AuthorizeRevokeSecurityGroup request ) { - if (null == request.getName()) throw new EC2ServiceException(ServerError.InternalError, "Name is a required parameter"); try { String[] groupSet = new String[1]; groupSet[0] = request.getName(); @@ -310,6 +309,9 @@ public class EC2Engine extends ManagerBase { EC2DescribeSecurityGroupsResponse response = listSecurityGroups( groupSet ); EC2SecurityGroup[] groups = response.getGroupSet(); + if ( groups.length == 0 ) { + throw new Exception("Unable to find security group name"); + } for (EC2SecurityGroup group : groups) { EC2IpPermission[] perms = group.getIpPermissionSet(); @@ -320,17 +322,16 @@ public class EC2Engine extends ManagerBase { } if (null == ruleId) - throw new EC2ServiceException(ClientError.InvalidGroup_NotFound, "Cannot find matching ruleid."); - + throw new Exception("Specified Ip permission is invalid"); CloudStackInfoResponse resp = getApi().revokeSecurityGroupIngress(ruleId); if (resp != null) { return resp.getSuccess(); } - return false; } catch( Exception e ) { logger.error( "EC2 revokeSecurityGroupIngress" + " - " + e.getMessage()); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage()); - } + handleException(e); + } + return false; } /** @@ -338,10 +339,7 @@ public class EC2Engine extends ManagerBase { * * @param request - ip permission parameters */ - public boolean authorizeSecurityGroup(EC2AuthorizeRevokeSecurityGroup request ) - { - if (null == request.getName()) throw new EC2ServiceException(ServerError.InternalError, "Name is a required parameter"); - + public boolean authorizeSecurityGroup(EC2AuthorizeRevokeSecurityGroup request ) { EC2IpPermission[] items = request.getIpPermissionSet(); try { @@ -374,7 +372,7 @@ public class EC2Engine extends ManagerBase { } } catch(Exception e) { logger.error( "EC2 AuthorizeSecurityGroupIngress - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage()); + handleException(e); } return true; } @@ -444,13 +442,14 @@ public class EC2Engine extends ManagerBase { * @param request * @return */ - public EC2DescribeSnapshotsResponse handleRequest( EC2DescribeSnapshots request ) + public EC2DescribeSnapshotsResponse describeSnapshots( EC2DescribeSnapshots request ) { + EC2DescribeSnapshotsResponse response = new EC2DescribeSnapshotsResponse(); EC2SnapshotFilterSet sfs = request.getFilterSet(); EC2TagKeyValue[] tagKeyValueSet = request.getResourceTagSet(); try { - EC2DescribeSnapshotsResponse response = listSnapshots( request.getSnapshotSet(), + response = listSnapshots( request.getSnapshotSet(), getResourceTags(tagKeyValueSet)); if (response == null) { return new EC2DescribeSnapshotsResponse(); @@ -475,17 +474,14 @@ public class EC2Engine extends ManagerBase { } snap.setVolumeSize(size); } - if ( null == sfs ) - return response; - else return sfs.evaluate( response ); - } catch( EC2ServiceException error ) { - logger.error( "EC2 DescribeSnapshots - ", error); - throw error; - + if (sfs != null) { + response = sfs.evaluate( response ); + } } catch( Exception e ) { logger.error( "EC2 DescribeSnapshots - ", e); - throw new EC2ServiceException(ServerError.InternalError, "An unexpected error occurred."); + handleException(e); } + return response; } /** @@ -495,14 +491,12 @@ public class EC2Engine extends ManagerBase { * @return */ public EC2Snapshot createSnapshot( String volumeId ) { + EC2Snapshot ec2Snapshot = new EC2Snapshot(); try { - CloudStackSnapshot snap = getApi().createSnapshot(volumeId, null, null, null); if (snap == null) { - throw new EC2ServiceException(ServerError.InternalError, "Unable to create snapshot!"); + throw new Exception("Unable to create snapshot"); } - EC2Snapshot ec2Snapshot = new EC2Snapshot(); - ec2Snapshot.setId(snap.getId()); ec2Snapshot.setName(snap.getName()); ec2Snapshot.setType(snap.getSnapshotType()); @@ -518,12 +512,11 @@ public class EC2Engine extends ManagerBase { Long sizeInGB = vols.get(0).getSize().longValue()/1073741824; ec2Snapshot.setVolumeSize(sizeInGB); } - - return ec2Snapshot; } catch( Exception e ) { logger.error( "EC2 CreateSnapshot - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage()); + handleException(e); } + return ec2Snapshot; } /** @@ -543,8 +536,9 @@ public class EC2Engine extends ManagerBase { return false; } catch(Exception e) { logger.error( "EC2 DeleteSnapshot - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage() != null ? e.getMessage() : "An unexpected error occurred."); + handleException(e); } + return false; } @@ -595,15 +589,11 @@ public class EC2Engine extends ManagerBase { } return false; } - } catch (Exception e) { logger.error( "EC2 modifyImageAttribute - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage()); + handleException(e); } - return false; - - } public EC2ImageAttributes describeImageAttribute(EC2DescribeImageAttribute request) { @@ -632,7 +622,7 @@ public class EC2Engine extends ManagerBase { } catch (Exception e) { logger.error( "EC2 describeImageAttribute - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage()); + handleException(e); } return imageAtts; @@ -646,18 +636,18 @@ public class EC2Engine extends ManagerBase { * @return */ public EC2PasswordData getPasswordData(String instanceId) { + EC2PasswordData passwdData = new EC2PasswordData(); try { CloudStackPasswordData resp = getApi().getVMPassword(instanceId); - EC2PasswordData passwdData = new EC2PasswordData(); if (resp != null) { passwdData.setInstanceId(instanceId); passwdData.setEncryptedPassword(resp.getEncryptedpassword()); } - return passwdData; } catch(Exception e) { logger.error("EC2 GetPasswordData - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage()); + handleException(e); } + return passwdData; } /** @@ -667,18 +657,17 @@ public class EC2Engine extends ManagerBase { * @return */ public EC2DescribeKeyPairsResponse describeKeyPairs( EC2DescribeKeyPairs request ) { + EC2DescribeKeyPairsResponse response = new EC2DescribeKeyPairsResponse(); try { - EC2DescribeKeyPairsResponse response = listKeyPairs(request.getKeyNames()); + response = listKeyPairs(request.getKeyNames()); EC2KeyPairFilterSet kfs = request.getKeyFilterSet(); - - if (kfs == null) - return response; - else - return kfs.evaluate(response); + if (kfs != null) + response = kfs.evaluate(response); } catch(Exception e) { logger.error("EC2 DescribeKeyPairs - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage()); + handleException(e); } + return response; } /** @@ -688,17 +677,18 @@ public class EC2Engine extends ManagerBase { * @return */ public boolean deleteKeyPair( EC2DeleteKeyPair request ) { + CloudStackInfoResponse resp = new CloudStackInfoResponse(); + String keyPairName = request.getKeyName(); try { - CloudStackInfoResponse resp = getApi().deleteSSHKeyPair(request.getKeyName(), null, null); + resp = getApi().deleteSSHKeyPair(keyPairName, null, null); if (resp == null) { throw new Exception("Ivalid CloudStack API response"); } - - return resp.getSuccess(); } catch(Exception e) { logger.error("EC2 DeleteKeyPair - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage()); + handleException(e); } + return resp.getSuccess(); } /** @@ -708,22 +698,21 @@ public class EC2Engine extends ManagerBase { * @return */ public EC2SSHKeyPair createKeyPair(EC2CreateKeyPair request) { + String keyPairName = request.getKeyName(); + EC2SSHKeyPair response = new EC2SSHKeyPair(); try { - CloudStackKeyPair resp = getApi().createSSHKeyPair(request.getKeyName(), null, null); + CloudStackKeyPair resp = getApi().createSSHKeyPair(keyPairName, null, null); if (resp == null) { throw new Exception("Ivalid CloudStack API response"); } - - EC2SSHKeyPair response = new EC2SSHKeyPair(); response.setFingerprint(resp.getFingerprint()); response.setKeyName(resp.getName()); response.setPrivateKey(resp.getPrivatekey()); - - return response; } catch (Exception e) { logger.error("EC2 CreateKeyPair - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage()); + handleException(e); } + return response; } /** @@ -733,22 +722,20 @@ public class EC2Engine extends ManagerBase { * @return */ public EC2SSHKeyPair importKeyPair( EC2ImportKeyPair request ) { + EC2SSHKeyPair response = new EC2SSHKeyPair(); try { CloudStackKeyPair resp = getApi().registerSSHKeyPair(request.getKeyName(), request.getPublicKeyMaterial()); if (resp == null) { throw new Exception("Ivalid CloudStack API response"); } - - EC2SSHKeyPair response = new EC2SSHKeyPair(); response.setFingerprint(resp.getFingerprint()); response.setKeyName(resp.getName()); response.setPrivateKey(resp.getPrivatekey()); - - return response; } catch (Exception e) { logger.error("EC2 ImportKeyPair - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage()); + handleException(e); } + return response; } /** @@ -758,18 +745,17 @@ public class EC2Engine extends ManagerBase { * @return */ public EC2DescribeAddressesResponse describeAddresses( EC2DescribeAddresses request ) { + EC2DescribeAddressesResponse response = new EC2DescribeAddressesResponse(); try { - EC2DescribeAddressesResponse response = listAddresses(request.getPublicIpsSet()); + response = listAddresses(request.getPublicIpsSet()); EC2AddressFilterSet afs = request.getFilterSet(); - - if (afs ==null) - return response; - else - return afs.evaluate(response); + if (afs != null) + response = afs.evaluate(response); } catch(Exception e) { logger.error("EC2 DescribeAddresses - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage()); + handleException(e); } + return response; } /** @@ -782,7 +768,7 @@ public class EC2Engine extends ManagerBase { try { List cloudIps = getApi().listPublicIpAddresses(null, null, null, null, null, request.getPublicIp(), null, null, null); if (cloudIps == null) - throw new EC2ServiceException(ServerError.InternalError, "Specified ipAddress doesn't exist"); + throw new Exception("Specified ipAddress doesn't exist"); CloudStackIpAddress cloudIp = cloudIps.get(0); CloudStackInfoResponse resp = getApi().disassociateIpAddress(cloudIp.getId()); if (resp != null) { @@ -790,7 +776,7 @@ public class EC2Engine extends ManagerBase { } } catch(Exception e) { logger.error("EC2 ReleaseAddress - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage()); + handleException(e); } return false; } @@ -805,13 +791,13 @@ public class EC2Engine extends ManagerBase { try { List cloudIps = getApi().listPublicIpAddresses(null, null, null, null, null, request.getPublicIp(), null, null, null); if (cloudIps == null) - throw new EC2ServiceException(ServerError.InternalError, "Specified ipAddress doesn't exist"); + throw new Exception("Specified ipAddress doesn't exist"); CloudStackIpAddress cloudIp = cloudIps.get(0); List vmList = getApi().listVirtualMachines(null, null, true, null, null, null, null, request.getInstanceId(), null, null, null, null, null, null, null, null, null); if (vmList == null || vmList.size() == 0) { - throw new EC2ServiceException(ServerError.InternalError, "Specified instance-id doesn't exist"); + throw new Exception("Instance not found"); } CloudStackUserVm cloudVm = vmList.get(0); @@ -821,7 +807,7 @@ public class EC2Engine extends ManagerBase { } } catch(Exception e) { logger.error( "EC2 AssociateAddress - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage() != null ? e.getMessage() : "An unexpected error occurred."); + handleException(e); } return false; } @@ -836,7 +822,7 @@ public class EC2Engine extends ManagerBase { try { List cloudIps = getApi().listPublicIpAddresses(null, null, null, null, null, request.getPublicIp(), null, null, null); if (cloudIps == null) - throw new EC2ServiceException(ServerError.InternalError, "Specified ipAddress doesn't exist"); + throw new Exception("Specified ipAddress doesn't exist"); CloudStackIpAddress cloudIp = cloudIps.get(0); CloudStackInfoResponse resp = getApi().disableStaticNat(cloudIp.getId()); @@ -845,7 +831,7 @@ public class EC2Engine extends ManagerBase { } } catch(Exception e) { logger.error( "EC2 DisassociateAddress - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage() != null ? e.getMessage() : "An unexpected error occurred."); + handleException(e); } return false; } @@ -856,10 +842,9 @@ public class EC2Engine extends ManagerBase { * @param request * @return */ - public EC2Address allocateAddress() - { + public EC2Address allocateAddress() { + EC2Address ec2Address = new EC2Address(); try { - EC2Address ec2Address = new EC2Address(); // this gets our networkId CloudStackAccount caller = getCurrentAccount(); @@ -881,12 +866,11 @@ public class EC2Engine extends ManagerBase { } else { ec2Address.setIpAddress(resp.getIpAddress()); } - - return ec2Address; } catch(Exception e) { logger.error( "EC2 AllocateAddress - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage() != null ? e.getMessage() : "An unexpected error occurred."); + handleException(e); } + return ec2Address; } /** @@ -896,10 +880,8 @@ public class EC2Engine extends ManagerBase { * @param request * @return */ - public EC2DescribeImagesResponse describeImages(EC2DescribeImages request) - { + public EC2DescribeImagesResponse describeImages(EC2DescribeImages request) { EC2DescribeImagesResponse images = new EC2DescribeImagesResponse(); - try { String[] templateIds = request.getImageSet(); EC2ImageFilterSet ifs = request.getFilterSet(); @@ -911,14 +893,13 @@ public class EC2Engine extends ManagerBase { images = listTemplates(s, images); } } - if (ifs == null) - return images; - else + if (ifs != null) return ifs.evaluate(images); } catch( Exception e ) { logger.error( "EC2 DescribeImages - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage() != null ? e.getMessage() : "An unexpected error occurred."); + handleException(e); } + return images; } /** @@ -938,10 +919,9 @@ public class EC2Engine extends ManagerBase { */ public EC2CreateImageResponse createImage(EC2CreateImage request) { - EC2CreateImageResponse response = null; + EC2CreateImageResponse response = new EC2CreateImageResponse(); boolean needsRestart = false; String volumeId = null; - try { // [A] Creating a template from a VM volume should be from the ROOT volume // Also for this to work the VM must be in a Stopped state so we 'reboot' it if its not @@ -954,7 +934,7 @@ public class EC2Engine extends ManagerBase { if (vmState.equalsIgnoreCase( "running" ) || vmState.equalsIgnoreCase( "starting" )) { needsRestart = true; if (!stopVirtualMachine( request.getInstanceId() )) - throw new EC2ServiceException(ClientError.IncorrectState, "CreateImage - instance must be in a stopped state"); + throw new Exception("Instance must be in a stopped state"); } volumeId = vol.getId(); break; @@ -975,25 +955,22 @@ public class EC2Engine extends ManagerBase { CloudStackTemplate resp = getApi().createTemplate((request.getDescription() == null ? "" : request.getDescription()), request.getName(), osTypeId, null, null, null, null, null, null, volumeId); if (resp == null || resp.getId() == null) { - throw new EC2ServiceException(ServerError.InternalError, "An upexpected error occurred."); + throw new Exception("Image couldn't be created"); } //if template was created succesfully, create the new image response - response = new EC2CreateImageResponse(); response.setId(resp.getId()); // [C] If we stopped the virtual machine now we need to restart it if (needsRestart) { - if (!startVirtualMachine( request.getInstanceId() )) - throw new EC2ServiceException(ServerError.InternalError, - "CreateImage - restarting instance " + request.getInstanceId() + " failed"); + if (!startVirtualMachine( request.getInstanceId() )) + throw new Exception("Failed to start the stopped instance"); } - return response; - } catch( Exception e ) { logger.error( "EC2 CreateImage - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage() != null ? e.getMessage() : "An unexpected error occurred."); + handleException(e); } + return response; } /** @@ -1002,13 +979,9 @@ public class EC2Engine extends ManagerBase { * @param request * @return */ - public EC2CreateImageResponse registerImage(EC2RegisterImage request) - { + public EC2CreateImageResponse registerImage(EC2RegisterImage request) { + EC2CreateImageResponse image = new EC2CreateImageResponse(); try { - CloudStackAccount caller = getCurrentAccount(); - if (null == request.getName()) - throw new EC2ServiceException(ClientError.Unsupported, "Missing parameter - name"); - List templates = getApi().registerTemplate((request.getDescription() == null ? request.getName() : request.getDescription()), request.getFormat(), request.getHypervisor(), request.getName(), toOSTypeId(request.getOsTypeName()), request.getLocation(), toZoneId(request.getZoneName(), null), null, null, null, null, null, null, null, null, null); @@ -1016,17 +989,16 @@ public class EC2Engine extends ManagerBase { // technically we will only ever register a single template... for (CloudStackTemplate template : templates) { if (template != null && template.getId() != null) { - EC2CreateImageResponse image = new EC2CreateImageResponse(); image.setId(template.getId().toString()); return image; } } } - return null; } catch( Exception e ) { logger.error( "EC2 RegisterImage - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage() != null ? e.getMessage() : "An unexpected error occurred."); + handleException(e); } + return image; } /** @@ -1044,8 +1016,9 @@ public class EC2Engine extends ManagerBase { return resp.getSuccess(); } catch( Exception e ) { logger.error( "EC2 DeregisterImage - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage() != null ? e.getMessage() : "An unexpected error occurred."); + handleException(e); } + return false; } /** @@ -1055,14 +1028,16 @@ public class EC2Engine extends ManagerBase { * @return */ public EC2DescribeInstancesResponse describeInstances(EC2DescribeInstances request ) { + EC2DescribeInstancesResponse response = new EC2DescribeInstancesResponse(); try { EC2TagKeyValue[] tagKeyValueSet = request.getResourceTagSet(); - return listVirtualMachines( request.getInstancesSet(), request.getFilterSet(), + response = listVirtualMachines( request.getInstancesSet(), request.getFilterSet(), getResourceTags(tagKeyValueSet)); } catch( Exception e ) { logger.error( "EC2 DescribeInstances - " ,e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage() != null ? e.getMessage() : "An unexpected error occurred."); + handleException(e); } + return response; } /** @@ -1071,22 +1046,18 @@ public class EC2Engine extends ManagerBase { * @param request * @return */ - public EC2DescribeAvailabilityZonesResponse handleRequest(EC2DescribeAvailabilityZones request) { + public EC2DescribeAvailabilityZonesResponse describeAvailabilityZones(EC2DescribeAvailabilityZones request) { + EC2DescribeAvailabilityZonesResponse availableZones = new EC2DescribeAvailabilityZonesResponse(); try { - EC2DescribeAvailabilityZonesResponse availableZones = listZones(request.getZoneSet(), null); + availableZones = listZones(request.getZoneSet(), null); EC2AvailabilityZonesFilterSet azfs = request.getFilterSet(); - if ( null == azfs ) - return availableZones; - else - return azfs.evaluate(availableZones); - } catch( EC2ServiceException error ) { - logger.error( "EC2 DescribeAvailabilityZones - ", error); - throw error; - + if ( azfs != null ) + availableZones = azfs.evaluate(availableZones); } catch( Exception e ) { logger.error( "EC2 DescribeAvailabilityZones - " ,e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage() != null ? e.getMessage() : "An unexpected error occurred."); + handleException(e); } + return availableZones; } /** @@ -1095,7 +1066,7 @@ public class EC2Engine extends ManagerBase { * @param request * @return */ - public EC2DescribeVolumesResponse handleRequest( EC2DescribeVolumes request ) { + public EC2DescribeVolumesResponse describeVolumes( EC2DescribeVolumes request ) { EC2DescribeVolumesResponse volumes = new EC2DescribeVolumesResponse(); EC2VolumeFilterSet vfs = request.getFilterSet(); EC2TagKeyValue[] tagKeyValueSet = request.getResourceTagSet(); @@ -1107,14 +1078,13 @@ public class EC2Engine extends ManagerBase { for (String s : volumeIds) volumes = listVolumes(s, null, volumes, getResourceTags(tagKeyValueSet) ); } - - if ( null == vfs ) - return volumes; - else return vfs.evaluate( volumes ); + if ( vfs != null ) + volumes = vfs.evaluate( volumes ); } catch( Exception e ) { logger.error( "EC2 DescribeVolumes - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage() != null ? e.getMessage() : "An unexpected error occurred."); + handleException(e); } + return volumes; } /** @@ -1124,10 +1094,9 @@ public class EC2Engine extends ManagerBase { * @return */ public EC2Volume attachVolume( EC2Volume request ) { + EC2Volume resp = new EC2Volume(); try { request.setDeviceId(mapDeviceToCloudDeviceId(request.getDevice())); - EC2Volume resp = new EC2Volume(); - CloudStackVolume vol = getApi().attachVolume(request.getId(), request.getInstanceId(), request.getDeviceId()); if(vol != null) { resp.setAttached(vol.getAttached()); @@ -1144,13 +1113,12 @@ public class EC2Engine extends ManagerBase { resp.setVMState(vol.getVirtualMachineState()); resp.setAttachmentState(mapToAmazonVolumeAttachmentState(vol.getVirtualMachineState())); resp.setZoneName(vol.getZoneName()); - return resp; } - throw new EC2ServiceException( ServerError.InternalError, "An unexpected error occurred." ); } catch( Exception e ) { - logger.error( "EC2 AttachVolume 2 - ", e); - throw new EC2ServiceException( ServerError.InternalError, e.getMessage() != null ? e.getMessage() : e.toString()); - } + logger.error( "EC2 AttachVolume - ", e); + handleException(e); + } + return resp; } /** @@ -1160,10 +1128,27 @@ public class EC2Engine extends ManagerBase { * @return */ public EC2Volume detachVolume(EC2Volume request) { + EC2Volume resp = new EC2Volume(); try { - CloudStackVolume vol = getApi().detachVolume(null, request.getId(), null); - EC2Volume resp = new EC2Volume(); + // verifying if instanceId and deviceId provided is valid + EC2DescribeVolumesResponse volumes = new EC2DescribeVolumesResponse(); + volumes = listVolumes(request.getId(), null, volumes, null); + if (volumes != null) { + EC2Volume[] volumeSet = volumes.getVolumeSet(); + if (request.getInstanceId() != null) { + if ( !request.getInstanceId().equalsIgnoreCase(volumeSet[0].getInstanceId()) ) + throw new Exception("Volume is not attached to the Instance"); + } + if (request.getDevice() != null) { + String devicePath = null; + if ( volumeSet[0].getDeviceId() != null ) + devicePath = cloudDeviceIdToDevicePath( volumeSet[0].getHypervisor(), volumeSet[0].getDeviceId()); + if ( !request.getDevice().equalsIgnoreCase(devicePath) ) + throw new Exception("Volume is not attached to the Device"); + } + } + CloudStackVolume vol = getApi().detachVolume(null , request.getId(), null); if(vol != null) { resp.setAttached(vol.getAttached()); resp.setCreated(vol.getCreated()); @@ -1178,14 +1163,12 @@ public class EC2Engine extends ManagerBase { resp.setType(vol.getVolumeType()); resp.setVMState(vol.getVirtualMachineState()); resp.setZoneName(vol.getZoneName()); - return resp; } - - throw new EC2ServiceException( ServerError.InternalError, "An unexpected error occurred." ); } catch( Exception e ) { logger.error( "EC2 DetachVolume - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage() != null ? e.getMessage() : "An unexpected error occurred."); - } + handleException(e); + } + return resp; } /** @@ -1195,9 +1178,8 @@ public class EC2Engine extends ManagerBase { * @return */ public EC2Volume createVolume( EC2CreateVolume request ) { + EC2Volume resp = new EC2Volume(); try { - - CloudStackAccount caller = getCurrentAccount(); // -> put either snapshotid or diskofferingid on the request String snapshotId = request.getSnapshotId(); Long size = request.getSize(); @@ -1216,7 +1198,6 @@ public class EC2Engine extends ManagerBase { // // -> no volume name is given in the Amazon request but is required in the cloud API CloudStackVolume vol = getApi().createVolume(UUID.randomUUID().toString(), null, diskOfferingId, null, size, snapshotId, toZoneId(request.getZoneName(), null)); if (vol != null) { - EC2Volume resp = new EC2Volume(); resp.setAttached(vol.getAttached()); resp.setCreated(vol.getCreated()); // resp.setDevice(); @@ -1231,13 +1212,12 @@ public class EC2Engine extends ManagerBase { resp.setVMState(vol.getVirtualMachineState()); resp.setAttachmentState("detached"); resp.setZoneName(vol.getZoneName()); - return resp; } - return null; } catch( Exception e ) { logger.error( "EC2 CreateVolume - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage() != null ? e.getMessage() : "An unexpected error occurred."); - } + handleException(e); + } + return resp; } /** @@ -1251,14 +1231,12 @@ public class EC2Engine extends ManagerBase { CloudStackInfoResponse resp = getApi().deleteVolume(request.getId()); if(resp != null) { request.setState("deleted"); - return request; } - - throw new EC2ServiceException(ServerError.InternalError, "An unexpected error occurred."); } catch( Exception e ) { - logger.error( "EC2 DeleteVolume 2 - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage() != null ? e.getMessage() : "An unexpected error occurred."); - } + logger.error( "EC2 DeleteVolume - ", e); + handleException(e); + } + return request; } /** @@ -1295,9 +1273,9 @@ public class EC2Engine extends ManagerBase { return true; } catch (Exception e){ logger.error( "EC2 Create/Delete Tags - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage() != null ? - e.getMessage() : "An unexpected error occurred."); + handleException(e); } + return false; } /** @@ -1307,11 +1285,11 @@ public class EC2Engine extends ManagerBase { * @return */ public EC2DescribeTagsResponse describeTags (EC2DescribeTags request) { + EC2DescribeTagsResponse tagResponse = new EC2DescribeTagsResponse(); try { - EC2DescribeTagsResponse tagResponse = new EC2DescribeTagsResponse(); + tagResponse = new EC2DescribeTagsResponse(); List resourceTagList = getApi().listTags(null, null, null, true, null); - List tagList = new ArrayList(); if (resourceTagList != null && resourceTagList.size() > 0) { for (CloudStackResourceTag resourceTag: resourceTagList) { EC2ResourceTag tag = new EC2ResourceTag(); @@ -1325,14 +1303,13 @@ public class EC2Engine extends ManagerBase { } EC2TagsFilterSet tfs = request.getFilterSet(); - if (tfs == null) - return tagResponse; - else - return tfs.evaluate(tagResponse); + if (tfs != null) + tagResponse = tfs.evaluate(tagResponse); } catch(Exception e) { logger.error("EC2 DescribeTags - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage()); + handleException(e); } + return tagResponse; } /** @@ -1362,13 +1339,14 @@ public class EC2Engine extends ManagerBase { // -> if some specified VMs where not found we have to tell the caller if (instanceSet.length != vms.length) - throw new EC2ServiceException(ClientError.InvalidAMIID_NotFound, "One or more instanceIds do not exist, other instances rebooted."); + throw new Exception("One or more instanceIds do not exist, other instances rebooted."); return true; } catch( Exception e ) { logger.error( "EC2 RebootInstances - ", e ); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage() != null ? e.getMessage() : "An unexpected error occurred."); + handleException(e); } + return false; } /** @@ -1384,15 +1362,13 @@ public class EC2Engine extends ManagerBase { int countCreated = 0; try { - CloudStackAccount caller = getCurrentAccount(); - // ugly... canCreateInstances = calculateAllowedInstances(); if (-1 == canCreateInstances) canCreateInstances = request.getMaxCount(); if (canCreateInstances < request.getMinCount()) { logger.info( "EC2 RunInstances - min count too big (" + request.getMinCount() + "), " + canCreateInstances + " left to allocate"); - throw new EC2ServiceException(ClientError.InstanceLimitExceeded ,"Only " + canCreateInstances + " instance(s) left to allocate"); + throw new Exception("Min Count is greater than the number of instances left to allocate"); } if ( canCreateInstances < request.getMaxCount()) @@ -1408,7 +1384,7 @@ public class EC2Engine extends ManagerBase { CloudStackServiceOfferingVO svcOffering = getCSServiceOfferingId(instanceType); if(svcOffering == null){ logger.info("No ServiceOffering found to be defined by name, please contact the administrator "+instanceType ); - throw new EC2ServiceException(ClientError.Unsupported, "instanceType: [" + instanceType + "] not found!"); + throw new Exception("instanceType not found"); } // zone stuff @@ -1417,7 +1393,7 @@ public class EC2Engine extends ManagerBase { List zones = getApi().listZones(null, null, zoneId, null); if (zones == null || zones.size() == 0) { logger.info("EC2 RunInstances - zone [" + request.getZoneName() + "] not found!"); - throw new EC2ServiceException(ClientError.InvalidZone_NotFound, "ZoneId [" + request.getZoneName() + "] not found!"); + throw new Exception("zone not found"); } // we choose first zone? CloudStackZone zone = zones.get(0); @@ -1467,19 +1443,17 @@ public class EC2Engine extends ManagerBase { logger.error("Failed to deploy VM number: "+ (i+1) +" due to error: "+e.getMessage()); break; } - } - + } if (0 == countCreated) { // TODO, we actually need to destroy left-over VMs when the exception is thrown - throw new EC2ServiceException(ServerError.InternalError, "Failed to deploy instances" ); + throw new Exception("Insufficient Instance Capacity"); } logger.debug("Could deploy "+ countCreated + " VM's successfully"); - - return instances; } catch( Exception e ) { logger.error( "EC2 RunInstances - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage() != null ? e.getMessage() : "An unexpected error occurred."); + handleException(e); } + return instances; } /** @@ -1515,11 +1489,11 @@ public class EC2Engine extends ManagerBase { } instances.addInstance(vm); } - return instances; } catch( Exception e ) { logger.error( "EC2 StartInstances - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage() != null ? e.getMessage() : "An unexpected error occurred."); + handleException(e); } + return instances; } /** @@ -1566,11 +1540,48 @@ public class EC2Engine extends ManagerBase { instances.addInstance(vm); } } - return instances; } catch( Exception e ) { logger.error( "EC2 StopInstances - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage() != null ? e.getMessage() + ", might already be destroyed" : "An unexpected error occurred."); + handleException(e); } + return instances; + } + + /** + * @param request + * @return + * @throws Exception + */ + public boolean modifyInstanceAttribute(EC2ModifyInstanceAttribute request) { + boolean status = true; + String instanceId = request.getInstanceId(); + try { + // AWS requires VM to be in stopped state to modify 'InstanceType' and 'UserData' + EC2DescribeInstancesResponse vmResponse = new EC2DescribeInstancesResponse(); + vmResponse = lookupInstances(instanceId, vmResponse, null); + EC2Instance[] instances = vmResponse.getInstanceSet(); + if ( !instances[0].getState().equalsIgnoreCase("stopped")) { + throw new Exception("Cannot modify, instance should be in stopped state"); + } + + if (request.getInstanceType() != null) { + String instanceType = request.getInstanceType(); + CloudStackServiceOfferingVO svcOffering = getCSServiceOfferingId(instanceType); + if (svcOffering == null) + throw new Exception("instanceType not found"); + CloudStackUserVm userVm = getApi().changeServiceForVirtualMachine(instanceId, svcOffering.getId()); + status = (userVm != null); + } + if (status != false && request.getUserData() != null) { + CloudStackUserVm userVm = getApi().updateVirtualMachine(instanceId, null, null, null, + null, request.getUserData()); + status = (userVm != null); + } + } catch (Exception e) { + logger.error("ModifyInstanceAttribute - ", e); + handleException(e); + } + return status; } /** @@ -1719,7 +1730,7 @@ public class EC2Engine extends ManagerBase { zones = listZones(interestedZones, domainId); if (zones == null || zones.getAvailabilityZoneSet().length == 0) - throw new EC2ServiceException(ClientError.InvalidParameterValue, "Unknown zoneName value - " + zoneName); + throw new Exception("Unknown zoneName value"); EC2AvailabilityZone[] zoneSet = zones.getAvailabilityZoneSet(); return zoneSet[0].getId(); @@ -1731,15 +1742,22 @@ public class EC2Engine extends ManagerBase { * */ - private CloudStackServiceOfferingVO getCSServiceOfferingId(String instanceType){ + private CloudStackServiceOfferingVO getCSServiceOfferingId(String instanceType) throws Exception { try { - if (null == instanceType) instanceType = "m1.small"; + // list of valid values for AWS EC2 instanceType + String[] validInstanceTypes = {"t1.micro", "m1.small", "m1.medium", "m1.large", "m1.xlarge", + "c1.medium", "c1.xlarge", "m2.xlarge", "m2.2xlarge", "m2.4xlarge", + "m3.xlarge", "m3.2xlarge", "hi1.4xlarge", "cc1.4xlarge", "cg1.4xlarge", "cc2.8xlarge"}; + if (instanceType == null) + instanceType = "m1.small"; // default value + else if ( !Arrays.asList(validInstanceTypes).contains(instanceType)) { + throw new Exception("Specified instance type is invalid"); + } return scvoDao.getSvcOfferingByName(instanceType); - } catch(Exception e) { logger.error( "Error while retrieving ServiceOffering information by name - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage()); + throw new Exception("No ServiceOffering found to be defined by name"); } } @@ -1751,7 +1769,7 @@ public class EC2Engine extends ManagerBase { * @return A valid value for the Amazon defined instanceType * @throws SQLException, ClassNotFoundException, IllegalAccessException, InstantiationException */ - private String serviceOfferingIdToInstanceType( String serviceOfferingId ){ + private String serviceOfferingIdToInstanceType( String serviceOfferingId ) throws Exception { try{ CloudStackServiceOfferingVO offering = scvoDao.getSvcOfferingById(serviceOfferingId); //dao.getSvcOfferingById(serviceOfferingId); @@ -1762,8 +1780,8 @@ public class EC2Engine extends ManagerBase { return offering.getName(); } catch(Exception e) { - logger.error( "sError while retrieving ServiceOffering information by id - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage()); + logger.error( "Error while retrieving ServiceOffering information by id - ", e); + throw new Exception( e.getMessage() != null ? e.getMessage() : e.toString() ); } } @@ -1784,7 +1802,7 @@ public class EC2Engine extends ManagerBase { return null; } catch(Exception e) { logger.error( "List OS Types - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage()); + throw new Exception( e.getMessage() != null ? e.getMessage() : e.toString() ); } } @@ -1898,7 +1916,7 @@ public class EC2Engine extends ManagerBase { }else{ if(instanceId != null){ //no such instance found - throw new EC2ServiceException(ServerError.InternalError, "Instance:" + instanceId + " not found"); + throw new Exception("Instance not found"); } } return instances; @@ -1915,7 +1933,7 @@ public class EC2Engine extends ManagerBase { * @return the same object passed in as the "images" parameter modified with one or more * EC2Image objects loaded. */ - private EC2DescribeImagesResponse listTemplates( String templateId, EC2DescribeImagesResponse images ) throws EC2ServiceException { + private EC2DescribeImagesResponse listTemplates( String templateId, EC2DescribeImagesResponse images ) throws Exception { try { List result = new ArrayList(); @@ -1985,7 +2003,7 @@ public class EC2Engine extends ManagerBase { return images; } catch(Exception e) { logger.error( "List Templates - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage()); + throw new Exception( e.getMessage() != null ? e.getMessage() : e.toString() ); } } @@ -2036,7 +2054,7 @@ public class EC2Engine extends ManagerBase { return groupSet; } catch(Exception e) { logger.error( "List Security Groups - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage()); + throw new Exception( e.getMessage() != null ? e.getMessage() : e.toString() ); } } @@ -2068,7 +2086,7 @@ public class EC2Engine extends ManagerBase { return keyPairSet; } catch(Exception e) { logger.error( "List Keypairs - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage()); + throw new Exception( e.getMessage() != null ? e.getMessage() : e.toString() ); } } @@ -2101,7 +2119,7 @@ public class EC2Engine extends ManagerBase { return addressSet; } catch(Exception e) { logger.error( "List Addresses - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage()); + throw new Exception(e.getMessage() != null ? e.getMessage() : e.toString()); } } @@ -2148,7 +2166,7 @@ public class EC2Engine extends ManagerBase { return snapshotSet; } catch(Exception e) { logger.error( "List Snapshots - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage()); + throw new Exception(e.getMessage() != null ? e.getMessage() : e.toString()); } } @@ -2298,7 +2316,7 @@ public class EC2Engine extends ManagerBase { } // if we get this far and haven't returned already return an error - throw new EC2ServiceException(ServerError.InternalError, "Unable to find an appropriate network for account " + caller.getName()); + throw new Exception("Unable to find an appropriate network for account "); } /** @@ -2344,12 +2362,12 @@ public class EC2Engine extends ManagerBase { /** * Finds the defaultZone marked for the account */ - private String getDefaultZoneId(String accountId) { + private String getDefaultZoneId(String accountId) throws Exception { try { return accDao.getDefaultZoneId(accountId); } catch(Exception e) { logger.error( "Error while retrieving Account information by id - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage()); + throw new Exception("Unable to retrieve account account information"); } } @@ -2400,7 +2418,7 @@ public class EC2Engine extends ManagerBase { * @param device string * @return deviceId value */ - private String mapDeviceToCloudDeviceId( String device ) + private String mapDeviceToCloudDeviceId( String device ) throws Exception { if (device.equalsIgnoreCase( "/dev/sdb" )) return "1"; else if (device.equalsIgnoreCase( "/dev/sdc" )) return "2"; @@ -2429,7 +2447,7 @@ public class EC2Engine extends ManagerBase { else if (device.equalsIgnoreCase( "xvdi" )) return "8"; else if (device.equalsIgnoreCase( "xvdj" )) return "9"; - else throw new EC2ServiceException( ClientError.Unsupported, device + " is not supported" ); + else throw new Exception("Device is not supported"); } /** @@ -2529,7 +2547,7 @@ public class EC2Engine extends ManagerBase { return resp != null; } catch(Exception e) { logger.error( "StopVirtualMachine - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage() != null ? e.getMessage() : "An unexpected error occurred."); + throw new Exception( e.getMessage() != null ? e.getMessage() : e.toString() ); } } @@ -2548,7 +2566,7 @@ public class EC2Engine extends ManagerBase { return resp != null; } catch(Exception e) { logger.error("StartVirtualMachine - ", e); - throw new EC2ServiceException(ServerError.InternalError, e.getMessage() != null ? e.getMessage() : "An unexpected error occurred."); + throw new Exception( e.getMessage() != null ? e.getMessage() : e.toString() ); } } @@ -2577,4 +2595,269 @@ public class EC2Engine extends ManagerBase { } return resourceTags; } + + private void handleException( Exception e) { + String[] error = e.getMessage().split("Error Code - "); + String errorMessage = error[0]; + if (error.length == 2) { // error code has been supplied + int errorCode = Integer.parseInt(error[1]); + if (errorCode == 431) { + if ( errorMessage.contains("Object vm_instance(uuid:") && errorMessage.contains(") does not exist") ) { + throw new EC2ServiceException( ClientError.InvalidInstanceID_NotFound, + "Specified Instance ID does not exist"); + } else if ( errorMessage.contains("Unable to find security group by name") || + errorMessage.contains("Unable to find security group") || + ( errorMessage.contains("Object security_group(uuid:") && errorMessage.contains(") does not exist") ) || + errorMessage.contains("Unable to find group by name ") ) { + throw new EC2ServiceException( ClientError.InvalidGroup_NotFound, + "Specified Security Group does not exist"); + } else if ( errorMessage.contains("Invalid port numbers") ) { + throw new EC2ServiceException( ClientError.InvalidPermission_Malformed, + "Specified Port value is invalid"); + } else if (errorMessage.contains("Nonexistent account") && errorMessage.contains("when trying to authorize security group rule for security group") ) { + throw new EC2ServiceException( ClientError.InvalidPermission_Malformed, + "Specified account doesn't exist"); + } else if ( errorMessage.contains("Nonexistent group") && errorMessage.contains("unable to authorize security group rule") ) { + throw new EC2ServiceException( ClientError.InvalidPermission_Malformed, + "Specified source security group doesn't exist"); + } else if ( errorMessage.contains("Invalid protocol") ) { + throw new EC2ServiceException( ClientError.InvalidPermission_Malformed, + "Specified protocol is invalid"); + } else if ( errorMessage.contains("is an Invalid CIDR") ) { + throw new EC2ServiceException( ClientError.InvalidPermission_Malformed, + "Specified CIDR is invalid"); + }else if ( errorMessage.contains("Nonexistent account") ) { + throw new EC2ServiceException( ClientError.InvalidParameterValue, + "Specified Account name is invalid"); + } else if ( errorMessage.contains("Object volumes(uuid:") && errorMessage.contains(") does not exist") ) { + throw new EC2ServiceException( ClientError.InvalidVolume_NotFound, + "Specified Volume ID doesn't exist"); + } else if ( errorMessage.contains("Object snapshots(uuid:") && errorMessage.contains(") does not exist") ) { + throw new EC2ServiceException( ClientError.InvalidSnapshot_NotFound, + "Specified Snapshot ID doesn't exist"); + } else if ( (errorMessage.contains("A key pair with name '") && errorMessage.contains("' does not exist")) || + (errorMessage.contains("A key pair with name '") && errorMessage.contains("' was not found")) ) { + throw new EC2ServiceException( ClientError.InvalidKeyPair_NotFound, + "Specified Key pair name is invalid"); + } else if ( errorMessage.contains("A key pair with name '") && errorMessage.contains("' already exists") ) { + throw new EC2ServiceException( ClientError.InvalidKeyPair_Duplicate, + "Specified Key pair already exists"); + } else if ( errorMessage.contains("Unknown zoneName value") ) { + throw new EC2ServiceException( ClientError.InvalidZone_NotFound, + "Specified AvailabilityZone name is invalid"); + } else if ( errorMessage.contains("specify a volume that is not attached to any VM") ) { + throw new EC2ServiceException( ClientError.DependencyViolation, + "Specified Volume is attached to a VM"); + } else if ( errorMessage.contains("Object vm_template(uuid: ")&& errorMessage.contains(") does not exist") ) { + throw new EC2ServiceException( ClientError.InvalidAMIID_NotFound, + "Specified Image ID does not exist"); + } else if ( errorMessage.contains("unable to find template by id") ) { + throw new EC2ServiceException( ClientError.InvalidAMIID_NotFound, + "Specified Image ID does not exist"); + } else if ( errorMessage.contains("a group with name") && errorMessage.contains("already exists") ) { + throw new EC2ServiceException( ClientError.InvalidGroup_Duplicate, + "Specified Security Group already exists"); + } else if ( errorMessage.contains("specified volume is not attached to a VM") ) { + throw new EC2ServiceException( ClientError.IncorrectState, + "Specified volume is not in the correct state 'attached' for detachment"); + } else if ( errorMessage.contains("Snapshot with specified snapshotId is not in BackedUp state yet and can't be used for volume creation") ) { + throw new EC2ServiceException( ClientError.IncorrectState, + "Specified snapshot is not in the correct state 'completed' for volume creation"); + } else if ( errorMessage.contains("Can't delete snapshotshot 4 due to it is not in BackedUp Status") ) { + throw new EC2ServiceException( ClientError.IncorrectState, + "Specified snapshot is not in the correct state 'completed' for deletion"); + } else if ( errorMessage.contains("Public key is invalid") ) { + throw new EC2ServiceException( ClientError.InvalidKeyPair_Format, + "Format of the specified key is invalid"); + } else if ( errorMessage.contains("Invalid resource type") ) { + throw new EC2ServiceException( ClientError.InvalidParameterValue, + "Specified resourceId is invalid"); + } else if ( errorMessage.contains("Unable to find tags by parameters specified") ) { + throw new EC2ServiceException( ClientError.InvalidParameterValue, + "Specified resourceTag for the specified resourceId doesn't exist"); + } else if ( errorMessage.contains("Failed to enable static nat for the ip address with specified ipId " + + "as vm with specified vmId is already associated with specified currentIp") ) { + throw new EC2ServiceException( ClientError.InvalidParameterValue, + "Specified publicIp is already associated to the specified VM"); + } else if ( errorMessage.contains("Specified IP address id is not associated with any vm Id") ) { + throw new EC2ServiceException( ClientError.InvalidParameterValue, + "Specified publicIp is not associated to any VM"); + } else if ( errorMessage.contains("specify a VM that is either running or stopped") ) { + throw new EC2ServiceException( ClientError.IncorrectInstanceState, + "Unable to attach. Specified instances is in an incorrect state"); + } else if ( errorMessage.contains("specify a valid data volume") ) { + throw new EC2ServiceException( ClientError.InvalidVolume_NotFound, + "Specified volume doen't exist"); + } else if ( errorMessage.contains("VolumeId is not in Ready state, but in state Allocated. Cannot take snapshot") ) { + throw new EC2ServiceException( ClientError.IncorrectState, + "Cannot take snapshot. Specified volume is not in the correct state"); + } else if ( errorMessage.contains("Can't delete snapshot") && errorMessage.contains("it is not in BackedUp Status") ) { + throw new EC2ServiceException( ClientError.IncorrectState, + "Cannot delete snapshot. Specified snapshot is not in the correct state"); + } else if ( errorMessage.contains("Invalid port range") ) { + throw new EC2ServiceException( ClientError.InvalidPermission_Malformed, + "The specified port range is invalid"); + } else if ( errorMessage.contains("specify a valid User VM") ) { + throw new EC2ServiceException( ClientError.InvalidInstanceID_NotFound, + "Specified instance is invalid"); + } else if ( errorMessage.contains("No password for VM with specified id found") ) { + throw new EC2ServiceException( ClientError.InvalidParameterValue, + "No password for VM with the specified id found"); + } else if ( errorMessage.contains("make sure the virtual machine is stopped and not in an error state before upgrading") ) { + throw new EC2ServiceException( ClientError.IncorrectInstanceState, + "Unable to modify. Specified instances is not in the correct state 'Stopped'"); + } else if ( errorMessage.contains("Not upgrading vm") && errorMessage.contains("it already has the" + + " requested service offering") ) { + throw new EC2ServiceException( ClientError.InvalidParameterValue, + "Unable to modify. Specified instance already has the requested instanceType"); + } + // Can't enable static, ip address with specified id is a sourceNat ip address ? + else { + throw new EC2ServiceException( ClientError.InvalidParameterValue, + "The value supplied for a parameter is invalid"); + } + } + else if (errorCode == 536) { + if ( errorMessage.contains("Cannot delete group when it's in use by virtual machines") ) { + throw new EC2ServiceException( ClientError.InvalidGroup_InUse, + "Group is in use by a virtual machine"); + } else { + throw new EC2ServiceException( ClientError.DependencyViolation, + "Specified resource is in use"); + } + } + else if (errorCode == 531) { + if ( errorMessage.contains("Acct") && errorMessage.contains("does not have permission to launch" + + " instances from Tmpl") ) { + throw new EC2ServiceException( ClientError.AuthFailure, + "User not authorized to operate on the specified AMI"); + } else { + throw new EC2ServiceException( ClientError.AuthFailure, "User not authorized"); + } + } + else if (errorCode == 530) { + if ( errorMessage.contains("deviceId") && errorMessage.contains("is used by VM") ) { + throw new EC2ServiceException( ClientError.InvalidDevice_InUse, + "Specified Device is already being used by the VM"); + } else if (errorMessage.contains("Entity already exists") ){ + throw new EC2ServiceException( ClientError.InvalidParameterValue, + "Specified resource tag already exists"); + } else if ( errorMessage.contains("Template") && errorMessage.contains("has not been completely downloaded") ){ + throw new EC2ServiceException( ClientError.InvalidAMIID_NotFound, + "Specified ImageId is unavailable"); + } else if ( errorMessage.contains("cannot stop VM") && errorMessage.contains("when it is in state Starting") ){ + throw new EC2ServiceException( ClientError.IncorrectInstanceState, + "Unable to stop. One or more of the specified instances is in an incorrect state 'pending'"); + } else if ( errorMessage.contains("Failed to authorize security group ingress rule(s)") ) { + throw new EC2ServiceException(ClientError.InvalidParameterValue, "Specified Ip-permission is invalid" + + " or the Ip-permission already exists"); + } else if ( errorMessage.contains("Failed to reboot vm instance") ) { + throw new EC2ServiceException(ClientError.IncorrectInstanceState, + "Unable to reboot. One or more of the specified instances is in an incorrect state"); + } else if ( errorMessage.contains("specify a template that is not currently being downloaded") ) { + throw new EC2ServiceException(ClientError.IncorrectState, + "Unable to deregister. Image is not in the correct state 'available'"); + } else { + throw new EC2ServiceException( ServerError.InternalError, "An unexpected error occured"); + } + } + else if (errorCode == 534) { + if ( errorMessage.contains("Maximum number of resources of type 'volume' for account") + && errorMessage.contains("has been exceeded") ) { + throw new EC2ServiceException( ClientError.VolumeLimitExceeded, + "You have reached the limit on the number of volumes that can be created"); + } else if ( errorMessage.contains("Maximum number of resources of type 'public_ip' for account") + && errorMessage.contains("has been exceeded") ) { + throw new EC2ServiceException( ClientError.AddressLimitExceeded, + "You have reached the limit on the number of elastic ip addresses your account can have"); + } else if ( errorMessage.contains("Unable to apply save userdata entry on router") ) { + throw new EC2ServiceException( ClientError.InvalidParameterValue, + "The value supplied for parameter UserData is invalid"); + } else { + throw new EC2ServiceException( ServerError.InternalError, "An unexpected error occured"); + } + } + else if (errorCode == 533) { + if ( errorMessage.contains("Unable to create a deployment for VM") ) { + throw new EC2ServiceException( ClientError.InsufficientInstanceCapacity, + "There is insufficient capacity available to deploy a VM"); + } else if ( errorMessage.contains("Insufficient address capacity") ) { + throw new EC2ServiceException( ServerError.InsufficientAddressCapacity, + "Not enough available addresses to satisfy your minimum request"); + } else { + throw new EC2ServiceException( ServerError.InternalError, "There is insufficient capacity"); + } + } else if (errorCode == 401) { + if ( errorMessage.contains("Unauthorized") ) { + throw new EC2ServiceException(ClientError.AuthFailure, "User not authorised"); + } else { + throw new EC2ServiceException(ClientError.AuthFailure, "User not authorised"); + } + } else { + throw new EC2ServiceException( ServerError.InternalError, "An unexpected error occured"); + } + } else { + if ( errorMessage.contains("Unknown zoneName value") ) { + throw new EC2ServiceException( ClientError.InvalidZone_NotFound, + "AvailabilityZone name is invalid"); + } else if ( errorMessage.contains("No ServiceOffering found to be defined by name") ) { + throw new EC2ServiceException(ClientError.InvalidParameterValue, "Specified InstanceType is invalid"); + } else if ( errorMessage.contains("Specified Ip permission is invalid") ) { + throw new EC2ServiceException(ClientError.InvalidPermission_Malformed, "Specified Ip permission is invalid"); + } else if ( errorMessage.contains("One or more instanceIds do not exist, other instances rebooted") ) { + throw new EC2ServiceException(ClientError.InvalidInstanceID_NotFound, + "One or more InstanceId doesn't exist, other instances rebooted"); + } else if ( errorMessage.contains("Device is not supported") ) { + throw new EC2ServiceException(ClientError.InvalidParameterValue, + "Value specified for parameter Device is invalid"); + } else if ( errorMessage.contains("Volume is not attached to the Instance") ) { + throw new EC2ServiceException(ClientError.InvalidAttachment_NotFound, + "Specified Volume is not attached to the specified Instance"); + } else if ( errorMessage.contains("Volume is not attached to the Device") ) { + throw new EC2ServiceException(ClientError.InvalidAttachment_NotFound, + "Specified Volume is not attached to the specified device"); + } else if ( errorMessage.contains("Unable to create snapshot") ) { + throw new EC2ServiceException(ServerError.InternalError, + "Unable to create snapshot"); + } else if ( errorMessage.contains("Instance must be in stopped state") ) { + throw new EC2ServiceException(ClientError.IncorrectInstanceState, + "Specified instance is not in the correct state 'stopped'"); + } else if ( errorMessage.contains("Image couldn't be created") ) { + throw new EC2ServiceException(ServerError.InternalError, + "Unable to create image"); + } else if ( errorMessage.contains("Failed to start the stopped instance") ) { + throw new EC2ServiceException(ServerError.InternalError, + "Unable to start the instance that was stopped during image creation"); + } else if ( errorMessage.contains("One or more of instanceIds specified is in stopped state") ) { + throw new EC2ServiceException(ClientError.IncorrectInstanceState, + "Unable to reboot. One or more of the specified instances is in an incorrect state 'stopped'"); + } else if ( errorMessage.contains("Specified ipAddress doesn't exist") ) { + throw new EC2ServiceException(ClientError.InvalidParameterValue, "Specified publicIp doesn't exist"); + } else if ( errorMessage.contains("Min Count is greater than the number of instances left to allocate") ) { + throw new EC2ServiceException(ClientError.InstanceLimitExceeded, + "Specified MinCount parameter is greater than the number of instances you can create"); + } else if ( errorMessage.contains("instanceType not found") ) { + throw new EC2ServiceException(ClientError.InvalidParameterValue, + "Specified instanceType not found"); + } else if ( errorMessage.contains("zone not found") ) { + throw new EC2ServiceException(ClientError.InvalidZone_NotFound, + "Specified zone doesn't exist"); + } else if ( errorMessage.contains("Both groupId and groupName has been specified") ) { + throw new EC2ServiceException(ClientError.InvalidParameterCombination, + " for EC2 groups either a group ID or a group name is accepted"); + } else if ( errorMessage.contains("Insufficient Instance Capacity") ) { + throw new EC2ServiceException(ServerError.InsufficientInstanceCapacity, "Insufficient Instance Capacity" ); + } else if ( errorMessage.contains("Unable to find security group name") ) { + throw new EC2ServiceException(ClientError.InvalidGroup_NotFound, "Specified Security Group does not exist" ); + } else if ( errorMessage.contains("Instance not found") ) { + throw new EC2ServiceException(ClientError.InvalidInstanceID_NotFound, + "One or more of the specified instanceId not found"); + } else if ( errorMessage.contains("Cannot modify, instance should be in stopped state") ) { + throw new EC2ServiceException(ClientError.IncorrectInstanceState, + "Unable to modify instance attribute. Specified instance is not in the correct state 'stopped'"); + } else { + throw new EC2ServiceException( ServerError.InternalError, "An unexpected error occured"); + } + } + } } diff --git a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2GroupFilterSet.java b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2GroupFilterSet.java index dbc367c109d..bd79041518e 100644 --- a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2GroupFilterSet.java +++ b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2GroupFilterSet.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.Map; import com.cloud.bridge.service.exception.EC2ServiceException; +import com.cloud.bridge.service.exception.EC2ServiceException.ClientError; public class EC2GroupFilterSet { @@ -52,11 +53,9 @@ public class EC2GroupFilterSet { String filterName = param.getName(); String value = (String) filterTypes.get( filterName ); - if (null == value) - throw new EC2ServiceException( "Unsupported filter [" + filterName + "] - 1", 501 ); - - if (null != value && value.equalsIgnoreCase( "null" )) - throw new EC2ServiceException( "Unsupported filter [" + filterName + "] - 2", 501 ); + if ( value == null || value.equalsIgnoreCase("null")) { + throw new EC2ServiceException( ClientError.InvalidFilter, "Filter '" + filterName + "' is invalid"); + } // ToDo we could add checks to make sure the type of a filters value is correct (e.g., an integer) filterSet.add( param ); diff --git a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2ImageFilterSet.java b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2ImageFilterSet.java index aea3df05dea..646d20f56c4 100644 --- a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2ImageFilterSet.java +++ b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2ImageFilterSet.java @@ -25,6 +25,7 @@ import java.util.Map; import org.apache.log4j.Logger; import com.cloud.bridge.service.exception.EC2ServiceException; +import com.cloud.bridge.service.exception.EC2ServiceException.ClientError; public class EC2ImageFilterSet { protected final static Logger logger = Logger.getLogger(EC2ImageFilterSet.class); @@ -51,10 +52,9 @@ public class EC2ImageFilterSet { String filterName = param.getName(); if ( !filterName.startsWith("tag:") ) { String value = (String) filterTypes.get( filterName ); - if (null == value) - throw new EC2ServiceException( "Unsupported filter [" + filterName + "] - 1", 501 ); - if (null != value && value.equalsIgnoreCase( "null" )) - throw new EC2ServiceException( "Unsupported filter [" + filterName + "] - 2", 501 ); + if ( value == null || value.equalsIgnoreCase("null")) { + throw new EC2ServiceException( ClientError.InvalidFilter, "Filter '" + filterName + "' is invalid"); + } } filterSet.add( param ); diff --git a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2InstanceFilterSet.java b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2InstanceFilterSet.java index b5b7c7840df..a71d476438f 100644 --- a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2InstanceFilterSet.java +++ b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2InstanceFilterSet.java @@ -24,6 +24,7 @@ import java.util.Map; import com.cloud.bridge.service.EC2SoapServiceImpl; import com.cloud.bridge.service.exception.EC2ServiceException; +import com.cloud.bridge.service.exception.EC2ServiceException.ClientError; public class EC2InstanceFilterSet { @@ -59,11 +60,10 @@ public class EC2InstanceFilterSet { String filterName = param.getName(); String value = (String) filterTypes.get( filterName ); - if (null == value) - throw new EC2ServiceException( "Unsupported filter [" + filterName + "]", 501 ); + if ( value == null || value.equalsIgnoreCase("null") ) { + throw new EC2ServiceException( ClientError.InvalidFilter, "Filter '" + filterName + "' is invalid"); + } - if (null != value && value.equalsIgnoreCase( "null" )) - throw new EC2ServiceException( "Unsupported filter [" + filterName + "]", 501 ); // ToDo we could add checks to make sure the type of a filters value is correct (e.g., an integer) filterSet.add( param ); } diff --git a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2KeyPairFilterSet.java b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2KeyPairFilterSet.java index 2ad005b7dc2..d27972d5eda 100644 --- a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2KeyPairFilterSet.java +++ b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2KeyPairFilterSet.java @@ -25,6 +25,9 @@ import java.util.Map; import org.apache.log4j.Logger; +import com.cloud.bridge.service.exception.EC2ServiceException; +import com.cloud.bridge.service.exception.EC2ServiceException.ClientError; + public class EC2KeyPairFilterSet { protected final static Logger logger = Logger.getLogger(EC2KeyPairFilterSet.class); @@ -42,16 +45,9 @@ public class EC2KeyPairFilterSet { String filterName = param.getName(); String value = (String) filterTypes.get( filterName ); - if (null == value) { - // Changing this to silently ignore - logger.error("Unsupported filter [" + filterName + "] - 1"); - return; - } - - if (null != value && value.equalsIgnoreCase( "null" )) { - logger.error("Unsupported filter [" + filterName + "] - 2"); - return; - } + if ( value == null || value.equalsIgnoreCase("null") ) { + throw new EC2ServiceException( ClientError.InvalidFilter, "Filter '" + filterName + "' is invalid"); + } // ToDo we could add checks to make sure the type of a filters value is correct (e.g., an integer) filterSet.add( param ); diff --git a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2ModifyInstanceAttribute.java b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2ModifyInstanceAttribute.java new file mode 100644 index 00000000000..8394e335310 --- /dev/null +++ b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2ModifyInstanceAttribute.java @@ -0,0 +1,64 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.bridge.service.core.ec2; + + +public class EC2ModifyInstanceAttribute { + private String instanceId; + private String instanceType; + private String userData; + + /** + * @return instanceId + */ + public String getInstanceId() { + return instanceId; + } + + /** + * @param instanceId to set + */ + public void setInstanceId(String instanceId) { + this.instanceId = instanceId; + } + + /** + * @return instanceType + */ + public String getInstanceType() { + return instanceType; + } + + /** + * @param instanceType to set + */ + public void setInstanceType(String instanceType) { + this.instanceType = instanceType; + } + + /** + * @return userData + */ + public String getUserData() { + return userData; + } + + public void setUserData(String userData) { + this.userData = userData; + } + +} diff --git a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2RegisterImage.java b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2RegisterImage.java index d71329701ea..43b27df6c05 100644 --- a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2RegisterImage.java +++ b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2RegisterImage.java @@ -72,7 +72,7 @@ public class EC2RegisterImage { if (null != param) { if (!param.contains(":") || param.split(":").length < 4) { throw new EC2ServiceException( ClientError.InvalidParameterValue, "Supported format for " + - "'architecture' is format:zonename:ostypename:hypervisor" ); + "parameter 'architecture' is format:zonename:ostypename:hypervisor" ); } String parts[] = param.split( ":" ); format = parts[0]; @@ -80,9 +80,6 @@ public class EC2RegisterImage { osTypeName = parts[2]; hypervisor = parts[3]; } - else { - throw new EC2ServiceException(ClientError.Unsupported, "Missing Parameter -" + " architecture"); - } } public String getFormat() { diff --git a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2SnapshotFilterSet.java b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2SnapshotFilterSet.java index c2bed3ce161..ef395d9818e 100644 --- a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2SnapshotFilterSet.java +++ b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2SnapshotFilterSet.java @@ -27,6 +27,7 @@ import java.util.TimeZone; import com.cloud.bridge.service.UserContext; import com.cloud.bridge.service.exception.EC2ServiceException; +import com.cloud.bridge.service.exception.EC2ServiceException.ClientError; import com.cloud.bridge.util.DateHelper; import com.cloud.bridge.util.EC2RestAuth; @@ -56,12 +57,9 @@ public class EC2SnapshotFilterSet { String filterName = param.getName(); if (!filterName.startsWith("tag:")) { String value = (String) filterTypes.get( filterName ); - - if (null == value) - throw new EC2ServiceException( "Unsupported filter [" + filterName + "] - 1", 501 ); - - if (null != value && value.equalsIgnoreCase( "null" )) - throw new EC2ServiceException( "Unsupported filter [" + filterName + "] - 2", 501 ); + if ( value == null || value.equalsIgnoreCase("null") ) { + throw new EC2ServiceException( ClientError.InvalidFilter, "Filter '" + filterName + "' is invalid"); + } } // ToDo we could add checks to make sure the type of a filters value is correct (e.g., an integer) filterSet.add( param ); diff --git a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2TagsFilterSet.java b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2TagsFilterSet.java index c2d33c39ea1..06edc3870ef 100644 --- a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2TagsFilterSet.java +++ b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2TagsFilterSet.java @@ -26,6 +26,7 @@ import java.util.Map; import org.apache.log4j.Logger; import com.cloud.bridge.service.exception.EC2ServiceException; +import com.cloud.bridge.service.exception.EC2ServiceException.ClientError; public class EC2TagsFilterSet { protected final static Logger logger = Logger.getLogger(EC2TagsFilterSet.class); @@ -45,11 +46,9 @@ public class EC2TagsFilterSet { String filterName = param.getName(); String value = (String) filterTypes.get( filterName ); - if (null == value) - throw new EC2ServiceException( "Unsupported filter [" + filterName + "] - 1", 501 ); - - if (null != value && value.equalsIgnoreCase( "null" )) - throw new EC2ServiceException( "Unsupported filter [" + filterName + "] - 2", 501 ); + if ( value == null || value.equalsIgnoreCase("null") ) { + throw new EC2ServiceException( ClientError.InvalidFilter, "Filter '" + filterName + "' is invalid"); + } filterSet.add( param ); } diff --git a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2VolumeFilterSet.java b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2VolumeFilterSet.java index b8021f3d4ba..af132421474 100644 --- a/awsapi/src/com/cloud/bridge/service/core/ec2/EC2VolumeFilterSet.java +++ b/awsapi/src/com/cloud/bridge/service/core/ec2/EC2VolumeFilterSet.java @@ -26,6 +26,7 @@ import java.util.TimeZone; import java.util.Date; import com.cloud.bridge.service.exception.EC2ServiceException; +import com.cloud.bridge.service.exception.EC2ServiceException.ClientError; import com.cloud.bridge.util.EC2RestAuth; @@ -61,11 +62,9 @@ public class EC2VolumeFilterSet { String filterName = param.getName(); String value = (String) filterTypes.get( filterName ); - if (null == value) - throw new EC2ServiceException( "Unsupported filter [" + filterName + "] - 1", 501 ); - - if (null != value && value.equalsIgnoreCase( "null" )) - throw new EC2ServiceException( "Unsupported filter [" + filterName + "] - 2", 501 ); + if ( value == null || value.equalsIgnoreCase("null") ) { + throw new EC2ServiceException( ClientError.InvalidFilter, "Filter '" + filterName + "' is invalid"); + } // ToDo we could add checks to make sure the type of a filters value is correct (e.g., an integer) filterSet.add( param ); } diff --git a/awsapi/src/com/cloud/bridge/service/exception/EC2ServiceException.java b/awsapi/src/com/cloud/bridge/service/exception/EC2ServiceException.java index e1f515ad2c0..c21e6e9574f 100644 --- a/awsapi/src/com/cloud/bridge/service/exception/EC2ServiceException.java +++ b/awsapi/src/com/cloud/bridge/service/exception/EC2ServiceException.java @@ -26,11 +26,11 @@ public class EC2ServiceException extends RuntimeException { // ServerError & ClientError are correct as of schema version 2010-08-31 public static enum ServerError { - InsufficientAddressCapacity("Server.InsufficientAddressCapacity", 500), - InsufficientInstanceCapacity("Server.InsufficientInstanceCapacity", 500), - InsufficientReservedInstanceCapacity("Server.InsufficientReservedInstanceCapacity", 500), - InternalError("Server.InternalError", 500), - Unavailable("Server.Unavailable", 501); + InsufficientAddressCapacity("Server.InsufficientAddressCapacity", 500), + InsufficientInstanceCapacity("Server.InsufficientInstanceCapacity", 500), + InsufficientReservedInstanceCapacity("Server.InsufficientReservedInstanceCapacity", 500), + InternalError("Server.InternalError", 500), + Unavailable("Server.Unavailable", 501); private String errorString; private int httpErrorCode; @@ -45,58 +45,64 @@ public class EC2ServiceException extends RuntimeException { } public static enum ClientError { - AddressLimitExceeded("Client.AddressLimitExceeded", 400), - AttachmentLimitExceeded("Client.AttachmentLimitExceeded", 400), - AuthFailure("Client.AuthFailure", 400), - Blocked("Client.Blocked", 400), - FilterLimitExceeded("Client.FilterLimitExceeded", 400), - IdempotentParameterMismatch("Client.IdempotentParameterMismatch", 400), - IncorrectState("Client.IncorrectState", 400), - InstanceLimitExceeded("Client.InstanceLimitExceeded", 400), - InsufficientInstanceCapacity("Client.InsufficientInstanceCapacity", 400), - InsufficientReservedInstancesCapacity("Client.InsufficientReservedInstancesCapacity", 400), - InvalidAMIAttributeItemValue("Client.InvalidAMIAttributeItemValue", 400), - InvalidAMIID_Malformed("Client.InvalidAMIID.Malformed", 400), - InvalidAMIID_NotFound("Client.InvalidAMIID.NotFound", 400), - InvalidAMIID_Unavailable("Client.InvalidAMIID.Unavailable", 400), - InvalidAttachment_NotFound("Client.InvalidAttachment.NotFound", 400), - InvalidDevice_InUse("Client.InvalidDevice.InUse", 400), - InvalidGroup_Duplicate("Client.InvalidGroup.Duplicate", 400), - InvalidGroup_InUse("Client.InvalidGroup.InUse", 400), - InvalidGroup_NotFound("Client.InvalidGroup.NotFound", 400), - InvalidGroup_Reserved("Client.InvalidGroup.Reserved", 400), - InvalidInstanceID_Malformed("Client.InvalidInstanceID.Malformed", 400), - InvalidInstanceID_NotFound("Client.InvalidInstanceID.NotFound", 400), - InvalidIPAddress_InUse("Client.InvalidIPAddress.InUse", 400), - InvalidKeyPair_Duplicate("Client.InvalidKeyPair.Duplicate", 400), - InvalidKeyPair_Format("Client.InvalidKeyPair.Format", 400), - InvalidKeyPair_NotFound("Client.InvalidKeyPair.NotFound", 400), - InvalidManifest("Client.InvalidManifest", 400), - InvalidParameterCombination("Client.InvalidParameterCombination", 400), - InvalidParameterValue("Client.InvalidParameterValue", 400), - InvalidPermission_Duplicate("Client.InvalidPermission.Duplicate", 400), - InvalidPermission_Malformed("Client.InvalidPermission.Malformed", 400), - InvalidReservationID_Malformed("Client.InvalidReservationID.Malformed", 400), - InvalidReservationID_NotFound("Client.InvalidReservationID.NotFound", 400), - InvalidResourceId_Format("Client.InvalidResourceId.Format", 400), - InvalidSnapshotID_Malformed("Client.InvalidSnapshotID.Malformed", 400), - InvalidSnapshot_NotFound("Client.InvalidSnapshot.NotFound", 400), - InvalidUserID_Malformed("Client.InvalidUserID.Malformed", 400), - InvalidReservedInstancesId("Client.InvalidReservedInstancesId", 400), - InvalidReservedInstancesOfferingId("Client.InvalidReservedInstancesOfferingId", 400), - InvalidVolumeID_Duplicate("Client.InvalidVolumeID.Duplicate", 400), - InvalidVolumeID_Malformed("Client.InvalidVolumeID.Malformed", 400), - InvalidVolume_NotFound("Client.InvalidVolume.NotFound", 400), - InvalidVolumeID_ZoneMismatch("Client.InvalidVolumeID.ZoneMismatch", 400), - InvalidZone_NotFound("Client.InvalidZone.NotFound", 400), - NonEBSInstance("Client.NonEBSInstance", 400), - PendingVerification("Client.PendingVerification", 400), - PendingSnapshotLimitExceeded("Client.PendingSnapshotLimitExceeded", 400), - ReservedInstancesLimitExceeded("Client.ReservedInstancesLimitExceeded", 400), - SnapshotLimitExceeded("Client.SnapshotLimitExceeded", 400), - UnknownParameter("Client.UnknownParameter", 400), - Unsupported("Client.Unsupported", 400), - VolumeLimitExceeded("Client.VolumeLimitExceeded", 400); + AddressLimitExceeded("Client.AddressLimitExceeded", 400), + AttachmentLimitExceeded("Client.AttachmentLimitExceeded", 400), + AuthFailure("Client.AuthFailure", 400), + Blocked("Client.Blocked", 400), + DependencyViolation("Client.DependencyViolation", 400), + FilterLimitExceeded("Client.FilterLimitExceeded", 400), + IdempotentParameterMismatch("Client.IdempotentParameterMismatch", 400), + IncorrectState("Client.IncorrectState", 400), + IncorrectInstanceState("Client.IncorrectInstanceState", 400), + InstanceLimitExceeded("Client.InstanceLimitExceeded", 400), + InsufficientInstanceCapacity("Client.InsufficientInstanceCapacity", 400), + InsufficientReservedInstancesCapacity("Client.InsufficientReservedInstancesCapacity", 400), + InvalidAMIAttributeItemValue("Client.InvalidAMIAttributeItemValue", 400), + InvalidAMIID_Malformed("Client.InvalidAMIID.Malformed", 400), + InvalidAMIID_NotFound("Client.InvalidAMIID.NotFound", 400), + InvalidAMIID_Unavailable("Client.InvalidAMIID.Unavailable", 400), + InvalidAttachment_NotFound("Client.InvalidAttachment.NotFound", 400), + InvalidDevice_InUse("Client.InvalidDevice.InUse", 400), + InvalidFilter("Client.InvalidFilter", 400), + InvalidGroup_Duplicate("Client.InvalidGroup.Duplicate", 400), + InvalidGroup_InUse("Client.InvalidGroup.InUse", 400), + InvalidGroup_NotFound("Client.InvalidGroup.NotFound", 400), + InvalidGroup_Reserved("Client.InvalidGroup.Reserved", 400), + InvalidInstanceID_Malformed("Client.InvalidInstanceID.Malformed", 400), + InvalidInstanceID_NotFound("Client.InvalidInstanceID.NotFound", 400), + InvalidIPAddress_InUse("Client.InvalidIPAddress.InUse", 400), + InvalidKeyPair_Duplicate("Client.InvalidKeyPair.Duplicate", 400), + InvalidKeyPair_Format("Client.InvalidKeyPair.Format", 400), + InvalidKeyPair_NotFound("Client.InvalidKeyPair.NotFound", 400), + InvalidManifest("Client.InvalidManifest", 400), + InvalidParameterCombination("Client.InvalidParameterCombination", 400), + InvalidParameterValue("Client.InvalidParameterValue", 400), + InvalidPermission_Duplicate("Client.InvalidPermission.Duplicate", 400), + InvalidPermission_Malformed("Client.InvalidPermission.Malformed", 400), + InvalidReservationID_Malformed("Client.InvalidReservationID.Malformed", 400), + InvalidReservationID_NotFound("Client.InvalidReservationID.NotFound", 400), + InvalidSecurity_RequestHasExpired("Client.InvalidSecurity.RequestHasExpired", 400), + InvalidSnapshotID_Malformed("Client.InvalidSnapshotID.Malformed", 400), + InvalidSnapshot_NotFound("Client.InvalidSnapshot.NotFound", 400), + InvalidUserID_Malformed("Client.InvalidUserID.Malformed", 400), + InvalidReservedInstancesId("Client.InvalidReservedInstancesId", 400), + InvalidReservedInstancesOfferingId("Client.InvalidReservedInstancesOfferingId", 400), + InvalidVolumeID_Duplicate("Client.InvalidVolumeID.Duplicate", 400), + InvalidVolumeID_Malformed("Client.InvalidVolumeID.Malformed", 400), + InvalidVolume_NotFound("Client.InvalidVolume.NotFound", 400), + InvalidVolumeID_ZoneMismatch("Client.InvalidVolumeID.ZoneMismatch", 400), + InvalidZone_NotFound("Client.InvalidZone.NotFound", 400), + MissingParamter("Client.MissingParamter", 400), + NonEBSInstance("Client.NonEBSInstance", 400), + PendingVerification("Client.PendingVerification", 400), + PendingSnapshotLimitExceeded("Client.PendingSnapshotLimitExceeded", 400), + SignatureDoesNotMatch("Client.SignatureDoesNotMatch", 400), + ReservedInstancesLimitExceeded("Client.ReservedInstancesLimitExceeded", 400), + ResourceLimitExceeded("Client.ResourceLimitExceeded", 400), + SnapshotLimitExceeded("Client.SnapshotLimitExceeded", 400), + UnknownParameter("Client.UnknownParameter", 400), + Unsupported("Client.UnsupportedOperation", 400), + VolumeLimitExceeded("Client.VolumeLimitExceeded", 400); private String errorString; private int httpErrorCode; diff --git a/awsapi/src/com/cloud/stack/CloudStackClient.java b/awsapi/src/com/cloud/stack/CloudStackClient.java index 5017bd423dd..fa114f5a2cf 100644 --- a/awsapi/src/com/cloud/stack/CloudStackClient.java +++ b/awsapi/src/com/cloud/stack/CloudStackClient.java @@ -103,8 +103,8 @@ public class CloudStackClient { int jobStatus = queryAsyncJobResponse.getAsInt("queryasyncjobresultresponse.jobstatus"); switch(jobStatus) { case 2: - throw new Exception(queryAsyncJobResponse.getAsString("queryasyncjobresultresponse.jobresult.errorcode") + " " + - queryAsyncJobResponse.getAsString("queryasyncjobresultresponse.jobresult.errortext")); + throw new Exception(queryAsyncJobResponse.getAsString("queryasyncjobresultresponse.jobresult.errortext") + " Error Code - " + + queryAsyncJobResponse.getAsString("queryasyncjobresultresponse.jobresult.errorcode") ); case 0 : try { @@ -179,6 +179,7 @@ public class CloudStackClient { if(errorMessage == null){ errorMessage = "CloudStack API call HTTP response error, HTTP status code: " + statusCode; } + errorMessage = errorMessage.concat(" Error Code - " + Integer.toString(statusCode)); throw new IOException(errorMessage); } From 5b80af0cda6b88745ff9eeaefdd56345e0edc23b Mon Sep 17 00:00:00 2001 From: Likitha Shetty Date: Wed, 22 May 2013 18:09:06 +0530 Subject: [PATCH 082/108] Add missing license headers --- .../cloud/network/nicira/DestinationNatRule.java | 16 ++++++++++++++++ .../com/cloud/network/nicira/SourceNatRule.java | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/plugins/network-elements/nicira-nvp/src/com/cloud/network/nicira/DestinationNatRule.java b/plugins/network-elements/nicira-nvp/src/com/cloud/network/nicira/DestinationNatRule.java index 48310417b72..20afea9710b 100644 --- a/plugins/network-elements/nicira-nvp/src/com/cloud/network/nicira/DestinationNatRule.java +++ b/plugins/network-elements/nicira-nvp/src/com/cloud/network/nicira/DestinationNatRule.java @@ -1,3 +1,19 @@ +// 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.network.nicira; public class DestinationNatRule extends NatRule { diff --git a/plugins/network-elements/nicira-nvp/src/com/cloud/network/nicira/SourceNatRule.java b/plugins/network-elements/nicira-nvp/src/com/cloud/network/nicira/SourceNatRule.java index 5f85ccbc579..4132da48094 100644 --- a/plugins/network-elements/nicira-nvp/src/com/cloud/network/nicira/SourceNatRule.java +++ b/plugins/network-elements/nicira-nvp/src/com/cloud/network/nicira/SourceNatRule.java @@ -1,3 +1,19 @@ +// 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.network.nicira; public class SourceNatRule extends NatRule { From e720e8a1a62722c2c032aafe00848079fbd92a03 Mon Sep 17 00:00:00 2001 From: Chip Childers Date: Wed, 22 May 2013 12:14:47 -0400 Subject: [PATCH 083/108] CLOUDSTACK-2516: Adding upgrade steps to deal with authenticator changes --- docs/en-US/Release_Notes.xml | 53 +++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/docs/en-US/Release_Notes.xml b/docs/en-US/Release_Notes.xml index dca95d37c16..25e1175b148 100644 --- a/docs/en-US/Release_Notes.xml +++ b/docs/en-US/Release_Notes.xml @@ -4586,7 +4586,7 @@ under the License. versions of Citrix CloudStack (last version prior to Apache is 3.0.2) and from the releases made while CloudStack was in the Apache Incubator. If you run into any issues during upgrades, please feel free to ask questions on - users@apache.cloudstack.org or dev@apache.cloudstack.org. + users@cloudstack.apache.org or dev@cloudstack.apache.org.
Upgrade from 4.0.x to 4.1.0 This section will guide you from &PRODUCT; 4.0.x versions to &PRODUCT; 4.1.0. @@ -4647,6 +4647,23 @@ under the License. automatically. (If you're unsure, we recommend making a backup of the original components.xml to be on the safe side. + + After upgrading to 4.1, API clients are expected to send plain text passwords for login and user creation, instead of MD5 hash. Incase, api client changes are not acceptable, following changes are to be made for backward compatibility: + Modify componentsContext.xml, and make PlainTextUserAuthenticator as the default authenticator (1st entry in the userAuthenticators adapter list is default) + +<!-- Security adapters --> +<bean id="userAuthenticators" class="com.cloud.utils.component.AdapterList"> + <property name="Adapters"> + <list> + <ref bean="PlainTextUserAuthenticator"/> + <ref bean="MD5UserAuthenticator"/> + <ref bean="LDAPUserAuthenticator"/> + </list> + </property> +</bean> + + PlainTextUserAuthenticator works the same way MD5UserAuthenticator worked prior to 4.1. + If you are using Ubuntu, follow this procedure to upgrade your packages. If not, skip to step . @@ -5110,6 +5127,23 @@ service cloudstack-agent start node. + + After upgrading to 4.1, API clients are expected to send plain text passwords for login and user creation, instead of MD5 hash. Incase, api client changes are not acceptable, following changes are to be made for backward compatibility: + Modify componentsContext.xml, and make PlainTextUserAuthenticator as the default authenticator (1st entry in the userAuthenticators adapter list is default) + +<!-- Security adapters --> +<bean id="userAuthenticators" class="com.cloud.utils.component.AdapterList"> + <property name="Adapters"> + <list> + <ref bean="PlainTextUserAuthenticator"/> + <ref bean="MD5UserAuthenticator"/> + <ref bean="LDAPUserAuthenticator"/> + </list> + </property> +</bean> + + PlainTextUserAuthenticator works the same way MD5UserAuthenticator worked prior to 4.1. + Start the first Management Server. Do not start any other Management Server nodes yet. @@ -5688,6 +5722,23 @@ service cloudstack-agent start + + After upgrading to 4.1, API clients are expected to send plain text passwords for login and user creation, instead of MD5 hash. Incase, api client changes are not acceptable, following changes are to be made for backward compatibility: + Modify componentsContext.xml, and make PlainTextUserAuthenticator as the default authenticator (1st entry in the userAuthenticators adapter list is default) + +<!-- Security adapters --> +<bean id="userAuthenticators" class="com.cloud.utils.component.AdapterList"> + <property name="Adapters"> + <list> + <ref bean="PlainTextUserAuthenticator"/> + <ref bean="MD5UserAuthenticator"/> + <ref bean="LDAPUserAuthenticator"/> + </list> + </property> +</bean> + + PlainTextUserAuthenticator works the same way MD5UserAuthenticator worked prior to 4.1. + If you have made changes to your existing copy of the /etc/cloud/management/db.properties file in your previous-version From 29574267c9776aed5d17d2495602f03bfb4c3487 Mon Sep 17 00:00:00 2001 From: Jessica Wang Date: Tue, 21 May 2013 11:59:03 -0700 Subject: [PATCH 084/108] CLOUDSTACK-747: UI - create network offering - default sourceNat type as per account instead of per zone. --- ui/scripts/configuration.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/scripts/configuration.js b/ui/scripts/configuration.js index fdeaba015b7..808b34289d7 100644 --- a/ui/scripts/configuration.js +++ b/ui/scripts/configuration.js @@ -1741,9 +1741,9 @@ dependsOn: 'service.SourceNat.isEnabled', select: function(args) { args.response.success({ - data: [ - { id: 'perzone', description: 'Per zone'}, - { id: 'peraccount', description: 'Per account'} + data: [ + { id: 'peraccount', description: 'Per account'}, + { id: 'perzone', description: 'Per zone'} ] }); } From c7b902024c321776bc1d00cf23dcf91793cda1d7 Mon Sep 17 00:00:00 2001 From: Jessica Wang Date: Wed, 22 May 2013 10:42:49 -0700 Subject: [PATCH 085/108] CLOUDSTACK-747: internalLb in VPC - Infrastructure menu - network service provider - add InternalLbVm. Clicking it will lead to a screen that can enable/disable provider and have instances tab that can start/stop LB Instance. --- ui/scripts/system.js | 385 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 383 insertions(+), 2 deletions(-) diff --git a/ui/scripts/system.js b/ui/scripts/system.js index 31e47e4a87d..2e23fc40202 100644 --- a/ui/scripts/system.js +++ b/ui/scripts/system.js @@ -2708,7 +2708,363 @@ } } }, - + + InternalLbVm: { + id: 'InternalLbVm', + label: 'InternalLbVm', + isMaximized: true, + type: 'detailView', + fields: { + name: { label: 'label.name' }, + ipaddress: { label: 'label.ip.address' }, + state: { label: 'label.status', indicator: { 'Enabled': 'on' } } + }, + tabs: { + network: { + title: 'label.network', + fields: [ + { + name: { label: 'label.name' } + }, + { + id: { label: 'label.id' }, + state: { label: 'label.state' }, + physicalnetworkid: { label: 'label.physical.network.ID' }, + destinationphysicalnetworkid: { label: 'label.destination.physical.network.id' }, + supportedServices: { label: 'label.supported.services' } + } + ], + dataProvider: function(args) { + refreshNspData("InternalLbVm"); + args.response.success({ + actionFilter: virtualRouterProviderActionFilter, + data: $.extend(nspMap["InternalLbVm"], { + supportedServices: nspMap["InternalLbVm"].servicelist.join(', ') + }) + }); + } + }, + + instances: { + title: 'label.instances', + listView: { + label: 'label.virtual.appliances', + id: 'internallbinstances', + fields: { + name: { label: 'label.name' }, + zonename: { label: 'label.zone' }, + routerType: { + label: 'label.type' + }, + state: { + converter: function(str) { + // For localization + return str; + }, + label: 'label.status', + indicator: { + 'Running': 'on', + 'Stopped': 'off', + 'Error': 'off' + } + } + }, + dataProvider: function(args) { + var array1 = []; + if(args.filterBy != null) { + if(args.filterBy.search != null && args.filterBy.search.by != null && args.filterBy.search.value != null) { + switch(args.filterBy.search.by) { + case "name": + if(args.filterBy.search.value.length > 0) + array1.push("&keyword=" + args.filterBy.search.value); + break; + } + } + } + + var routers = []; + $.ajax({ + url: createURL("listInternalLoadBalancerVMs&zoneid=" + selectedZoneObj.id + "&listAll=true&page=" + args.page + "&pagesize=" + pageSize + array1.join("")), + success: function(json) { + var items = json.listinternallbvmssresponse.internalloadbalancervm ? + json.listinternallbvmssresponse.internalloadbalancervm : []; + + $(items).map(function(index, item) { + routers.push(item); + }); + + // Get project routers + $.ajax({ + url: createURL("listInternalLoadBalancerVMs&zoneid=" + selectedZoneObj.id + "&listAll=true&page=" + args.page + "&pagesize=" + pageSize + array1.join("") + "&projectid=-1"), + success: function(json) { + var items = json.listinternallbvmssresponse.internalloadbalancervm ? + json.listinternallbvmssresponse.internalloadbalancervm : []; + + $(items).map(function(index, item) { + routers.push(item); + }); + args.response.success({ + actionFilter: internallbinstanceActionfilter, + data: $(routers).map(mapRouterType) + }); + } + }); + } + }); + }, + detailView: { + name: 'Virtual applicance details', + actions: { + start: { + label: 'Start LB VM', + messages: { + confirm: function(args) { + return 'Please confirm you want to start LB VM'; + }, + notification: function(args) { + return 'Start LB VM'; + } + }, + action: function(args) { + $.ajax({ + url: createURL('startInternalLoadBalancerVM&id=' + args.context.internallbinstances[0].id), + dataType: 'json', + async: true, + success: function(json) { + var jid = json.startinternallbvmresponse.jobid; + args.response.success({ + _custom: { + jobId: jid, + getUpdatedItem: function(json) { + return json.queryasyncjobresultresponse.jobresult.internalloadbalancervm; + }, + getActionFilter: function() { + return internallbinstanceActionfilter; + } + } + }); + } + }); + }, + notification: { + poll: pollAsyncJobResult + } + }, + + stop: { + label: 'Stop LB VM', + createForm: { + title: 'Please confirm you want to stop LB VM', + desc: 'Stop LB VM', + fields: { + forced: { + label: 'force.stop', + isBoolean: true, + isChecked: false + } + } + }, + messages: { + notification: function(args) { + return 'Stop LB VM'; + } + }, + action: function(args) { + var array1 = []; + array1.push("&forced=" + (args.data.forced == "on")); + $.ajax({ + url: createURL('stopInternalLoadBalancerVM&id=' + args.context.internallbinstances[0].id + array1.join("")), + dataType: 'json', + async: true, + success: function(json) { + var jid = json.stopinternallbvmresponse.jobid; + args.response.success({ + _custom: { + jobId: jid, + getUpdatedItem: function(json) { + return json.queryasyncjobresultresponse.jobresult.internalloadbalancervm; + }, + getActionFilter: function() { + return internallbinstanceActionfilter; + } + } + }); + } + }); + }, + notification: { + poll: pollAsyncJobResult + } + } + }, + tabs: { + details: { + title: 'label.details', + preFilter: function(args) { + var hiddenFields = []; + if (!args.context.internallbinstances[0].project) { + hiddenFields.push('project'); + hiddenFields.push('projectid'); + } + if(selectedZoneObj.networktype == 'Basic') { + hiddenFields.push('publicip'); //In Basic zone, guest IP is public IP. So, publicip is not returned by listRouters API. Only guestipaddress is returned by listRouters API. + } + return hiddenFields; + }, + fields: [ + { + name: { label: 'label.name' }, + project: { label: 'label.project' } + }, + { + id: { label: 'label.id' }, + projectid: { label: 'label.project.id' }, + state: { label: 'label.state' }, + guestnetworkid: { label: 'label.network.id' }, + publicip: { label: 'label.public.ip' }, + guestipaddress: { label: 'label.guest.ip' }, + linklocalip: { label: 'label.linklocal.ip' }, + hostname: { label: 'label.host' }, + serviceofferingname: { label: 'label.compute.offering' }, + networkdomain: { label: 'label.network.domain' }, + domain: { label: 'label.domain' }, + account: { label: 'label.account' }, + created: { label: 'label.created', converter: cloudStack.converters.toLocalDate }, + isredundantrouter: { + label: 'label.redundant.router', + converter: cloudStack.converters.toBooleanText + }, + redundantRouterState: { label: 'label.redundant.state' } + } + ], + dataProvider: function(args) { + $.ajax({ + url: createURL("listInternalLoadBalancerVMs&id=" + args.context.internallbinstances[0].id), + dataType: 'json', + async: true, + success: function(json) { + var jsonObj = json.listinternallbvmssresponse.internalloadbalancervm[0]; + addExtraPropertiesToRouterInstanceObject(jsonObj); + args.response.success({ + actionFilter: internallbinstanceActionfilter, + data: jsonObj + }); + } + }); + } + }, + nics: { + title: 'label.nics', + multiple: true, + fields: [ + { + name: { label: 'label.name', header: true }, + type: { label: 'label.type' }, + traffictype: { label: 'label.traffic.type' }, + networkname: { label: 'label.network.name' }, + netmask: { label: 'label.netmask' }, + ipaddress: { label: 'label.ip.address' }, + id: { label: 'label.id' }, + networkid: { label: 'label.network.id' }, + isolationuri: { label: 'label.isolation.uri' }, + broadcasturi: { label: 'label.broadcast.uri' } + } + ], + dataProvider: function(args) { + $.ajax({ + url: createURL("listInternalLoadBalancerVMs&id=" + args.context.internallbinstances[0].id), + dataType: 'json', + async: true, + success: function(json) { + var jsonObj = json.listinternallbvmssresponse.internalloadbalancervm[0].nic; + + args.response.success({ + actionFilter: internallbinstanceActionfilter, + data: $.map(jsonObj, function(nic, index) { + var name = 'NIC ' + (index + 1); + if (nic.isdefault) { + name += ' (' + _l('label.default') + ')'; + } + return $.extend(nic, { + name: name + }); + }) + }); + } + }); + } + } + } + } + } + } + }, + actions: { + enable: { + label: 'label.enable.provider', + action: function(args) { + $.ajax({ + url: createURL("updateNetworkServiceProvider&id=" + nspMap["InternalLbVm"].id + "&state=Enabled"), + dataType: "json", + success: function(json) { + var jid = json.updatenetworkserviceproviderresponse.jobid; + args.response.success( + {_custom: + { + jobId: jid, + getUpdatedItem: function(json) { + $(window).trigger('cloudStack.fullRefresh'); + } + } + } + ); + } + }); + }, + messages: { + confirm: function(args) { + return 'message.confirm.enable.provider'; + }, + notification: function() { + return 'label.enable.provider'; + } + }, + notification: { poll: pollAsyncJobResult } + }, + disable: { + label: 'label.disable.provider', + action: function(args) { + $.ajax({ + url: createURL("updateNetworkServiceProvider&id=" + nspMap["InternalLbVm"].id + "&state=Disabled"), + dataType: "json", + success: function(json) { + var jid = json.updatenetworkserviceproviderresponse.jobid; + args.response.success( + {_custom: + { + jobId: jid, + getUpdatedItem: function(json) { + $(window).trigger('cloudStack.fullRefresh'); + } + } + } + ); + } + }); + }, + messages: { + confirm: function(args) { + return 'message.confirm.disable.provider'; + }, + notification: function() { + return 'label.disable.provider'; + } + }, + notification: { poll: pollAsyncJobResult } + } + } + }, + vpcVirtualRouter: { id: 'vpcVirtualRouterProviders', label: 'VPC Virtual Router', @@ -12020,7 +12376,20 @@ } return allowedActions; } + + var internallbinstanceActionfilter = function(args) { + var jsonObj = args.context.item; + var allowedActions = []; + if (jsonObj.state == 'Running') { + allowedActions.push("stop"); + } + else if (jsonObj.state == 'Stopped') { + allowedActions.push("start"); + } + return allowedActions; + } + var systemvmActionfilter = function(args) { var jsonObj = args.context.item; var allowedActions = []; @@ -12102,6 +12471,9 @@ case "VirtualRouter": nspMap["virtualRouter"] = items[i]; break; + case "InternalLbVm": + nspMap["InternalLbVm"] = items[i]; + break; case "VpcVirtualRouter": nspMap["vpcVirtualRouter"] = items[i]; break; @@ -12178,7 +12550,16 @@ state: nspMap.midoNet? nspMap.midoNet.state : 'Disabled' } ); - nspHardcodingArray.push( + + nspHardcodingArray.push( + { + id: 'InternalLbVm', + name: 'InternalLbVm', + state: nspMap.InternalLbVm ? nspMap.InternalLbVm.state : 'Disabled' + } + ); + + nspHardcodingArray.push( { id: 'vpcVirtualRouter', name: 'VPC Virtual Router', From c7976b668586181267bf9d8ceba50d194fd96d1c Mon Sep 17 00:00:00 2001 From: Jessica Wang Date: Wed, 22 May 2013 11:03:56 -0700 Subject: [PATCH 086/108] CLOUDSTACK-747: internal LB in VPC - remove module internalLbProvider since internalLbVm section has been added in system.js --- .../internalLbProvider/internalLbProvider.js | 182 ------------------ ui/modules/modules.js | 3 +- 2 files changed, 1 insertion(+), 184 deletions(-) delete mode 100644 ui/modules/internalLbProvider/internalLbProvider.js diff --git a/ui/modules/internalLbProvider/internalLbProvider.js b/ui/modules/internalLbProvider/internalLbProvider.js deleted file mode 100644 index aaa386ee246..00000000000 --- a/ui/modules/internalLbProvider/internalLbProvider.js +++ /dev/null @@ -1,182 +0,0 @@ -// 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. -(function($, cloudStack) { - cloudStack.modules.internalLbProvider = function(module) { - var internalLbDeviceViewAll = [ - { - label: 'Devices', - path: '_zone.internalLbDevices' - } - ]; - - var internalLbListView = { - id: 'internalLbDevices', - fields: { - resourcename: { label: 'Resource Name' }, - provider: { label: 'Provider' } - }, - dataProvider: function(args) { - args.response.success({ data: [] }); - }, - actions: { - add: { - label: 'Add internal LB device', - - messages: { - notification: function(args) { - return 'Add internal LB device'; - } - }, - - createForm: { - title: 'Add internal LB device', - fields: { - hostname: { - label: 'label.host', - validation: { required: true } - }, - username: { - label: 'label.username', - validation: { required: true } - }, - password: { - label: 'label.password', - isPassword: true, - validation: { required: true } - } - } - }, - - action: function(args) { - args.response.success(); - }, - - notification: { - poll: function(args) { - args.complete(); - } - } - } - }, - - detailView: { - name: 'Internal LB resource details', - actions: { - remove: { - label: 'delete Internal LB resource', - messages: { - confirm: function(args) { - return 'Please confirm you want to delete Internal LB resource'; - }, - notification: function(args) { - return 'delete Internal LB resource'; - } - }, - action: function(args) { - args.response.success(); - }, - notification: { - poll: function(args) { - args.complete(); - } - } - } - }, - - tabs: { - details: { - title: 'label.details', - fields: [ - { - resourcename: { label: 'Resource Name' } - }, - { - resourceid: { label: 'Resource ID'}, - provider: { label: 'Provider' }, - RESOURCE_NAME: { label: 'Resource Name'} - } - ], - dataProvider: function(args) { - args.response.success({ data: args.context.internalLbDevices[0] }); - } - } - } - } - }; - - var internalLbProviderDetailView = { - id: 'internalLbProvider', - label: 'internal LB', - viewAll: internalLbDeviceViewAll, - tabs: { - details: { - title: 'label.details', - fields: [ - { - name: { label: 'label.name' } - }, - { - state: { label: 'label.state' }, - id: { label: 'label.id' }, - servicelist: { - label: 'Services', - converter: function(args){ - if(args) - return args.join(', '); - else - return ''; - } - } - } - ], - dataProvider: function(args) { - $.ajax({ - url: createURL('listNetworkServiceProviders'), - data: { - name: 'InternalLb', - physicalnetworkid: args.context.physicalNetworks[0].id - }, - success: function(json){ - var items = json.listnetworkserviceprovidersresponse.networkserviceprovider; - if(items != null && items.length > 0) { - args.response.success({ data: items[0] }); - } - else { - args.response.success({ - data: { - name: 'InternalLb', - state: 'Disabled' - } - }) - } - } - }); - } - } - } - }; - - module.infrastructure.networkServiceProvider({ - id: 'internalLb', - name: 'Internal LB', - //state: 'Disabled', //don't know state until log in and visit Infrastructure menu > zone detail > physical network > network service providers - listView: internalLbListView, - - detailView: internalLbProviderDetailView - }); - }; -}(jQuery, cloudStack)); diff --git a/ui/modules/modules.js b/ui/modules/modules.js index 31701220dd8..d4502a195bc 100644 --- a/ui/modules/modules.js +++ b/ui/modules/modules.js @@ -18,7 +18,6 @@ cloudStack.modules = [ 'infrastructure', 'vnmcNetworkProvider', - 'vnmcAsa1000v', - 'internalLbProvider' + 'vnmcAsa1000v' ]; }(jQuery, cloudStack)); From 19321d4eeb7accd2bd67af1c8b2c2a5988a23ac0 Mon Sep 17 00:00:00 2001 From: Min Chen Date: Wed, 22 May 2013 15:27:04 -0700 Subject: [PATCH 087/108] CLOUDSTACK-2629: ListRouters with networkid throws exception. --- server/src/com/cloud/api/query/QueryManagerImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/com/cloud/api/query/QueryManagerImpl.java b/server/src/com/cloud/api/query/QueryManagerImpl.java index a126925e5f0..c586a7b19f2 100644 --- a/server/src/com/cloud/api/query/QueryManagerImpl.java +++ b/server/src/com/cloud/api/query/QueryManagerImpl.java @@ -1095,7 +1095,7 @@ public class QueryManagerImpl extends ManagerBase implements QueryService { } if (networkId != null) { - sc.setJoinParameters("nicSearch", "networkId", networkId); + sc.setParameters("networkId", networkId); } if (vpcId != null) { From f41d398cf75cee5e700d668252b4d52d5fe4f1a7 Mon Sep 17 00:00:00 2001 From: Alena Prokharchyk Date: Wed, 22 May 2013 16:05:03 -0700 Subject: [PATCH 088/108] Remote access vpn: method name change --- api/src/com/cloud/network/vpn/RemoteAccessVpnService.java | 2 +- .../api/command/user/vpn/DeleteRemoteAccessVpnCmd.java | 2 +- server/src/com/cloud/network/NetworkManagerImpl.java | 2 +- .../src/com/cloud/network/vpn/RemoteAccessVpnManagerImpl.java | 4 ++-- server/src/com/cloud/user/AccountManagerImpl.java | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/api/src/com/cloud/network/vpn/RemoteAccessVpnService.java b/api/src/com/cloud/network/vpn/RemoteAccessVpnService.java index d637da638ac..b554719f188 100644 --- a/api/src/com/cloud/network/vpn/RemoteAccessVpnService.java +++ b/api/src/com/cloud/network/vpn/RemoteAccessVpnService.java @@ -32,7 +32,7 @@ public interface RemoteAccessVpnService { RemoteAccessVpn createRemoteAccessVpn(long vpnServerAddressId, String ipRange, boolean openFirewall, long networkId) throws NetworkRuleConflictException; - void destroyRemoteAccessVpn(long vpnServerAddressId, Account caller) throws ResourceUnavailableException; + void destroyRemoteAccessVpnForIp(long vpnServerAddressId, Account caller) throws ResourceUnavailableException; RemoteAccessVpn startRemoteAccessVpn(long vpnServerAddressId, boolean openFirewall) throws ResourceUnavailableException; VpnUser addVpnUser(long vpnOwnerId, String userName, String password); diff --git a/api/src/org/apache/cloudstack/api/command/user/vpn/DeleteRemoteAccessVpnCmd.java b/api/src/org/apache/cloudstack/api/command/user/vpn/DeleteRemoteAccessVpnCmd.java index 5b1c5c6b4e6..06c25305a00 100644 --- a/api/src/org/apache/cloudstack/api/command/user/vpn/DeleteRemoteAccessVpnCmd.java +++ b/api/src/org/apache/cloudstack/api/command/user/vpn/DeleteRemoteAccessVpnCmd.java @@ -84,7 +84,7 @@ public class DeleteRemoteAccessVpnCmd extends BaseAsyncCmd { @Override public void execute() throws ResourceUnavailableException { - _ravService.destroyRemoteAccessVpn(publicIpId, UserContext.current().getCaller()); + _ravService.destroyRemoteAccessVpnForIp(publicIpId, UserContext.current().getCaller()); } @Override diff --git a/server/src/com/cloud/network/NetworkManagerImpl.java b/server/src/com/cloud/network/NetworkManagerImpl.java index 0f43b87685e..254510b15a9 100755 --- a/server/src/com/cloud/network/NetworkManagerImpl.java +++ b/server/src/com/cloud/network/NetworkManagerImpl.java @@ -3273,7 +3273,7 @@ public class NetworkManagerImpl extends ManagerBase implements NetworkManager, L // the code would be triggered s_logger.debug("Cleaning up remote access vpns as a part of public IP id=" + ipId + " release..."); try { - _vpnMgr.destroyRemoteAccessVpn(ipId, caller); + _vpnMgr.destroyRemoteAccessVpnForIp(ipId, caller); } catch (ResourceUnavailableException e) { s_logger.warn("Unable to destroy remote access vpn for ip id=" + ipId + " as a part of ip release", e); success = false; diff --git a/server/src/com/cloud/network/vpn/RemoteAccessVpnManagerImpl.java b/server/src/com/cloud/network/vpn/RemoteAccessVpnManagerImpl.java index 062743b23af..9e7bb13b867 100755 --- a/server/src/com/cloud/network/vpn/RemoteAccessVpnManagerImpl.java +++ b/server/src/com/cloud/network/vpn/RemoteAccessVpnManagerImpl.java @@ -226,10 +226,10 @@ public class RemoteAccessVpnManagerImpl extends ManagerBase implements RemoteAcc } @Override @DB - public void destroyRemoteAccessVpn(long ipId, Account caller) throws ResourceUnavailableException { + public void destroyRemoteAccessVpnForIp(long ipId, Account caller) throws ResourceUnavailableException { RemoteAccessVpnVO vpn = _remoteAccessVpnDao.findByPublicIpAddress(ipId); if (vpn == null) { - s_logger.debug("vpn id=" + ipId + " does not exists "); + s_logger.debug("there are no Remote access vpns for public ip address id=" + ipId); return; } diff --git a/server/src/com/cloud/user/AccountManagerImpl.java b/server/src/com/cloud/user/AccountManagerImpl.java index aac8d19eb0e..7421422d294 100755 --- a/server/src/com/cloud/user/AccountManagerImpl.java +++ b/server/src/com/cloud/user/AccountManagerImpl.java @@ -628,7 +628,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M try { for (RemoteAccessVpnVO vpn : remoteAccessVpns) { - _remoteAccessVpnMgr.destroyRemoteAccessVpn(vpn.getServerAddressId(), caller); + _remoteAccessVpnMgr.destroyRemoteAccessVpnForIp(vpn.getServerAddressId(), caller); } } catch (ResourceUnavailableException ex) { s_logger.warn("Failed to cleanup remote access vpn resources as a part of account id=" + accountId + " cleanup due to Exception: ", ex); From c52879b88c4cc418289e87927f5c10ddbfe62f82 Mon Sep 17 00:00:00 2001 From: Alena Prokharchyk Date: Wed, 22 May 2013 16:14:47 -0700 Subject: [PATCH 089/108] CLOUDSTACK-2627: disassociate ip address - assign return value of releaseIpAddress backend call to the result returned to the API caller --- .../api/command/user/address/DisassociateIPAddrCmd.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/org/apache/cloudstack/api/command/user/address/DisassociateIPAddrCmd.java b/api/src/org/apache/cloudstack/api/command/user/address/DisassociateIPAddrCmd.java index 8f78fe3a959..41691ea86d0 100644 --- a/api/src/org/apache/cloudstack/api/command/user/address/DisassociateIPAddrCmd.java +++ b/api/src/org/apache/cloudstack/api/command/user/address/DisassociateIPAddrCmd.java @@ -76,9 +76,9 @@ public class DisassociateIPAddrCmd extends BaseAsyncCmd { UserContext.current().setEventDetails("Ip Id: " + getIpAddressId()); boolean result = false; if (!isPortable(id)) { - _networkService.releaseIpAddress(getIpAddressId()); + result = _networkService.releaseIpAddress(getIpAddressId()); } else { - _networkService.releaseIpAddress(getIpAddressId()); + result = _networkService.releaseIpAddress(getIpAddressId()); } if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); From a69101dceb535af2d6c6382b83d26f14d4ef03bf Mon Sep 17 00:00:00 2001 From: Prachi Damle Date: Wed, 22 May 2013 16:17:14 -0700 Subject: [PATCH 090/108] - To check if a host is in avoid set, DPM should check the zones/pods/cluster/hosts in teh avoid list - not just the hosts in avoid list. --- .../deploy/DeploymentPlanningManagerImpl.java | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/server/src/com/cloud/deploy/DeploymentPlanningManagerImpl.java b/server/src/com/cloud/deploy/DeploymentPlanningManagerImpl.java index c86d5e1a1b2..795b526c403 100644 --- a/server/src/com/cloud/deploy/DeploymentPlanningManagerImpl.java +++ b/server/src/com/cloud/deploy/DeploymentPlanningManagerImpl.java @@ -380,7 +380,7 @@ public class DeploymentPlanningManagerImpl extends ManagerBase implements Deploy if (planner instanceof DeploymentClusterPlanner) { - ExcludeList PlannerAvoidInput = new ExcludeList(avoids.getDataCentersToAvoid(), + ExcludeList plannerAvoidInput = new ExcludeList(avoids.getDataCentersToAvoid(), avoids.getPodsToAvoid(), avoids.getClustersToAvoid(), avoids.getHostsToAvoid(), avoids.getPoolsToAvoid()); @@ -388,19 +388,19 @@ public class DeploymentPlanningManagerImpl extends ManagerBase implements Deploy if (clusterList != null && !clusterList.isEmpty()) { // planner refactoring. call allocators to list hosts - ExcludeList PlannerAvoidOutput = new ExcludeList(avoids.getDataCentersToAvoid(), + ExcludeList plannerAvoidOutput = new ExcludeList(avoids.getDataCentersToAvoid(), avoids.getPodsToAvoid(), avoids.getClustersToAvoid(), avoids.getHostsToAvoid(), avoids.getPoolsToAvoid()); - resetAvoidSet(PlannerAvoidOutput, PlannerAvoidInput); + resetAvoidSet(plannerAvoidOutput, plannerAvoidInput); dest = checkClustersforDestination(clusterList, vmProfile, plan, avoids, dc, - getPlannerUsage(planner), PlannerAvoidOutput); + getPlannerUsage(planner), plannerAvoidOutput); if (dest != null) { return dest; } // reset the avoid input to the planners - resetAvoidSet(avoids, PlannerAvoidOutput); + resetAvoidSet(avoids, plannerAvoidOutput); } else { return null; @@ -815,12 +815,8 @@ public class DeploymentPlanningManagerImpl extends ManagerBase implements Deploy // remove any hosts/pools that the planners might have added // to get the list of hosts/pools that Allocators flagged as 'avoid' - if (allocatorAvoidOutput.getHostsToAvoid() != null && plannerAvoidOutput.getHostsToAvoid() != null) { - allocatorAvoidOutput.getHostsToAvoid().removeAll(plannerAvoidOutput.getHostsToAvoid()); - } - if (allocatorAvoidOutput.getPoolsToAvoid() != null && plannerAvoidOutput.getPoolsToAvoid() != null) { - allocatorAvoidOutput.getPoolsToAvoid().removeAll(plannerAvoidOutput.getPoolsToAvoid()); - } + + resetAvoidSet(allocatorAvoidOutput, plannerAvoidOutput); // if all hosts or all pools in the cluster are in avoid set after this // pass, then put the cluster in avoid set. @@ -829,8 +825,7 @@ public class DeploymentPlanningManagerImpl extends ManagerBase implements Deploy List allhostsInCluster = _hostDao.listAllUpAndEnabledNonHAHosts(Host.Type.Routing, clusterVO.getId(), clusterVO.getPodId(), clusterVO.getDataCenterId(), null); for (HostVO host : allhostsInCluster) { - if (allocatorAvoidOutput.getHostsToAvoid() == null - || !allocatorAvoidOutput.getHostsToAvoid().contains(host.getId())) { + if (!allocatorAvoidOutput.shouldAvoid(host)) { // there's some host in the cluster that is not yet in avoid set avoidAllHosts = false; } @@ -839,8 +834,7 @@ public class DeploymentPlanningManagerImpl extends ManagerBase implements Deploy List allPoolsInCluster = _storagePoolDao.findPoolsByTags(clusterVO.getDataCenterId(), clusterVO.getPodId(), clusterVO.getId(), null); for (StoragePoolVO pool : allPoolsInCluster) { - if (allocatorAvoidOutput.getPoolsToAvoid() == null - || !allocatorAvoidOutput.getPoolsToAvoid().contains(pool.getId())) { + if (!allocatorAvoidOutput.shouldAvoid(pool)) { // there's some pool in the cluster that is not yet in avoid set avoidAllPools = false; } From 4786420986b60955ca8f084f24b69f9d07153ab5 Mon Sep 17 00:00:00 2001 From: Mice Xia Date: Thu, 23 May 2013 09:54:46 +0800 Subject: [PATCH 091/108] [Automation] fix CLOUDSTACK-2546 Failed to add second NIC to vm in KVM environment --- test/integration/smoke/test_nic.py | 51 +++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/test/integration/smoke/test_nic.py b/test/integration/smoke/test_nic.py index bae6dfda15d..8e8d3407dfb 100644 --- a/test/integration/smoke/test_nic.py +++ b/test/integration/smoke/test_nic.py @@ -79,11 +79,35 @@ class Services: "PortForwarding": 'VirtualRouter', }, }, + "network_offering_shared": { + "name": 'Test Network offering shared', + "displaytext": 'Test Network offering Shared', + "guestiptype": 'Shared', + "supportedservices": 'Dhcp,Dns,UserData', + "traffictype": 'GUEST', + "specifyVlan" : "True", + "specifyIpRanges" : "True", + "serviceProviderList" : { + "Dhcp": 'VirtualRouter', + "Dns": 'VirtualRouter', + "UserData": 'VirtualRouter', + }, + }, "network": { "name": "Test Network", "displaytext": "Test Network", "acltype": "Account", }, + "network2": { + "name": "Test Network Shared", + "displaytext": "Test Network Shared", + "vlan" :1201, + "gateway" :"172.16.15.1", + "netmask" :"255.255.255.0", + "startip" :"172.16.15.21", + "endip" :"172.16.15.41", + "acltype": "Account", + }, # ISO settings for Attach/Detach ISO tests "iso": { "displaytext": "Test ISO", @@ -176,6 +200,14 @@ class TestDeployVM(cloudstackTestCase): self.network_offering.update(self.apiclient, state='Enabled') # Enable Network offering self.services["network"]["networkoffering"] = self.network_offering.id + self.network_offering_shared = NetworkOffering.create( + self.apiclient, + self.services["network_offering_shared"], + ) + self.cleanup.insert(0, self.network_offering_shared) + self.network_offering_shared.update(self.apiclient, state='Enabled') # Enable Network offering + self.services["network2"]["networkoffering"] = self.network_offering_shared.id + ################ ### Test Network self.test_network = Network.create( @@ -185,6 +217,14 @@ class TestDeployVM(cloudstackTestCase): self.account.domainid, ) self.cleanup.insert(0, self.test_network) + self.test_network2 = Network.create( + self.apiclient, + self.services["network2"], + self.account.name, + self.account.domainid, + zoneid=self.services["network"]["zoneid"] + ) + self.cleanup.insert(0, self.test_network2) except Exception as ex: self.debug("Exception during NIC test SETUP!: " + str(ex)) self.assertEqual(True, False, "Exception during NIC test SETUP!: " + str(ex)) @@ -201,10 +241,10 @@ class TestDeployVM(cloudstackTestCase): accountid=self.account.name, domainid=self.account.domainid, serviceofferingid=self.service_offering.id, - mode=self.services['mode'] + mode=self.services['mode'], + networkids=[self.test_network.id] ) self.cleanup.insert(0, self.virtual_machine) - list_vm_response = list_virtual_machines( self.apiclient, id=self.virtual_machine.id @@ -256,7 +296,7 @@ class TestDeployVM(cloudstackTestCase): existing_nic_id = vm_response.nic[0].id # 1. add a nic - add_response = self.virtual_machine.add_nic(self.apiclient, self.test_network.id) + add_response = self.virtual_machine.add_nic(self.apiclient, self.test_network2.id) time.sleep(5) # now go get the vm list? @@ -308,8 +348,9 @@ class TestDeployVM(cloudstackTestCase): sawException = True self.assertEqual(sawException, True, "Make sure we cannot delete the default NIC") - - self.virtual_machine.remove_nic(self.apiclient, existing_nic_id) + self.virtual_machine.update_default_nic(self.apiclient, existing_nic_id) + time.sleep(5) + self.virtual_machine.remove_nic(self.apiclient, new_nic_id) time.sleep(5) list_vm_response = list_virtual_machines( From 795fd803da3eadee0fae1a13d0e97ae57b239657 Mon Sep 17 00:00:00 2001 From: Marcus Sorensen Date: Wed, 22 May 2013 23:12:30 -0600 Subject: [PATCH 092/108] Summary: KVM - double migration can fail Detail: Undefine VM after migration. Lingering domain definitions cause migrations back to the original host to fail, since domain already exists. BUG-ID: CLOUDSTACK-2640 Bugfix-for: 4.1.0,4.2.0 Signed-off-by: Marcus Sorensen 1369285950 -0600 --- .../hypervisor/kvm/resource/LibvirtComputingResource.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java index b31fb5dfbe5..09038559ab9 100755 --- a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java +++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java @@ -2728,6 +2728,9 @@ ServerResource { } finally { try { if (dm != null) { + if (dm.isPersistent() == 1) { + dm.undefine(); + } dm.free(); } if (dconn != null) { From fce59b8fc42412f87ccbda5cf4f53e1465935070 Mon Sep 17 00:00:00 2001 From: Marcus Sorensen Date: Wed, 22 May 2013 23:30:49 -0600 Subject: [PATCH 093/108] Summary: Code cleanup for KVM - look up domains by name rather than coversion Detail: We do two strange things, #1, when a vm is created, we create the uuid for the domain by converting the name into a uuid, then subsequently, any time we look for a domain, we convert its name to a uuid and domainLookupByUUID. This is an unnecessary obfuscation, so instead we just do a domainLookupByName. As a bonus, we are now setting the domain's uuid to be the cloudstack uuid. It is no longer used anywhere, but will be less confusing to admins. This shouldn't affect upgrades, since we can always lookup existing VMs by name. Signed-off-by: Marcus Sorensen 1369287049 -0600 --- .../resource/LibvirtComputingResource.java | 45 +++++++------------ .../LibvirtComputingResourceTest.java | 4 +- 2 files changed, 18 insertions(+), 31 deletions(-) diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java index 09038559ab9..1e20d75a6a0 100755 --- a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java +++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java @@ -2619,8 +2619,7 @@ ServerResource { Domain vms = null; while (retry-- > 0) { try { - vms = conn.domainLookupByUUID(UUID.nameUUIDFromBytes(vmName - .getBytes())); + vms = conn.domainLookupByName(vmName); State s = convertToState(vms.getInfo().state); return s; } catch (final LibvirtException e) { @@ -2712,8 +2711,7 @@ ServerResource { try { conn = LibvirtConnection.getConnectionByVmName(cmd.getVmName()); ifaces = getInterfaces(conn, vmName); - dm = conn.domainLookupByUUID(UUID.nameUUIDFromBytes(vmName - .getBytes())); + dm = conn.domainLookupByName(vmName); dconn = new Connect("qemu+tcp://" + cmd.getDestinationIp() + "/system"); /* @@ -3162,8 +3160,7 @@ ServerResource { protected LibvirtVMDef createVMFromSpec(VirtualMachineTO vmTO) { LibvirtVMDef vm = new LibvirtVMDef(); vm.setDomainName(vmTO.getName()); - vm.setDomUUID(UUID.nameUUIDFromBytes(vmTO.getName().getBytes()) - .toString()); + vm.setDomUUID(vmTO.getUuid()); vm.setDomDescription(vmTO.getOs()); GuestDef guest = new GuestDef(); @@ -3587,8 +3584,7 @@ ServerResource { KVMStoragePool attachingPool = attachingDisk.getPool(); try { if (!attach) { - dm = conn.domainLookupByUUID(UUID.nameUUIDFromBytes(vmName - .getBytes())); + dm = conn.domainLookupByName(vmName); LibvirtDomainXMLParser parser = new LibvirtDomainXMLParser(); String xml = dm.getXMLDesc(0); parser.parseDomainXML(xml); @@ -3637,9 +3633,7 @@ ServerResource { InternalErrorException { Domain dm = null; try { - dm = conn.domainLookupByUUID(UUID.nameUUIDFromBytes((vmName - .getBytes()))); - + dm = conn.domainLookupByName(vmName); if (attach) { s_logger.debug("Attaching device: " + xml); dm.attachDevice(xml); @@ -3870,8 +3864,7 @@ ServerResource { for (; i < 5; i++) { try { Connect conn = LibvirtConnection.getConnectionByVmName(vm); - dm = conn.domainLookupByUUID(UUID.nameUUIDFromBytes(vm - .getBytes())); + dm = conn.domainLookupByName(vm); DomainInfo.DomainState vps = dm.getInfo().state; if (vps != null && vps != DomainInfo.DomainState.VIR_DOMAIN_SHUTOFF @@ -4008,8 +4001,7 @@ ServerResource { for (int i = 0; i < vms.length; i++) { try { - dm = conn.domainLookupByUUID(UUID.nameUUIDFromBytes(vms[i] - .getBytes())); + dm = conn.domainLookupByName(vms[i]); DomainInfo.DomainState ps = dm.getInfo().state; final State state = convertToState(ps); @@ -4114,8 +4106,7 @@ ServerResource { Domain dm = null; String msg = null; try { - dm = conn.domainLookupByUUID(UUID.nameUUIDFromBytes(vmName - .getBytes())); + dm = conn.domainLookupByName(vmName); String vmDef = dm.getXMLDesc(0); s_logger.debug(vmDef); msg = stopVM(conn, vmName); @@ -4157,8 +4148,7 @@ ServerResource { /* Retry 3 times, to make sure we can get the vm's status */ for (int i = 0; i < 3; i++) { try { - dm = conn.domainLookupByUUID(UUID.nameUUIDFromBytes(vmName - .getBytes())); + dm = conn.domainLookupByName(vmName); state = dm.getInfo().state; break; } catch (LibvirtException e) { @@ -4194,8 +4184,7 @@ ServerResource { protected String stopVM(Connect conn, String vmName, boolean force) { Domain dm = null; try { - dm = conn.domainLookupByUUID(UUID.nameUUIDFromBytes(vmName - .getBytes())); + dm = conn.domainLookupByName(vmName); int persist = dm.isPersistent(); if (force) { if (dm.isActive() == 1) { @@ -4282,8 +4271,7 @@ ServerResource { LibvirtDomainXMLParser parser = new LibvirtDomainXMLParser(); Domain dm = null; try { - dm = conn.domainLookupByUUID(UUID.nameUUIDFromBytes(vmName - .getBytes())); + dm = conn.domainLookupByName(vmName); String xmlDesc = dm.getXMLDesc(0); parser.parseDomainXML(xmlDesc); return parser.getVncPort(); @@ -4328,8 +4316,7 @@ ServerResource { LibvirtDomainXMLParser parser = new LibvirtDomainXMLParser(); Domain dm = null; try { - dm = conn.domainLookupByUUID(UUID.nameUUIDFromBytes(vmName - .getBytes())); + dm = conn.domainLookupByName(vmName); String xmlDesc = dm.getXMLDesc(0); parser.parseDomainXML(xmlDesc); return parser.getDescription(); @@ -4427,15 +4414,14 @@ ServerResource { private Domain getDomain(Connect conn, String vmName) throws LibvirtException { return conn - .domainLookupByUUID(UUID.nameUUIDFromBytes(vmName.getBytes())); + .domainLookupByName(vmName); } protected List getInterfaces(Connect conn, String vmName) { LibvirtDomainXMLParser parser = new LibvirtDomainXMLParser(); Domain dm = null; try { - dm = conn.domainLookupByUUID(UUID.nameUUIDFromBytes(vmName - .getBytes())); + dm = conn.domainLookupByName(vmName); parser.parseDomainXML(dm.getXMLDesc(0)); return parser.getInterfaces(); @@ -4457,8 +4443,7 @@ ServerResource { LibvirtDomainXMLParser parser = new LibvirtDomainXMLParser(); Domain dm = null; try { - dm = conn.domainLookupByUUID(UUID.nameUUIDFromBytes(vmName - .getBytes())); + dm = conn.domainLookupByName(vmName); parser.parseDomainXML(dm.getXMLDesc(0)); return parser.getDisks(); diff --git a/plugins/hypervisors/kvm/test/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java b/plugins/hypervisors/kvm/test/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java index 39e36d65c65..0bafd073f68 100644 --- a/plugins/hypervisors/kvm/test/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java +++ b/plugins/hypervisors/kvm/test/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java @@ -60,6 +60,7 @@ public class LibvirtComputingResourceTest { LibvirtComputingResource lcr = new LibvirtComputingResource(); VirtualMachineTO to = new VirtualMachineTO(id, name, VirtualMachine.Type.User, cpus, speed, minRam, maxRam, BootloaderType.HVM, os, false, false, vncPassword); to.setVncAddr(vncAddr); + to.setUuid("b0f0a72d-7efb-3cad-a8ff-70ebf30b3af9"); LibvirtVMDef vm = lcr.createVMFromSpec(to); vm.setHvsType(_hyperVisorType); @@ -135,6 +136,7 @@ public class LibvirtComputingResourceTest { LibvirtComputingResource lcr = new LibvirtComputingResource(); VirtualMachineTO to = new VirtualMachineTO(id, name, VirtualMachine.Type.User, cpus, minSpeed, maxSpeed, minRam, maxRam, BootloaderType.HVM, os, false, false, vncPassword); to.setVncAddr(vncAddr); + to.setUuid("b0f0a72d-7efb-3cad-a8ff-70ebf30b3af9"); LibvirtVMDef vm = lcr.createVMFromSpec(to); vm.setHvsType(_hyperVisorType); @@ -181,4 +183,4 @@ public class LibvirtComputingResourceTest { assertEquals(vmStr, vm.toString()); } -} \ No newline at end of file +} From 4fdd0261fe82b3a3fa7c8360ebb49429f0c36d7c Mon Sep 17 00:00:00 2001 From: Pranav Saxena Date: Thu, 23 May 2013 14:56:32 +0530 Subject: [PATCH 094/108] port forwarding issues with the listNics API response parameter --- ui/scripts/network.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/scripts/network.js b/ui/scripts/network.js index 6b310ce0e83..3eef1367e97 100755 --- a/ui/scripts/network.js +++ b/ui/scripts/network.js @@ -50,7 +50,7 @@ nicId: nic.id }, success: function(json) { - var nic = json.listnics.nic[0]; + var nic = json.listnicsresponse.nic[0]; var ips = nic.secondaryip ? nic.secondaryip : []; var ipSelection = []; From 4e7f87681c8ac15aee56e39693d7a328ccf97f7f Mon Sep 17 00:00:00 2001 From: Koushik Das Date: Thu, 23 May 2013 16:07:26 +0530 Subject: [PATCH 095/108] CLOUDSTACK-1396: uuid field is NULL in hypervisore_capability table for Vmware ESXI 5.1 Adding uuid at the time of inserting new entry in hypervisor_capabilities table --- setup/db/db/schema-410to420.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup/db/db/schema-410to420.sql b/setup/db/db/schema-410to420.sql index 7b5e9cb5f29..ea7a18df0ff 100644 --- a/setup/db/db/schema-410to420.sql +++ b/setup/db/db/schema-410to420.sql @@ -25,8 +25,8 @@ SET foreign_key_checks = 0; ALTER TABLE `cloud`.`hypervisor_capabilities` ADD COLUMN `max_hosts_per_cluster` int unsigned DEFAULT NULL COMMENT 'Max. hosts in cluster supported by hypervisor'; ALTER TABLE `cloud`.`hypervisor_capabilities` ADD COLUMN `storage_motion_supported` int(1) unsigned DEFAULT 0 COMMENT 'Is storage motion supported'; UPDATE `cloud`.`hypervisor_capabilities` SET `max_hosts_per_cluster`=32 WHERE `hypervisor_type`='VMware'; -INSERT IGNORE INTO `cloud`.`hypervisor_capabilities`(hypervisor_type, hypervisor_version, max_guests_limit, security_group_enabled, max_data_volumes_limit, storage_motion_supported) VALUES ('XenServer', '6.1.0', 50, 1, 13, 1); -INSERT IGNORE INTO `cloud`.`hypervisor_capabilities`(hypervisor_type, hypervisor_version, max_guests_limit, security_group_enabled, max_hosts_per_cluster) VALUES ('VMware', '5.1', 128, 0, 32); +INSERT IGNORE INTO `cloud`.`hypervisor_capabilities`(uuid, hypervisor_type, hypervisor_version, max_guests_limit, security_group_enabled, max_data_volumes_limit, storage_motion_supported) VALUES (UUID(), 'XenServer', '6.1.0', 50, 1, 13, 1); +INSERT IGNORE INTO `cloud`.`hypervisor_capabilities`(uuid, hypervisor_type, hypervisor_version, max_guests_limit, security_group_enabled, max_hosts_per_cluster) VALUES (UUID(), 'VMware', '5.1', 128, 0, 32); DELETE FROM `cloud`.`configuration` where name='vmware.percluster.host.max'; INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'AgentManager', 'xen.nics.max', '7', 'Maximum allowed nics for Vms created on Xen'); INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Network', 'DEFAULT', 'management-server', 'midonet.apiserver.address', 'http://localhost:8081', 'Specify the address at which the Midonet API server can be contacted (if using Midonet)'); @@ -348,7 +348,7 @@ ALTER TABLE `cloud`.`remote_access_vpn` ADD COLUMN `uuid` varchar(40) UNIQUE; -- START: support for LXC -INSERT IGNORE INTO `cloud`.`hypervisor_capabilities`(hypervisor_type, hypervisor_version, max_guests_limit, security_group_enabled) VALUES ('LXC', 'default', 50, 1); +INSERT IGNORE INTO `cloud`.`hypervisor_capabilities`(uuid, hypervisor_type, hypervisor_version, max_guests_limit, security_group_enabled) VALUES (UUID(), 'LXC', 'default', 50, 1); ALTER TABLE `cloud`.`physical_network_traffic_types` ADD COLUMN `lxc_network_label` varchar(255) DEFAULT 'cloudbr0' COMMENT 'The network name label of the physical device dedicated to this traffic on a LXC host'; UPDATE configuration SET value='KVM,XenServer,VMware,BareMetal,Ovm,LXC' WHERE name='hypervisor.list'; From 80a3c0535e0a8ad3edba3713a61d063a176191d8 Mon Sep 17 00:00:00 2001 From: Devdeep Singh Date: Thu, 23 May 2013 16:50:33 +0530 Subject: [PATCH 096/108] CLOUDSTACK-2643: Implicit dedication planner isn't listed in non oss builds. The entry for implicit planner was missing in nonoss component context xml. Added it there. --- client/tomcatconf/nonossComponentContext.xml.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/tomcatconf/nonossComponentContext.xml.in b/client/tomcatconf/nonossComponentContext.xml.in index 1b6ee6eb089..6fa9d38baa4 100644 --- a/client/tomcatconf/nonossComponentContext.xml.in +++ b/client/tomcatconf/nonossComponentContext.xml.in @@ -251,7 +251,7 @@ - + From cc492305cead9307b9959fa696e11f0fad415c0a Mon Sep 17 00:00:00 2001 From: Chip Childers Date: Thu, 23 May 2013 17:50:15 +0100 Subject: [PATCH 097/108] CLOUDSTACK-2612: Correcting missing db qualifiers in schema-302to40.sql Signed-off-by: Chip Childers --- setup/db/db/schema-302to40.sql | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/setup/db/db/schema-302to40.sql b/setup/db/db/schema-302to40.sql index f17f067c6ef..7fa73483db6 100644 --- a/setup/db/db/schema-302to40.sql +++ b/setup/db/db/schema-302to40.sql @@ -134,7 +134,7 @@ ALTER TABLE `cloud`.`account` ADD COLUMN `default_zone_id` bigint unsigned; ALTER TABLE `cloud`.`account` ADD CONSTRAINT `fk_account__default_zone_id` FOREIGN KEY `fk_account__default_zone_id`(`default_zone_id`) REFERENCES `data_center`(`id`) ON DELETE CASCADE; -DELETE FROM `cloud`.`storage_pool_host_ref` WHERE pool_id IN (SELECT id FROM storage_pool WHERE removed IS NOT NULL); +DELETE FROM `cloud`.`storage_pool_host_ref` WHERE pool_id IN (SELECT id FROM `cloud`.`storage_pool` WHERE removed IS NOT NULL); DROP TABLE IF EXISTS `cloud`.`cluster_vsm_map`; DROP TABLE IF EXISTS `cloud`.`virtual_supervisor_module`; @@ -179,14 +179,14 @@ CREATE TABLE `cloud`.`port_profile` ( PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -DELETE FROM `cloud`.`storage_pool_host_ref` WHERE pool_id IN (SELECT id FROM storage_pool WHERE removed IS NOT NULL); +DELETE FROM `cloud`.`storage_pool_host_ref` WHERE pool_id IN (SELECT id FROM `cloud`.`storage_pool` WHERE removed IS NOT NULL); ALTER TABLE `cloud`.`service_offering` MODIFY `nw_rate` smallint(5) unsigned DEFAULT '200' COMMENT 'network rate throttle mbits/s'; -- RBD Primary Storage pool support (commit: 406fd95d87bfcdbb282d65589ab1fb6e9fd0018a) -ALTER TABLE `storage_pool` ADD `user_info` VARCHAR( 255 ) NULL COMMENT 'Authorization information for the storage pool. Used by network filesystems' AFTER `host_address`; +ALTER TABLE `cloud`.`storage_pool` ADD `user_info` VARCHAR( 255 ) NULL COMMENT 'Authorization information for the storage pool. Used by network filesystems' AFTER `host_address`; -- Resource tags (commit: 62d45b9670520a1ee8b520509393d4258c689b50) CREATE TABLE `cloud`.`resource_tags` ( @@ -232,9 +232,9 @@ CREATE TABLE `cloud`.`nicira_nvp_nic_map` ( -- Remove the unique constraint on physical_network_id, provider_name from physical_network_service_providers -- Because the name of this contraint is not set we need this roundabout way -- The key is also used by the foreign key constraint so drop and recreate that one -ALTER TABLE physical_network_service_providers DROP FOREIGN KEY fk_pnetwork_service_providers__physical_network_id; +ALTER TABLE `cloud`.`physical_network_service_providers` DROP FOREIGN KEY fk_pnetwork_service_providers__physical_network_id; -SET @constraintname = (select CONCAT(CONCAT('DROP INDEX ', A.CONSTRAINT_NAME), ' ON physical_network_service_providers' ) +SET @constraintname = (select CONCAT(CONCAT('DROP INDEX ', A.CONSTRAINT_NAME), ' ON cloud.physical_network_service_providers' ) from information_schema.key_column_usage A JOIN information_schema.key_column_usage B ON B.table_name = 'physical_network_service_providers' AND B.COLUMN_NAME = 'provider_name' AND A.COLUMN_NAME ='physical_network_id' AND B.CONSTRAINT_NAME=A.CONSTRAINT_NAME where A.table_name = 'physical_network_service_providers' LIMIT 1); @@ -243,7 +243,7 @@ PREPARE stmt1 FROM @constraintname; EXECUTE stmt1; DEALLOCATE PREPARE stmt1; -AlTER TABLE physical_network_service_providers ADD CONSTRAINT `fk_pnetwork_service_providers__physical_network_id` FOREIGN KEY (`physical_network_id`) REFERENCES `physical_network`(`id`) ON DELETE CASCADE; +AlTER TABLE `cloud`.`physical_network_service_providers` ADD CONSTRAINT `fk_pnetwork_service_providers__physical_network_id` FOREIGN KEY (`physical_network_id`) REFERENCES `physical_network`(`id`) ON DELETE CASCADE; UPDATE `cloud`.`configuration` SET description='In second, timeout for creating volume from snapshot' WHERE name='create.volume.from.snapshot.wait'; ALTER TABLE `cloud`.`data_center` ADD COLUMN `is_local_storage_enabled` tinyint NOT NULL DEFAULT 0 COMMENT 'Is local storage offering enabled for this data center; 1: enabled, 0: not'; From 623a26e469ca7354a7dbe6b8954276985e289687 Mon Sep 17 00:00:00 2001 From: Murali Reddy Date: Thu, 23 May 2013 21:59:32 +0530 Subject: [PATCH 098/108] CLOUDSTACK-2587: netscaler_pod_ref DB upgrade is missing from 3.0.2 to 4.x added missing schema as part of 4.0 to 4.1 upgrade --- setup/db/db/schema-40to410.sql | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/setup/db/db/schema-40to410.sql b/setup/db/db/schema-40to410.sql index b7b1c7a91dd..381a4cea612 100644 --- a/setup/db/db/schema-40to410.sql +++ b/setup/db/db/schema-40to410.sql @@ -1639,3 +1639,15 @@ INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Usage', 'DEFAULT', 'manageme INSERT IGNORE INTO `cloud`.`guest_os` (id, uuid, category_id, display_name) VALUES (163, UUID(), 10, 'Ubuntu 12.04 (32-bit)'); INSERT IGNORE INTO `cloud`.`guest_os` (id, uuid, category_id, display_name) VALUES (164, UUID(), 10, 'Ubuntu 12.04 (64-bit)'); + +DROP TABLE IF EXISTS `cloud`.`netscaler_pod_ref`; +CREATE TABLE `cloud`.`netscaler_pod_ref` ( + `id` bigint unsigned NOT NULL auto_increment COMMENT 'id', + `external_load_balancer_device_id` bigint unsigned NOT NULL COMMENT 'id of external load balancer device', + `pod_id` bigint unsigned NOT NULL COMMENT 'pod id', + PRIMARY KEY (`id`), + CONSTRAINT `fk_ns_pod_ref__pod_id` FOREIGN KEY (`pod_id`) REFERENCES `cloud`.`host_pod_ref`(`id`) ON DELETE CASCADE, + CONSTRAINT `fk_ns_pod_ref__device_id` FOREIGN KEY (`external_load_balancer_device_id`) REFERENCES `external_load_balancer_devices`(`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'eip.use.multiple.netscalers' , 'false', 'Should be set to true, if there will be multiple NetScaler devices providing EIP service in a zone'); From 1201d623a7091517a1e26bc4b82c5daeea3c155f Mon Sep 17 00:00:00 2001 From: Hugo Trippaers Date: Thu, 23 May 2013 16:58:12 +0200 Subject: [PATCH 099/108] CLOUDSTACK-862 Updated documentation with the new features available in the Nicira NVP plugin. --- docs/en-US/CloudStack_Nicira_NVP_Guide.xml | 1 + docs/en-US/images/nvp-add-controller.png | Bin 0 -> 35928 bytes docs/en-US/images/nvp-enable-provider.png | Bin 0 -> 32158 bytes docs/en-US/images/nvp-network-offering.png | Bin 0 -> 104060 bytes .../en-US/images/nvp-physical-network-stt.png | Bin 0 -> 27317 bytes docs/en-US/images/nvp-vpc-offering-edit.png | Bin 0 -> 29279 bytes docs/en-US/plugin-niciranvp-about.xml | 2 +- .../plugin-niciranvp-devicemanagement.xml | 32 ++--- docs/en-US/plugin-niciranvp-features.xml | 67 +++++++-- docs/en-US/plugin-niciranvp-introduction.xml | 5 +- .../plugin-niciranvp-networkofferings.xml | 131 ++++++++++++++++++ docs/en-US/plugin-niciranvp-physicalnet.xml | 37 +++++ docs/en-US/plugin-niciranvp-preparations.xml | 11 +- docs/en-US/plugin-niciranvp-provider.xml | 28 ++-- docs/en-US/plugin-niciranvp-revisions.xml | 14 ++ docs/en-US/plugin-niciranvp-tables.xml | 105 ++++++++++---- docs/en-US/plugin-niciranvp-usage.xml | 11 +- ...nvp-guide.xml => plugin-niciranvp-vpc.xml} | 19 ++- docs/en-US/plugin-niciranvp-vpcfeatures.xml | 28 ++++ .../plugin-niciranvp-vpcnetworkoffering.xml | 81 +++++++++++ docs/en-US/plugin-niciranvp-vpcoffering.xml | 38 +++++ 21 files changed, 516 insertions(+), 94 deletions(-) create mode 100644 docs/en-US/images/nvp-add-controller.png create mode 100644 docs/en-US/images/nvp-enable-provider.png create mode 100644 docs/en-US/images/nvp-network-offering.png create mode 100644 docs/en-US/images/nvp-physical-network-stt.png create mode 100644 docs/en-US/images/nvp-vpc-offering-edit.png create mode 100644 docs/en-US/plugin-niciranvp-networkofferings.xml create mode 100644 docs/en-US/plugin-niciranvp-physicalnet.xml rename docs/en-US/{plugin-niciranvp-guide.xml => plugin-niciranvp-vpc.xml} (65%) create mode 100644 docs/en-US/plugin-niciranvp-vpcfeatures.xml create mode 100644 docs/en-US/plugin-niciranvp-vpcnetworkoffering.xml create mode 100644 docs/en-US/plugin-niciranvp-vpcoffering.xml diff --git a/docs/en-US/CloudStack_Nicira_NVP_Guide.xml b/docs/en-US/CloudStack_Nicira_NVP_Guide.xml index 7f156d5dc09..5431fc1cb43 100644 --- a/docs/en-US/CloudStack_Nicira_NVP_Guide.xml +++ b/docs/en-US/CloudStack_Nicira_NVP_Guide.xml @@ -48,6 +48,7 @@ + diff --git a/docs/en-US/images/nvp-add-controller.png b/docs/en-US/images/nvp-add-controller.png new file mode 100644 index 0000000000000000000000000000000000000000..e02d31f0a37edca722c3a97312b2e5103ec723c5 GIT binary patch literal 35928 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 HfLfH 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 Lm2kLVVfI$`EgD)y^rNc}_fYo_?A~iSqM_k* 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 SWzM%`3Hs7>~{WmF09u4W)}Jw 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 VNVU?Bai@Tv(gSt*;``qP5hkv&4P9qb@@) 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ⅇ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 literal 0 HcmV?d00001 diff --git a/docs/en-US/plugin-niciranvp-about.xml b/docs/en-US/plugin-niciranvp-about.xml index 8d2e20e7756..cfab83c73c3 100644 --- a/docs/en-US/plugin-niciranvp-about.xml +++ b/docs/en-US/plugin-niciranvp-about.xml @@ -1,5 +1,5 @@ - %BOOK_ENTITIES; diff --git a/docs/en-US/plugin-niciranvp-devicemanagement.xml b/docs/en-US/plugin-niciranvp-devicemanagement.xml index 57b8eee9d7d..761c39f3179 100644 --- a/docs/en-US/plugin-niciranvp-devicemanagement.xml +++ b/docs/en-US/plugin-niciranvp-devicemanagement.xml @@ -21,27 +21,15 @@ under the License. -->
- Device-management - In &PRODUCT; 4.0.x each Nicira NVP setup is considered a "device" that can be added and removed from a physical network. To complete the configuration of the Nicira NVP plugin a device needs to be added to the physical network using the "addNiciraNVPDevice" API call. The plugin is now enabled on the physical network and any guest networks created on that network will be provisioned using the Nicira NVP Controller. - The plugin introduces a set of new API calls to manage the devices, see below or refer to the API reference. - - addNiciraNvpDevice - - - physicalnetworkid: the UUID of the physical network on which the device is configured - hostname: the IP address of the NVP controller - username: the username for access to the NVP API - password: the password for access to the NVP API - transportzoneuuid: the UUID of the transportzone - - - deleteNiciraNVPDevice - - - nvpdeviceid: the UUID of the device - - - listNiciraNVPDevices - + Device Management + In &PRODUCT; a Nicira NVP setup is considered a "device" that can be added and removed from a physical network. To complete the configuration of the Nicira NVP plugin a device needs to be added to the physical network. Press the "Add NVP Controller" button on the provider panel and enter the configuration details. + + + + + + nvp-physical-network-stt.png: a screenshot of the device configuration popup. + +
diff --git a/docs/en-US/plugin-niciranvp-features.xml b/docs/en-US/plugin-niciranvp-features.xml index c346bfb64e3..e439f1b4923 100644 --- a/docs/en-US/plugin-niciranvp-features.xml +++ b/docs/en-US/plugin-niciranvp-features.xml @@ -22,12 +22,63 @@ -->
Features of the Nicira NVP Plugin - In &PRODUCT; release 4.0.0-incubating this plugin supports the Connectivity service. This service is responsible for creating Layer 2 networks supporting the networks created by Guests. In other words when an tenant creates a new network, instead of the traditional VLAN a logical network will be created by sending the appropriate calls to the Nicira NVP Controller. - The plugin has been tested with Nicira NVP versions 2.1.0, 2.2.0 and 2.2.1 - In &PRODUCT; 4.0.0-incubating only the XenServer hypervisor is supported for use in - combination with Nicira NVP. - In &PRODUCT; 4.1.0-incubating both KVM and XenServer hypervisors are - supported. - In &PRODUCT; 4.0.0-incubating the UI components for this plugin are not complete, - configuration is done by sending commands to the API. + The following table lists the CloudStack network services provided by the Nicira NVP Plugin. + + Supported Services + + + + Network Service + CloudStack version + NVP version + + + + + Virtual Networking + >= 4.0 + >= 2.2.1 + + + Source NAT + >= 4.1 + >= 3.0.1 + + + Static NAT + >= 4.1 + >= 3.0.1 + + + Port Forwarding + >= 4.1 + >= 3.0.1 + + + +
+ The Virtual Networking service was originally called 'Connectivity' in CloudStack 4.0 + The following hypervisors are supported by the Nicira NVP Plugin. + + Supported Hypervisors + + + + Hypervisor + CloudStack version + + + + + XenServer + >= 4.0 + + + KVM + >= 4.1 + + + +
+ Please refer to the Nicira NVP configuration guide on how to prepare the hypervisors for Nicira NVP integration.
diff --git a/docs/en-US/plugin-niciranvp-introduction.xml b/docs/en-US/plugin-niciranvp-introduction.xml index 9c1d42df32d..a06f12317e5 100644 --- a/docs/en-US/plugin-niciranvp-introduction.xml +++ b/docs/en-US/plugin-niciranvp-introduction.xml @@ -22,5 +22,8 @@ -->
Introduction to the Nicira NVP Plugin - The Nicira NVP plugin allows CloudStack to use the Nicira solution for virtualized network as a provider for CloudStack networks and services. + The Nicira NVP plugin adds Nicira NVP as one of the available SDN implementations in + CloudStack. With the plugin an exisiting Nicira NVP setup can be used by CloudStack to + implement isolated guest networks and to provide additional services like routing and + NAT.
diff --git a/docs/en-US/plugin-niciranvp-networkofferings.xml b/docs/en-US/plugin-niciranvp-networkofferings.xml new file mode 100644 index 00000000000..b30437e97ba --- /dev/null +++ b/docs/en-US/plugin-niciranvp-networkofferings.xml @@ -0,0 +1,131 @@ + + +%BOOK_ENTITIES; + +%xinclude; +]> + +
+ Network Offerings + Using the Nicira NVP plugin requires a network offering with Virtual Networking enabled and configured to use the NiciraNvp element. Typical use cases combine services from the Virtual Router appliance and the Nicira NVP plugin. + + Isolated network offering with regular services from the Virtual Router. + + + + Service + Provider + + + + + VPN + VirtualRouter + + + DHCP + VirtualRouter + + + DNS + VirtualRouter + + + Firewall + VirtualRouter + + + Load Balancer + VirtualRouter + + + User Data + VirtualRouter + + + Source NAT + VirtualRouter + + + Static NAT + VirtualRouter + + + Post Forwarding + VirtualRouter + + + Virtual Networking + NiciraNVP + + + +
+ + + + + + nvp-physical-network-stt.png: a screenshot of a network offering. + + + The tag in the network offering should be set to the name of the physical network with the NVP provider. + Isolated network with network services. The virtual router is still required to provide network services like dns and dhcp. + + Isolated network offering with network services + + + + Service + Provider + + + + + DHCP + VirtualRouter + + + DNS + VirtualRouter + + + User Data + VirtualRouter + + + Source NAT + NiciraNVP + + + Static NAT + NiciraNVP + + + Post Forwarding + NiciraNVP + + + Virtual Networking + NiciraNVP + + + +
+ +
diff --git a/docs/en-US/plugin-niciranvp-physicalnet.xml b/docs/en-US/plugin-niciranvp-physicalnet.xml new file mode 100644 index 00000000000..d3202905fb1 --- /dev/null +++ b/docs/en-US/plugin-niciranvp-physicalnet.xml @@ -0,0 +1,37 @@ + + +%BOOK_ENTITIES; + +%xinclude; +]> + +
+ Zone Configuration + &PRODUCT; needs to have at least one physical network with the isolation method set to "STT". This network should be enabled for the Guest traffic type. + The Guest traffic type should be configured with the traffic label that matches the name of + the Integration Bridge on the hypervisor. See the Nicira NVP User Guide for more details + on how to set this up in XenServer or KVM. + + + + + + nvp-physical-network-stt.png: a screenshot of a physical network with the STT isolation type + + +
diff --git a/docs/en-US/plugin-niciranvp-preparations.xml b/docs/en-US/plugin-niciranvp-preparations.xml index 762c941fd13..60725591fda 100644 --- a/docs/en-US/plugin-niciranvp-preparations.xml +++ b/docs/en-US/plugin-niciranvp-preparations.xml @@ -22,17 +22,16 @@ -->
Prerequisites - Before enabling the Nicira NVP plugin the NVP Controller needs to be configured. Please review the NVP User Guide on how to do that. - &PRODUCT; needs to have at least one physical network with the isolation method set to "STT". This network should be enabled for the Guest traffic type. - The Guest traffic type should be configured with the traffic label that matches the name of - the Integration Bridge on the hypervisor. See the Nicira NVP User Guide for more details - on how to set this up in XenServer or KVM. + Before enabling the Nicira NVP plugin the NVP Controller needs to be configured. Please review the NVP User Guide on how to do that. Make sure you have the following information ready: The IP address of the NVP Controller The username to access the API The password to access the API The UUID of the Transport Zone that contains the hypervisors in this Zone - The UUID of the Physical Network that will be used for the Guest networks + + The UUID of the Gateway Service used to provide router and NAT services. + + The gateway service uuid is optional and is used for Layer 3 services only (SourceNat, StaticNat and PortForwarding)
diff --git a/docs/en-US/plugin-niciranvp-provider.xml b/docs/en-US/plugin-niciranvp-provider.xml index 80fb2273238..8694478b483 100644 --- a/docs/en-US/plugin-niciranvp-provider.xml +++ b/docs/en-US/plugin-niciranvp-provider.xml @@ -22,21 +22,15 @@ -->
Enabling the service provider - To allow CloudStack to use the Nicira NVP Plugin the network service provider needs to be enabled on the physical network. The following sequence of API calls will enable the network service provider - - addNetworkServiceProvider - - - name = "NiciraNvp" - physicalnetworkid = <the uuid of the physical network> - - - updateNetworkServiceProvider - - - id = <the provider uuid returned by the previous call> - state = "Enabled" - - - + The Nicira NVP provider is disabled by default. Navigate to the "Network Service Providers" configuration of the physical network with the STT isolation type. Navigate to the Nicira NVP provider and press the "Enable Provider" button. + CloudStack 4.0 does not have the UI interface to configure the Nicira NVP plugin. Configuration needs to be done using the API directly. + + + + + + nvp-physical-network-stt.png: a screenshot of an enabled Nicira NVP provider + + +
\ No newline at end of file diff --git a/docs/en-US/plugin-niciranvp-revisions.xml b/docs/en-US/plugin-niciranvp-revisions.xml index b8e6935c5d1..b58d3336aba 100644 --- a/docs/en-US/plugin-niciranvp-revisions.xml +++ b/docs/en-US/plugin-niciranvp-revisions.xml @@ -40,6 +40,20 @@ + + 1-0 + Wed May 22 2013 + + Hugo + Trippaers + hugo@apache.org + + + + Documentation updated for &PRODUCT; 4.1.0 + + + diff --git a/docs/en-US/plugin-niciranvp-tables.xml b/docs/en-US/plugin-niciranvp-tables.xml index 4f816550b30..615f3494c09 100644 --- a/docs/en-US/plugin-niciranvp-tables.xml +++ b/docs/en-US/plugin-niciranvp-tables.xml @@ -23,29 +23,84 @@
Database tables The following tables are added to the cloud database for the Nicira NVP Plugin - - nicira_nvp_nic_map, contains a mapping from nic to logical switch port - - - id - logicalswitch, uuid of the logical switch this port is connected to - logicalswitchport, uuid of the logical switch port for this nic - nic, the CloudStack uuid for this nic, reference to the nics table - - - - - external_nicira_nvp_devices, contains all configured devices - - - id - uuid - physical_network_id, the physical network this device is configured on - provider_name, set to "NiciraNvp" - device_name, display name for this device - host_id, reference to the host table with the device configuration - - - - + + nicira_nvp_nic_map + + + + id + auto incrementing id + + + logicalswitch + uuid of the logical switch this port is connected to + + + logicalswitchport + uuid of the logical switch port for this nic + + + nic + the &PRODUCT; uuid for this nic, reference to the nics table + + + +
+ + + external_nicira_nvp_devices + + + + id + auto incrementing id + + + uuid + UUID identifying this device + + + physical_network_id + the physical network this device is configured on + + + provider_name + NiciraNVP + + + device_name + display name for this device + + + host_id + reference to the host table with the device configuration + + + +
+ + + nicira_nvp_router_map + + + + id + auto incrementing id + + + logicalrouter_uuid + uuid of the logical router + + + network_id + id of the network this router is linked to + + + +
+ + + nicira_nvp_router_map is only available in &PRODUCT; 4.1 and above + +
\ No newline at end of file diff --git a/docs/en-US/plugin-niciranvp-usage.xml b/docs/en-US/plugin-niciranvp-usage.xml index 76f9a0b5b05..9f04c382bd6 100644 --- a/docs/en-US/plugin-niciranvp-usage.xml +++ b/docs/en-US/plugin-niciranvp-usage.xml @@ -21,10 +21,13 @@ under the License. --> - Using the Nicira NVP Plugin + Configuring the Nicira NVP Plugin - - - + + + + diff --git a/docs/en-US/plugin-niciranvp-guide.xml b/docs/en-US/plugin-niciranvp-vpc.xml similarity index 65% rename from docs/en-US/plugin-niciranvp-guide.xml rename to docs/en-US/plugin-niciranvp-vpc.xml index 89c9871021d..a43c5fa85d3 100644 --- a/docs/en-US/plugin-niciranvp-guide.xml +++ b/docs/en-US/plugin-niciranvp-vpc.xml @@ -1,11 +1,10 @@ - + %BOOK_ENTITIES; %xinclude; ]> - - - Plugin Guide for the Nicira NVP Plugin - - - + + Using the Nicira NVP plugin with VPC + + + + + diff --git a/docs/en-US/plugin-niciranvp-vpcfeatures.xml b/docs/en-US/plugin-niciranvp-vpcfeatures.xml new file mode 100644 index 00000000000..a8d8194e9ba --- /dev/null +++ b/docs/en-US/plugin-niciranvp-vpcfeatures.xml @@ -0,0 +1,28 @@ + + +%BOOK_ENTITIES; + +%xinclude; +]> + +
+ Supported VPC features + The Nicira NVP plugin supports &PRODUCT; VPC to a certain extent. Starting with &PRODUCT; version 4.1 VPCs can be deployed using NVP isolated networks. + It is not possible to use a Nicira NVP Logical Router for as a VPC Router + It is not possible to connect a private gateway using a Nicira NVP Logical Switch +
diff --git a/docs/en-US/plugin-niciranvp-vpcnetworkoffering.xml b/docs/en-US/plugin-niciranvp-vpcnetworkoffering.xml new file mode 100644 index 00000000000..141006ee350 --- /dev/null +++ b/docs/en-US/plugin-niciranvp-vpcnetworkoffering.xml @@ -0,0 +1,81 @@ + + +%BOOK_ENTITIES; + +%xinclude; +]> + +
+ VPC Network Offerings + The VPC needs specific network offerings with the VPC flag enabled. Otherwise these network offerings are identical to regular network offerings. To allow VPC networks with a Nicira NVP isolated network the offerings need to support the Virtual Networking service with the NiciraNVP provider. + In a typical configuration two network offerings need to be created. One with the loadbalancing service enabled and one without loadbalancing. + + VPC Network Offering with Loadbalancing + + + + Service + Provider + + + + + VPN + VpcVirtualRouter + + + DHCP + VpcVirtualRouter + + + DNS + VpcVirtualRouter + + + Load Balancer + VpcVirtualRouter + + + User Data + VpcVirtualRouter + + + Source NAT + VpcVirtualRouter + + + Static NAT + VpcVirtualRouter + + + Post Forwarding + VpcVirtualRouter + + + NetworkACL + VpcVirtualRouter + + + Virtual Networking + NiciraNVP + + + +
+ +
diff --git a/docs/en-US/plugin-niciranvp-vpcoffering.xml b/docs/en-US/plugin-niciranvp-vpcoffering.xml new file mode 100644 index 00000000000..292621e516c --- /dev/null +++ b/docs/en-US/plugin-niciranvp-vpcoffering.xml @@ -0,0 +1,38 @@ + + +%BOOK_ENTITIES; + +%xinclude; +]> + +
+ VPC Offering with Nicira NVP + To allow a VPC to use the Nicira NVP plugin to provision networks, a new VPC offering needs to be created which allows the Virtual Networking service to be implemented by NiciraNVP. + This is not currently possible with the UI. The API does provide the proper calls to create a VPC offering with Virtual Networking enabled. However due to a limitation in the 4.1 API it is not possible to select the provider for this network service. To configure the VPC offering with the NiciraNVP provider edit the database table 'vpc_offering_service_map' and change the provider to NiciraNvp for the service 'Connectivity' + It is also possible to update the default VPC offering by adding a row to the + 'vpc_offering_service_map' with service 'Connectivity' and provider 'NiciraNvp' + + + + + + nvp-physical-network-stt.png: a screenshot of the mysql table. + + + When creating a new VPC offering please note that the UI does not allow you to select a VPC offering yet. The VPC needs to be created using the API with the offering UUID. +
From 2336d478beca8c3d17fbf76bbf09f915906bdea8 Mon Sep 17 00:00:00 2001 From: Wido den Hollander Date: Thu, 23 May 2013 23:19:03 +0200 Subject: [PATCH 100/108] debian: When building packages run a Maven clean first --- debian/rules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/rules b/debian/rules index e381b1a8ebe..48485bb9d9b 100755 --- a/debian/rules +++ b/debian/rules @@ -34,7 +34,7 @@ build: build-indep build-indep: build-indep-stamp build-indep-stamp: configure - mvn package -Pawsapi -DskipTests -Dsystemvm \ + mvn clean package -Pawsapi -DskipTests -Dsystemvm \ -Dcs.replace.properties=replace.properties.tmp \ ${ACS_BUILD_OPTS} touch $@ From e8fbee0e18383a4ae07b62c4dafdadf742ec62bb Mon Sep 17 00:00:00 2001 From: Anthony Xu Date: Thu, 23 May 2013 14:18:09 -0700 Subject: [PATCH 101/108] firewall service is not supported in shared SG enabled network offering , remove it --- .../cloud/upgrade/dao/Upgrade410to420.java | 32 +++++++++++++++++++ setup/db/db/schema-410to420.sql | 2 +- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/engine/schema/src/com/cloud/upgrade/dao/Upgrade410to420.java b/engine/schema/src/com/cloud/upgrade/dao/Upgrade410to420.java index c03d377cbe0..95abe5f161e 100644 --- a/engine/schema/src/com/cloud/upgrade/dao/Upgrade410to420.java +++ b/engine/schema/src/com/cloud/upgrade/dao/Upgrade410to420.java @@ -75,6 +75,7 @@ public class Upgrade410to420 implements DbUpgrade { updateNetworkACLs(conn); addHostDetailsIndex(conn); updateNetworksForPrivateGateways(conn); + removeFirewallServiceFromSharedNetworkOfferingWithSGService(conn); } private void updateSystemVmTemplates(Connection conn) { @@ -747,4 +748,35 @@ public class Upgrade410to420 implements DbUpgrade { throw new CloudRuntimeException("Failed to update private networks with VPC id.", e); } } + + private void removeFirewallServiceFromSharedNetworkOfferingWithSGService(Connection conn) { + PreparedStatement pstmt = null; + ResultSet rs = null; + + try { + pstmt = conn.prepareStatement("select id from `cloud`.`network_offerings` where unique_name='DefaultSharedNetworkOfferingWithSGService'"); + rs = pstmt.executeQuery(); + while (rs.next()) { + long id = rs.getLong(1); + // remove Firewall service for SG shared network offering + pstmt = conn.prepareStatement("DELETE `cloud`.`ntwk_offering_service_map` where network_offering_id=? and service='Firewall'"); + pstmt.setLong(1, id); + pstmt.executeUpdate(); + } + } catch (SQLException e) { + throw new CloudRuntimeException("Unable to remove Firewall service for SG shared network offering.", e); + } finally { + try { + if (rs != null) { + rs.close(); + } + + if (pstmt != null) { + pstmt.close(); + } + } catch (SQLException e) { + } + } + } + } diff --git a/setup/db/db/schema-410to420.sql b/setup/db/db/schema-410to420.sql index ea7a18df0ff..b5480359edd 100644 --- a/setup/db/db/schema-410to420.sql +++ b/setup/db/db/schema-410to420.sql @@ -1710,5 +1710,5 @@ INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'VpcMa -- Re-enable foreign key checking, at the end of the upgrade path SET foreign_key_checks = 1; - +UPDATE `cloud`.`snapshot_policy` set uuid=id WHERE uuid is NULL; From 026c2cec6d3cb42c32929c9ed3ee417b7771b667 Mon Sep 17 00:00:00 2001 From: Jessica Wang Date: Thu, 23 May 2013 14:34:26 -0700 Subject: [PATCH 102/108] CLOUDSTACK-2504: UI - create network offering dialog - pass specifyIpRanges=false for Isolated Network. --- ui/scripts/configuration.js | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/ui/scripts/configuration.js b/ui/scripts/configuration.js index 808b34289d7..9932cdc3aa6 100644 --- a/ui/scripts/configuration.js +++ b/ui/scripts/configuration.js @@ -1919,26 +1919,22 @@ inputData['isPersistent'] = false; } else if (inputData['guestIpType'] == "Isolated") { //specifyVlan checkbox is shown - if (inputData['specifyVlan'] == 'on') { //specifyVlan checkbox is checked + inputData['specifyIpRanges'] = false; + + if (inputData['specifyVlan'] == 'on') { //specifyVlan checkbox is checked inputData['specifyVlan'] = true; - inputData['specifyIpRanges'] = true; - - - - } else { //specifyVlan checkbox is unchecked inputData['specifyVlan'] = false; - inputData['specifyIpRanges'] = false; + } - if(inputData['isPersistent'] == 'on') { //It is a persistent network - inputData['isPersistent'] = true; - } - else { //Isolated Network with Non-persistent network - inputData['isPersistent'] = false; - } - + if(inputData['isPersistent'] == 'on') { //It is a persistent network + inputData['isPersistent'] = true; + } + else { //Isolated Network with Non-persistent network + inputData['isPersistent'] = false; + } } From b1f4c8e89af1d79f2d5cba03910e93b40e3c0859 Mon Sep 17 00:00:00 2001 From: Wido den Hollander Date: Thu, 23 May 2013 23:46:55 +0200 Subject: [PATCH 103/108] Fix a small typo in a log line --- server/src/com/cloud/vm/VirtualMachineManagerImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/com/cloud/vm/VirtualMachineManagerImpl.java b/server/src/com/cloud/vm/VirtualMachineManagerImpl.java index f4875377f93..c65514b1b31 100755 --- a/server/src/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/server/src/com/cloud/vm/VirtualMachineManagerImpl.java @@ -358,7 +358,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac } if (s_logger.isDebugEnabled()) { - s_logger.debug("Allocaing disks for " + vm); + s_logger.debug("Allocating disks for " + vm); } if (template.getFormat() == ImageFormat.ISO) { From a46b4d8273e9033e55172bf2d00845820bc9511a Mon Sep 17 00:00:00 2001 From: Min Chen Date: Thu, 23 May 2013 15:14:43 -0700 Subject: [PATCH 104/108] CLOUDSTACK-2656: UUID column for all 4.0 schema entities should be populated when they are upgraded to 4.1 --- setup/db/db/schema-40to410.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup/db/db/schema-40to410.sql b/setup/db/db/schema-40to410.sql index 381a4cea612..acc29a2eb7f 100644 --- a/setup/db/db/schema-40to410.sql +++ b/setup/db/db/schema-40to410.sql @@ -184,6 +184,7 @@ UPDATE `cloud`.`alert` set uuid=id WHERE uuid is NULL; UPDATE `cloud`.`async_job` set uuid=id WHERE uuid is NULL; UPDATE `cloud`.`cluster` set uuid=id WHERE uuid is NULL; UPDATE `cloud`.`data_center` set uuid=id WHERE uuid is NULL; +UPDATE `cloud`.`dc_storage_network_ip_range` set uuid=id WHERE uuid is NULL; UPDATE `cloud`.`disk_offering` set uuid=id WHERE uuid is NULL; UPDATE `cloud`.`domain` set uuid=id WHERE uuid is NULL; UPDATE `cloud`.`event` set uuid=id WHERE uuid is NULL; @@ -217,6 +218,7 @@ UPDATE `cloud`.`security_group` set uuid=id WHERE uuid is NULL; UPDATE `cloud`.`security_group_rule` set uuid=id WHERE uuid is NULL; UPDATE `cloud`.`snapshot_schedule` set uuid=id WHERE uuid is NULL; UPDATE `cloud`.`snapshots` set uuid=id WHERE uuid is NULL; +UPDATE `cloud`.`snapshot_policy` set uuid=id WHERE uuid is NULL; UPDATE `cloud`.`static_routes` set uuid=id WHERE uuid is NULL; UPDATE `cloud`.`storage_pool` set uuid=id WHERE uuid is NULL; UPDATE `cloud`.`swift` set uuid=id WHERE uuid is NULL; From cf8ff0cc654e5da4cf7510b964b9ef6cd828c90b Mon Sep 17 00:00:00 2001 From: Min Chen Date: Thu, 23 May 2013 15:43:14 -0700 Subject: [PATCH 105/108] Populate UUID column for new data introduced in 4.2.0. --- setup/db/db/schema-410to420.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup/db/db/schema-410to420.sql b/setup/db/db/schema-410to420.sql index b5480359edd..885eff46132 100644 --- a/setup/db/db/schema-410to420.sql +++ b/setup/db/db/schema-410to420.sql @@ -353,8 +353,8 @@ ALTER TABLE `cloud`.`physical_network_traffic_types` ADD COLUMN `lxc_network_lab UPDATE configuration SET value='KVM,XenServer,VMware,BareMetal,Ovm,LXC' WHERE name='hypervisor.list'; -INSERT INTO `cloud`.`vm_template` (id, unique_name, name, public, created, type, hvm, bits, account_id, url, checksum, enable_password, display_text, format, guest_os_id, featured, cross_zones, hypervisor_type) - VALUES (10, 'routing-10', 'SystemVM Template (LXC)', 0, now(), 'SYSTEM', 0, 64, 1, 'http://download.cloud.com/templates/acton/acton-systemvm-02062012.qcow2.bz2', '2755de1f9ef2ce4d6f2bee2efbb4da92', 0, 'SystemVM Template (LXC)', 'QCOW2', 15, 0, 1, 'LXC'); +INSERT INTO `cloud`.`vm_template` (id, uuid, unique_name, name, public, created, type, hvm, bits, account_id, url, checksum, enable_password, display_text, format, guest_os_id, featured, cross_zones, hypervisor_type) + VALUES (10, UUID(), 'routing-10', 'SystemVM Template (LXC)', 0, now(), 'SYSTEM', 0, 64, 1, 'http://download.cloud.com/templates/acton/acton-systemvm-02062012.qcow2.bz2', '2755de1f9ef2ce4d6f2bee2efbb4da92', 0, 'SystemVM Template (LXC)', 'QCOW2', 15, 0, 1, 'LXC'); ALTER TABLE `cloud`.`user_vm` MODIFY user_data TEXT(32768); From f92db64c78778e58821073971152167472ef340d Mon Sep 17 00:00:00 2001 From: Brian Federle Date: Thu, 23 May 2013 15:59:03 -0700 Subject: [PATCH 106/108] Install wizard UI: Add preinstall hook For fresh install, adds hook to install wizard UI to show a custom 'step' before the initial welcome screen. Pre-install specified by providing 'cloudStack.preInstall,' which returns a jQuery object to be displayed. It passes args.complete, which when called will proceed to the first step of the install wizard. --- ui/scripts/ui-custom/installWizard.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/ui/scripts/ui-custom/installWizard.js b/ui/scripts/ui-custom/installWizard.js index 86919f9fa41..9f1f4c761e5 100644 --- a/ui/scripts/ui-custom/installWizard.js +++ b/ui/scripts/ui-custom/installWizard.js @@ -298,6 +298,17 @@ * Layout/behavior for each step in wizard */ var steps = { + start: function(args) { + if (cloudStack.preInstall) { + return cloudStack.preInstall({ + complete: function() { + goTo('intro'); + } + }); + } + + return steps.intro(args); + }, intro: function(args) { var $intro = $('
').addClass('intro what-is-cloudstack'); var $title = $('
').addClass('title').html(_l('label.what.is.cloudstack')); @@ -775,7 +786,7 @@ } }; - var initialStep = steps.intro().addClass('step'); + var initialStep = steps.start().addClass('step'); showDiagram(''); From 7e6f3f94ba467f8f58ed6b130055bdd6df73815a Mon Sep 17 00:00:00 2001 From: Alena Prokharchyk Date: Thu, 23 May 2013 10:24:44 -0700 Subject: [PATCH 107/108] CLOUDSTACK-2628: added networkId parameter to the list of request parameters in listLoadBalancerRules API --- .../ListLoadBalancerRulesCmd.java | 9 +++++++++ .../api/response/LoadBalancerResponse.java | 11 ++++++++++- .../src/com/cloud/api/ApiResponseHelper.java | 19 +++++++++++-------- .../lb/LoadBalancingRulesManagerImpl.java | 6 ++++++ 4 files changed, 36 insertions(+), 9 deletions(-) diff --git a/api/src/org/apache/cloudstack/api/command/user/loadbalancer/ListLoadBalancerRulesCmd.java b/api/src/org/apache/cloudstack/api/command/user/loadbalancer/ListLoadBalancerRulesCmd.java index e022cc78db0..21d5b2a440e 100644 --- a/api/src/org/apache/cloudstack/api/command/user/loadbalancer/ListLoadBalancerRulesCmd.java +++ b/api/src/org/apache/cloudstack/api/command/user/loadbalancer/ListLoadBalancerRulesCmd.java @@ -27,6 +27,7 @@ import org.apache.cloudstack.api.response.FirewallRuleResponse; import org.apache.cloudstack.api.response.IPAddressResponse; import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.api.response.LoadBalancerResponse; +import org.apache.cloudstack.api.response.NetworkResponse; import org.apache.cloudstack.api.response.UserVmResponse; import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.log4j.Logger; @@ -62,6 +63,10 @@ public class ListLoadBalancerRulesCmd extends BaseListTaggedResourcesCmd { @Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class, description = "the availability zone ID") private Long zoneId; + + @Parameter(name = ApiConstants.NETWORK_ID, type = CommandType.UUID, entityType = NetworkResponse.class, + description = "list by network id the rule belongs to") + private Long networkId; // /////////////////////////////////////////////////// // ///////////////// Accessors /////////////////////// @@ -86,6 +91,10 @@ public class ListLoadBalancerRulesCmd extends BaseListTaggedResourcesCmd { public Long getZoneId() { return zoneId; } + + public Long getNetworkId() { + return networkId; + } // /////////////////////////////////////////////////// // ///////////// API Implementation/////////////////// diff --git a/api/src/org/apache/cloudstack/api/response/LoadBalancerResponse.java b/api/src/org/apache/cloudstack/api/response/LoadBalancerResponse.java index 79b01b17ff2..0f180970fc7 100644 --- a/api/src/org/apache/cloudstack/api/response/LoadBalancerResponse.java +++ b/api/src/org/apache/cloudstack/api/response/LoadBalancerResponse.java @@ -57,10 +57,14 @@ public class LoadBalancerResponse extends BaseResponse implements ControlledEnti @SerializedName(ApiConstants.ALGORITHM) @Param(description = "the load balancer algorithm (source, roundrobin, leastconn)") private String algorithm; + + @SerializedName(ApiConstants.NETWORK_ID) + @Param(description = "the id of the guest network the lb rule belongs to") + private String networkId; @SerializedName(ApiConstants.CIDR_LIST) @Param(description="the cidr list to forward traffic from") private String cidrList; - + @SerializedName(ApiConstants.ACCOUNT) @Param(description = "the account of the load balancer rule") private String accountName; @@ -89,6 +93,7 @@ public class LoadBalancerResponse extends BaseResponse implements ControlledEnti @SerializedName(ApiConstants.TAGS) @Param(description="the list of resource tags associated with load balancer", responseObject = ResourceTagResponse.class) private List tags; + public void setId(String id) { this.id = id; @@ -160,5 +165,9 @@ public class LoadBalancerResponse extends BaseResponse implements ControlledEnti public void setTags(List tags) { this.tags = tags; } + + public void setNetworkId(String networkId) { + this.networkId = networkId; + } } diff --git a/server/src/com/cloud/api/ApiResponseHelper.java b/server/src/com/cloud/api/ApiResponseHelper.java index fc1c6a0be63..540c8e9a4d7 100755 --- a/server/src/com/cloud/api/ApiResponseHelper.java +++ b/server/src/com/cloud/api/ApiResponseHelper.java @@ -34,12 +34,6 @@ import java.util.TimeZone; import javax.inject.Inject; -import com.cloud.network.vpc.NetworkACL; -import com.cloud.network.vpc.NetworkACLItem; -import com.cloud.network.vpc.PrivateGateway; -import com.cloud.network.vpc.StaticRoute; -import com.cloud.network.vpc.Vpc; -import com.cloud.network.vpc.VpcOffering; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.affinity.AffinityGroup; @@ -49,8 +43,6 @@ import org.apache.cloudstack.api.ApiConstants.VMDetails; import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.ResponseGenerator; import org.apache.cloudstack.api.command.user.job.QueryAsyncJobResultCmd; -import org.apache.cloudstack.region.PortableIp; -import org.apache.cloudstack.region.PortableIpRange; import org.apache.cloudstack.api.response.AccountResponse; import org.apache.cloudstack.api.response.ApplicationLoadBalancerInstanceResponse; import org.apache.cloudstack.api.response.ApplicationLoadBalancerResponse; @@ -146,6 +138,8 @@ import org.apache.cloudstack.api.response.VpcResponse; import org.apache.cloudstack.api.response.VpnUsersResponse; import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.cloudstack.network.lb.ApplicationLoadBalancerRule; +import org.apache.cloudstack.region.PortableIp; +import org.apache.cloudstack.region.PortableIpRange; import org.apache.cloudstack.region.Region; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.cloudstack.usage.Usage; @@ -241,6 +235,12 @@ import com.cloud.network.security.SecurityGroup; import com.cloud.network.security.SecurityGroupVO; import com.cloud.network.security.SecurityRule; import com.cloud.network.security.SecurityRule.SecurityRuleType; +import com.cloud.network.vpc.NetworkACL; +import com.cloud.network.vpc.NetworkACLItem; +import com.cloud.network.vpc.PrivateGateway; +import com.cloud.network.vpc.StaticRoute; +import com.cloud.network.vpc.Vpc; +import com.cloud.network.vpc.VpcOffering; import com.cloud.offering.DiskOffering; import com.cloud.offering.NetworkOffering; import com.cloud.offering.NetworkOffering.Detail; @@ -776,6 +776,9 @@ public class ApiResponseHelper implements ResponseGenerator { tagResponses.add(tagResponse); } lbResponse.setTags(tagResponses); + + Network ntwk = ApiDBUtils.findNetworkById(loadBalancer.getNetworkId()); + lbResponse.setNetworkId(ntwk.getUuid()); lbResponse.setObjectName("loadbalancer"); return lbResponse; diff --git a/server/src/com/cloud/network/lb/LoadBalancingRulesManagerImpl.java b/server/src/com/cloud/network/lb/LoadBalancingRulesManagerImpl.java index 520dd763667..0118ca534e1 100755 --- a/server/src/com/cloud/network/lb/LoadBalancingRulesManagerImpl.java +++ b/server/src/com/cloud/network/lb/LoadBalancingRulesManagerImpl.java @@ -1902,6 +1902,7 @@ public class LoadBalancingRulesManagerImpl extends ManagerBase implements String name = cmd.getLoadBalancerRuleName(); String keyword = cmd.getKeyword(); Long instanceId = cmd.getVirtualMachineId(); + Long networkId = cmd.getNetworkId(); Map tags = cmd.getTags(); Account caller = UserContext.current().getCaller(); @@ -1922,6 +1923,7 @@ public class LoadBalancingRulesManagerImpl extends ManagerBase implements sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ); sb.and("name", sb.entity().getName(), SearchCriteria.Op.LIKE); sb.and("sourceIpAddress", sb.entity().getSourceIpAddressId(), SearchCriteria.Op.EQ); + sb.and("networkId", sb.entity().getNetworkId(), SearchCriteria.Op.EQ); if (instanceId != null) { SearchBuilder lbVMSearch = _lb2VmMapDao.createSearchBuilder(); @@ -1979,6 +1981,10 @@ public class LoadBalancingRulesManagerImpl extends ManagerBase implements if (zoneId != null) { sc.setJoinParameters("ipSearch", "zoneId", zoneId); } + + if (networkId != null) { + sc.setParameters("networkId", networkId); + } if (tags != null && !tags.isEmpty()) { int count = 0; From 780b45e245aba1ca99d12fd4a8de88999642b4ae Mon Sep 17 00:00:00 2001 From: Prachi Damle Date: Thu, 23 May 2013 16:32:34 -0700 Subject: [PATCH 108/108] CLOUDSTACK-2657: listTemplatePermissions API should also include the owner of the template to the list of users having permission to launch Changes: - Add template owner to the list of users --- server/src/com/cloud/template/TemplateManagerImpl.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/server/src/com/cloud/template/TemplateManagerImpl.java b/server/src/com/cloud/template/TemplateManagerImpl.java index d4976cdc357..517d4ba80d7 100755 --- a/server/src/com/cloud/template/TemplateManagerImpl.java +++ b/server/src/com/cloud/template/TemplateManagerImpl.java @@ -1553,6 +1553,13 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager, accountNames.add(acct.getAccountName()); } } + + // also add the owner if not public + if (!template.isPublicTemplate()) { + Account templateOwner = _accountDao.findById(template.getAccountId()); + accountNames.add(templateOwner.getAccountName()); + } + return accountNames; }