CLOUDSTACK-10102: New network type L2 (#2281)

This feature allows CloudStack administrators to create layer 2 networks on CloudStack. As these networks are purely layer 2, they don't require IP addresses or Virtual Router, only VLAN is necessary (provided by administrator or assigned by CloudStack). Also, network services should be handled externally, e.g. DNS, DHCP, as they are not provided by L2 networks.
As a consequence, a new Guest Network type is created within CloudStack: L2

Description:
Network offerings and networks support new guest type: L2.
L2 Network offering creation allows administrator to select Specify VLAN or let CloudStack assign it dynamically.
L2 Network creation allows administrator to specify VLAN tag (if network offerings allows it) or simply create network.
VM deployments on L2 networks:
VMs should not IP addresses or any network service
No Virtual Router deployed on network
If Specify VLAN = true for network offering, network gets implemented using a dynamically assigned VLAN
UI changes

A new button is added on Networks tab, available for admins, to allow L2 networks creation
This commit is contained in:
Nicolas Vazquez 2017-12-20 08:37:39 -03:00 committed by Rohit Yadav
parent 8acb0908c4
commit 13c325aad4
33 changed files with 625 additions and 34 deletions

View File

@ -38,7 +38,7 @@ import com.cloud.utils.fsm.StateObject;
public interface Network extends ControlledEntity, StateObject<Network.State>, InternalIdentity, Identity, Serializable, Displayable {
public enum GuestType {
Shared, Isolated
Shared, Isolated, L2
}
public String updatingInSequence ="updatingInSequence";

View File

@ -2207,8 +2207,8 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
+ zone.getName());
}
if (! UuidUtils.validateUUID(vlanId)){
// For Isolated networks, don't allow to create network with vlan that already exists in the zone
if (ntwkOff.getGuestType() == GuestType.Isolated) {
// For Isolated and L2 networks, don't allow to create network with vlan that already exists in the zone
if (ntwkOff.getGuestType() == GuestType.Isolated || ntwkOff.getGuestType() == GuestType.L2) {
if (_networksDao.listByZoneAndUriAndGuestType(zoneId, uri.toString(), null).size() > 0) {
throw new InvalidParameterValueException("Network with vlan " + vlanId + " already exists or overlaps with other network vlans in zone " + zoneId);
} else {
@ -2289,8 +2289,9 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
// with different Cidrs for the same Shared network
final boolean cidrRequired = zone.getNetworkType() == NetworkType.Advanced
&& ntwkOff.getTrafficType() == TrafficType.Guest
&& (ntwkOff.getGuestType() == GuestType.Shared || ntwkOff.getGuestType() == GuestType.Isolated && !_networkModel.areServicesSupportedByNetworkOffering(
ntwkOff.getId(), Service.SourceNat));
&& (ntwkOff.getGuestType() == GuestType.Shared || (ntwkOff.getGuestType() == GuestType.Isolated
&& !_networkModel.areServicesSupportedByNetworkOffering(ntwkOff.getId(), Service.SourceNat))
|| ntwkOff.getGuestType() == GuestType.L2 && !_networkModel.listNetworkOfferingServices(ntwkOff.getId()).isEmpty());
if (cidr == null && ip6Cidr == null && cidrRequired) {
throw new InvalidParameterValueException("StartIp/endIp/gateway/netmask are required when create network of" + " type " + Network.GuestType.Shared
+ " and network of type " + GuestType.Isolated + " with service " + Service.SourceNat.getName() + " disabled");
@ -2560,7 +2561,6 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
s_logger.debug("Unable to find network with id: " + networkId);
return false;
}
// Make sure that there are no user vms in the network that are not Expunged/Error
final List<UserVmVO> userVms = _userVmDao.listByNetworkIdAndStates(networkId);

View File

@ -29,6 +29,9 @@ import java.util.Set;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import com.cloud.network.Network;
import com.cloud.network.dao.NetworkDao;
import com.cloud.network.dao.NetworkVO;
import org.apache.log4j.Logger;
import com.cloud.server.ResourceTag.ResourceObjectType;
@ -77,6 +80,8 @@ public class UserVmDaoImpl extends GenericDaoBase<UserVmVO, Long> implements Use
// ResourceTagsDaoImpl _tagsDao = ComponentLocator.inject(ResourceTagsDaoImpl.class);
@Inject
ResourceTagDao _tagsDao;
@Inject
NetworkDao networkDao;
private static final String LIST_PODS_HAVING_VMS_FOR_ACCOUNT =
"SELECT pod_id FROM cloud.vm_instance WHERE data_center_id = ? AND account_id = ? AND pod_id IS NOT NULL AND (state = 'Running' OR state = 'Stopped') "
@ -299,21 +304,32 @@ public class UserVmDaoImpl extends GenericDaoBase<UserVmVO, Long> implements Use
return listBy(sc);
}
@Override
public List<UserVmVO> listByNetworkIdAndStates(long networkId, State... states) {
if (UserVmSearch == null) {
/**
* Recreates UserVmSearch depending on network type, as nics on L2 networks have no ip addresses
* @param network network
*/
private void recreateUserVmSeach(NetworkVO network) {
if (network != null) {
SearchBuilder<NicVO> nicSearch = _nicDao.createSearchBuilder();
nicSearch.and("networkId", nicSearch.entity().getNetworkId(), SearchCriteria.Op.EQ);
nicSearch.and("removed", nicSearch.entity().getRemoved(), SearchCriteria.Op.NULL);
nicSearch.and().op("ip4Address", nicSearch.entity().getIPv4Address(), SearchCriteria.Op.NNULL);
nicSearch.or("ip6Address", nicSearch.entity().getIPv6Address(), SearchCriteria.Op.NNULL);
nicSearch.cp();
if (!Network.GuestType.L2.equals(network.getGuestType())) {
nicSearch.and().op("ip4Address", nicSearch.entity().getIPv4Address(), SearchCriteria.Op.NNULL);
nicSearch.or("ip6Address", nicSearch.entity().getIPv6Address(), SearchCriteria.Op.NNULL);
nicSearch.cp();
}
UserVmSearch = createSearchBuilder();
UserVmSearch.and("states", UserVmSearch.entity().getState(), SearchCriteria.Op.IN);
UserVmSearch.join("nicSearch", nicSearch, UserVmSearch.entity().getId(), nicSearch.entity().getInstanceId(), JoinBuilder.JoinType.INNER);
UserVmSearch.done();
}
}
@Override
public List<UserVmVO> listByNetworkIdAndStates(long networkId, State... states) {
NetworkVO network = networkDao.findById(networkId);
recreateUserVmSeach(network);
SearchCriteria<UserVmVO> sc = UserVmSearch.create();
if (states != null && states.length != 0) {

View File

@ -107,6 +107,7 @@ public class BrocadeVcsGuestNetworkGuruTest {
guru._ntwkOfferingSrvcDao = nosd;
guru._dcDao = dcdao;
guru._agentMgr = agentmgr;
((GuestNetworkGuru)guru)._networkModel = netmodel;
final DataCenterVO dc = mock(DataCenterVO.class);
when(dc.getNetworkType()).thenReturn(NetworkType.Advanced);
@ -163,6 +164,8 @@ public class BrocadeVcsGuestNetworkGuruTest {
when(nosd.areServicesSupportedByNetworkOffering(NETWORK_ID, Service.Connectivity)).thenReturn(true);
when(netmodel.listNetworkOfferingServices(NETWORK_ID)).thenReturn(Arrays.asList(Service.Connectivity));
final DeploymentPlan plan = mock(DeploymentPlan.class);
final Network network = mock(Network.class);
final Account account = mock(Account.class);

View File

@ -94,7 +94,7 @@ public class NiciraNvpGuestNetworkGuruTest {
guru.niciraNvpDao = nvpdao;
guru._dcDao = dcdao;
guru.ntwkOfferingSrvcDao = nosd;
guru.networkModel = netmodel;
((GuestNetworkGuru)guru)._networkModel = netmodel;
guru.hostDao = hostdao;
guru.agentMgr = agentmgr;
guru.networkDao = netdao;
@ -162,6 +162,8 @@ public class NiciraNvpGuestNetworkGuruTest {
when(nosd.areServicesSupportedByNetworkOffering(NETWORK_ID, Service.Connectivity)).thenReturn(true);
when(netmodel.listNetworkOfferingServices(NETWORK_ID)).thenReturn(Arrays.asList(Service.Connectivity));
final DeploymentPlan plan = mock(DeploymentPlan.class);
final Network network = mock(Network.class);
final Account account = mock(Account.class);

View File

@ -2013,7 +2013,9 @@ public class ApiResponseHelper implements ResponseGenerator {
// FIXME - either set netmask or cidr
response.setCidr(network.getCidr());
response.setNetworkCidr((network.getNetworkCidr()));
if (network.getNetworkCidr() != null) {
response.setNetworkCidr((network.getNetworkCidr()));
}
// If network has reservation its entire network cidr is defined by
// getNetworkCidr()
// if no reservation is present then getCidr() will define the entire

View File

@ -799,7 +799,6 @@ public class IpAddressManagerImpl extends ManagerBase implements IpAddressManage
throw new AccountLimitException("Maximum number of public IP addresses for account: " + owner.getAccountName() + " has been exceeded.");
}
}
IPAddressVO addr = addrs.get(0);
addr.setSourceNat(sourceNat);
addr.setAllocatedTime(new Date());
@ -1317,12 +1316,20 @@ public class IpAddressManagerImpl extends ManagerBase implements IpAddressManage
if (zone.getNetworkType() == NetworkType.Advanced) {
// In Advance zone allow to do IP assoc only for Isolated networks with source nat service enabled
if (network.getGuestType() == GuestType.Isolated && !(_networkModel.areServicesSupportedInNetwork(network.getId(), Service.SourceNat))) {
if (releaseOnFailure && ipToAssoc != null) {
s_logger.warn("Failed to associate ip address, so unassigning ip from the database " + ipToAssoc);
_ipAddressDao.unassignIpAddress(ipToAssoc.getId());
}
throw new InvalidParameterValueException("In zone of type " + NetworkType.Advanced + " ip address can be associated only to the network of guest type "
+ GuestType.Isolated + " with the " + Service.SourceNat.getName() + " enabled");
}
// In Advance zone allow to do IP assoc only for shared networks with source nat/static nat/lb/pf services enabled
if (network.getGuestType() == GuestType.Shared && !isSharedNetworkOfferingWithServices(network.getNetworkOfferingId())) {
if (releaseOnFailure && ipToAssoc != null) {
s_logger.warn("Failed to associate ip address, so unassigning ip from the database " + ipToAssoc);
_ipAddressDao.unassignIpAddress(ipToAssoc.getId());
}
throw new InvalidParameterValueException("In zone of type " + NetworkType.Advanced + " ip address can be associated with network of guest type " + GuestType.Shared
+ "only if at " + "least one of the services " + Service.SourceNat.getName() + "/" + Service.StaticNat.getName() + "/" + Service.Lb.getName() + "/"
+ Service.PortForwarding.getName() + " is enabled");
@ -1766,6 +1773,10 @@ public class IpAddressManagerImpl extends ManagerBase implements IpAddressManage
return null;
}
if (_networkModel.listNetworkOfferingServices(network.getNetworkOfferingId()).isEmpty() && network.getCidr() == null) {
return null;
}
Set<Long> availableIps = _networkModel.getAvailableIps(network, requestedIp);
if (availableIps == null || availableIps.isEmpty()) {

View File

@ -29,6 +29,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.Collections;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
@ -583,6 +584,9 @@ public class NetworkModelImpl extends ManagerBase implements NetworkModel, Confi
if (network.getTrafficType() != TrafficType.Guest) {
return false;
}
if (listNetworkOfferingServices(network.getNetworkOfferingId()).isEmpty()) {
return true; // do not check free IPs if there is no service in the network
}
boolean hasFreeIps = true;
if (network.getGuestType() == GuestType.Shared) {
if (network.getGateway() != null) {
@ -1823,6 +1827,9 @@ public class NetworkModelImpl extends ManagerBase implements NetworkModel, Confi
@Override
public Set<Long> getAvailableIps(Network network, String requestedIp) {
if (network.getCidr() == null) {
return Collections.emptySet();
}
String[] cidr = network.getCidr().split("/");
List<String> ips = getUsedIpsInNetwork(network);
Set<Long> usedIps = new TreeSet<Long>();

View File

@ -1124,7 +1124,7 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService {
}
}
} else {
if (ntwkOff.getGuestType() == GuestType.Isolated) {
if (ntwkOff.getGuestType() == GuestType.Isolated || ntwkOff.getGuestType() == GuestType.L2) {
aclType = ACLType.Account;
} else if (ntwkOff.getGuestType() == GuestType.Shared) {
aclType = ACLType.Domain;
@ -1861,9 +1861,9 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService {
Account owner = _accountMgr.getAccount(network.getAccountId());
// Only Admin can delete Shared networks
if (network.getGuestType() == GuestType.Shared && !_accountMgr.isAdmin(caller.getId())) {
throw new InvalidParameterValueException("Only Admins can delete network with guest type " + GuestType.Shared);
// Only Admin can delete Shared and L2 networks
if ((network.getGuestType() == GuestType.Shared || network.getGuestType() == GuestType.L2) && !_accountMgr.isAdmin(caller.getId())) {
throw new InvalidParameterValueException("Only Admins can delete network with guest type " + network.getGuestType());
}
// Perform permission check

View File

@ -91,8 +91,9 @@ public class ExternalGuestNetworkGuru extends GuestNetworkGuru {
protected boolean canHandle(NetworkOffering offering, final NetworkType networkType, final PhysicalNetwork physicalNetwork) {
// This guru handles only Guest Isolated network that supports Source
// nat service
if (networkType == NetworkType.Advanced && isMyTrafficType(offering.getTrafficType()) && offering.getGuestType() == Network.GuestType.Isolated &&
isMyIsolationMethod(physicalNetwork) && !offering.isSystemOnly()) {
if (networkType == NetworkType.Advanced && isMyTrafficType(offering.getTrafficType())
&& (offering.getGuestType() == Network.GuestType.Isolated || offering.getGuestType() == GuestType.L2)
&& isMyIsolationMethod(physicalNetwork) && !offering.isSystemOnly()) {
return true;
} else {
s_logger.trace("We only take care of Guest networks of type " + GuestType.Isolated + " in zone of type " + NetworkType.Advanced);
@ -295,12 +296,14 @@ public class ExternalGuestNetworkGuru extends GuestNetworkGuru {
nic.setIPv4Gateway(config.getGateway());
if (nic.getIPv4Address() == null) {
String guestIp = _ipAddrMgr.acquireGuestIpAddress(config, null);
if (guestIp == null) {
throw new InsufficientVirtualNetworkCapacityException("Unable to acquire guest IP address for network " + config, DataCenter.class, dc.getId());
}
if (!_networkModel.listNetworkOfferingServices(config.getNetworkOfferingId()).isEmpty()) {
String guestIp = _ipAddrMgr.acquireGuestIpAddress(config, null);
if (guestIp == null) {
throw new InsufficientVirtualNetworkCapacityException("Unable to acquire guest IP address for network " + config, DataCenter.class, dc.getId());
}
nic.setIPv4Address(guestIp);
nic.setIPv4Address(guestIp);
}
} else {
long ipMask = NetUtils.ip2Long(nic.getIPv4Address()) & ~(0xffffffffffffffffl << (32 - cidrSize));
nic.setIPv4Address(NetUtils.long2Ip(cidrAddress | ipMask));

View File

@ -22,6 +22,7 @@ import java.util.Random;
import javax.inject.Inject;
import com.cloud.network.Network.GuestType;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
import org.apache.cloudstack.framework.config.ConfigKey;
@ -199,7 +200,7 @@ public abstract class GuestNetworkGuru extends AdapterBase implements NetworkGur
if (userSpecified.getCidr() != null) {
network.setCidr(userSpecified.getCidr());
network.setGateway(userSpecified.getGateway());
} else {
} else if (offering.getGuestType() == GuestType.Shared || !_networkModel.listNetworkOfferingServices(offering.getId()).isEmpty()) {
final String guestNetworkCidr = dc.getGuestNetworkCidr();
if (guestNetworkCidr != null) {
final String[] cidrTuple = guestNetworkCidr.split("\\/");
@ -369,14 +370,16 @@ public abstract class GuestNetworkGuru extends AdapterBase implements NetworkGur
guestIp = network.getGateway();
} else {
guestIp = _ipAddrMgr.acquireGuestIpAddress(network, nic.getRequestedIPv4());
if (guestIp == null) {
if (guestIp == null && !_networkModel.listNetworkOfferingServices(network.getNetworkOfferingId()).isEmpty()) {
throw new InsufficientVirtualNetworkCapacityException("Unable to acquire Guest IP" + " address for network " + network, DataCenter.class,
dc.getId());
}
}
nic.setIPv4Address(guestIp);
nic.setIPv4Netmask(NetUtils.cidr2Netmask(_networkModel.getValidNetworkCidr(network)));
if (network.getCidr() != null) {
nic.setIPv4Netmask(NetUtils.cidr2Netmask(_networkModel.getValidNetworkCidr(network)));
}
nic.setIPv4Dns1(dc.getDns1());
nic.setIPv4Dns2(dc.getDns2());

View File

@ -1271,3 +1271,225 @@ class TestRouterRules(cloudstackTestCase):
delay=0
)
return
class TestL2Networks(cloudstackTestCase):
def setUp(self):
self.apiclient = self.testClient.getApiClient()
self.services["network"]["networkoffering"] = self.network_offering.id
self.l2_network = Network.create(
self.apiclient,
self.services["l2-network"],
zoneid=self.zone.id,
networkofferingid=self.network_offering.id
)
self.cleanup = [
self.l2_network]
def tearDown(self):
cleanup_resources(self.apiclient, self.cleanup)
return
@classmethod
def setUpClass(cls):
testClient = super(TestL2Networks, cls).getClsTestClient()
cls.apiclient = testClient.getApiClient()
cls.services = testClient.getParsedTestDataConfig()
# Get Zone, Domain and templates
cls.domain = get_domain(cls.apiclient)
cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests())
cls.services['mode'] = cls.zone.networktype
# Create Accounts & networks
cls.account = Account.create(
cls.apiclient,
cls.services["account"],
admin=True,
domainid=cls.domain.id
)
cls.template = get_template(
cls.apiclient,
cls.zone.id,
cls.services["ostype"]
)
cls.service_offering = ServiceOffering.create(
cls.apiclient,
cls.services["service_offerings"]["tiny"]
)
cls.services["network"]["zoneid"] = cls.zone.id
cls.network_offering = NetworkOffering.create(
cls.apiclient,
cls.services["l2-network_offering"],
)
# Enable Network offering
cls.network_offering.update(cls.apiclient, state='Enabled')
cls._cleanup = [
cls.account,
cls.network_offering,
cls.service_offering
]
return
@classmethod
def tearDownClass(cls):
try:
# Cleanup resources used
cleanup_resources(cls.apiclient, cls._cleanup)
except Exception as e:
raise Exception("Warning: Exception during cleanup : %s" % e)
return
@attr(tags=["advanced", "advancedns", "smoke"], required_hardware="false")
def test_deploy_vm_l2network(self):
"""Creates an l2 network and verifies user is able to deploy a VM in it"""
# Validate the following:
# 1. Deploys a VM
# 2. There are no network services available since this is L2 Network
self.virtual_machine = VirtualMachine.create(
self.apiclient,
self.services["virtual_machine"],
templateid=self.template.id,
serviceofferingid=self.service_offering.id,
networkids=self.l2_network.id,
zoneid=self.zone.id
)
self.cleanup.insert(0, self.virtual_machine)
list_vm = list_virtual_machines(
self.apiclient,
id = self.virtual_machine.id
)
self.assertEqual(
isinstance(list_vm, list),
True,
"Check if virtual machine is present"
)
self.assertEqual(
list_vm[0].nic[0].type,
'L2',
"Check Correct Network type is available"
)
self.assertFalse(
'gateway' in str(list_vm[0].nic[0])
)
self.assertFalse(
'ipaddress' in str(list_vm[0].nic[0])
)
return
@attr(tags=["advanced", "advancedns", "smoke"], required_hardware="false")
def test_delete_network_while_vm_on_it(self):
"""It verifies the user is not able to delete network which has running vms"""
# Validate the following:
# 1. Deploys a VM
# 2. Tries to delete network and expects exception to appear
self.virtual_machine = VirtualMachine.create(
self.apiclient,
self.services["virtual_machine"],
templateid=self.template.id,
serviceofferingid=self.service_offering.id,
networkids=self.l2_network.id,
zoneid=self.zone.id
)
self.cleanup.insert(0, self.virtual_machine)
list_vm = list_virtual_machines(
self.apiclient,
id = self.virtual_machine.id
)
self.assertEqual(
isinstance(list_vm, list),
True,
"Check if virtual machine is present"
)
try:
self.l2_network.delete(self.apiclient)
except Exception:
pass
else:
self.fail("Expected an exception to be thrown, failing")
return
@attr(tags=["advanced", "advancedns", "smoke"], required_hardware="false")
def test_l2network_restart(self):
"""This test covers a few scenarios around restarting a network"""
# Validate the following:
# 1. Creates a l2 network
# 2. Tries to restart a network with no VMs, which trows error 'not in the right state'
# 3. Deploys a VM
# 4. Restarts the network without cleanup
# 5. Restarts the network with cleanup
try:
self.l2_network.restart(self.apiclient, cleanup=True)
except Exception:
pass
else:
self.fail("Expected an exception to be thrown, failing")
li_net = self.l2_network.list(self.apiclient)[0]
self.assertTrue(
li_net.state,
'Allocated'
"Not the correct state"
)
self.virtual_machine = VirtualMachine.create(
self.apiclient,
self.services["virtual_machine"],
templateid=self.template.id,
serviceofferingid=self.service_offering.id,
networkids=self.l2_network.id,
zoneid=self.zone.id
)
self.cleanup.insert(0, self.virtual_machine)
list_vm = list_virtual_machines(
self.apiclient,
id = self.virtual_machine.id
)
self.assertEqual(
isinstance(list_vm, list),
True,
"Check if virtual machine is present"
)
self.l2_network.restart(self.apiclient, cleanup=False)
li_net = self.l2_network.list(self.apiclient)[0]
self.assertTrue(
li_net.state,
'Implemented'
"Not the correct state"
)
self.l2_network.restart(self.apiclient, cleanup=True)
li_net = self.l2_network.list(self.apiclient)[0]
self.assertTrue(
li_net.state,
'Implemented'
"Not the correct state"
)
return

View File

@ -190,6 +190,10 @@ test_data = {
"displaytext": "Test Network",
"acltype": "Account",
},
"l2-network": {
"name": "Test L2 Network",
"displaytext": "Test L2 Network"
},
"network2": {
"name": "Test Network Shared",
"displaytext": "Test Network Shared",
@ -200,6 +204,14 @@ test_data = {
"endip": "172.16.15.41",
"acltype": "Account",
},
"l2-network_offering": {
"name": 'Test L2 - Network offering',
"displaytext": 'Test L2 - Network offering',
"guestiptype": 'L2',
"supportedservices": '',
"traffictype": 'GUEST',
"availability": 'Optional'
},
"network_offering": {
"name": 'Test Network offering',
"displaytext": 'Test Network offering',

View File

@ -338,6 +338,7 @@ var dictionary = {
"label.add.isolated.guest.network": "Add Isolated Guest Network",
"label.add.isolated.guest.network.with.sourcenat": "Add Isolated Guest Network with SourceNat",
"label.add.isolated.network": "Add Isolated Network",
"label.add.l2.guest.network":"Add L2 Guest Network",
"label.add.ldap.account": "Add LDAP account",
"label.add.list.name": "ACL List Name",
"label.add.load.balancer": "Add Load Balancer",

View File

@ -338,6 +338,7 @@ var dictionary = {
"label.add.isolated.guest.network": "Add Isolated Guest Network",
"label.add.isolated.guest.network.with.sourcenat": "Add Isolated Guest Network with SourceNat",
"label.add.isolated.network": "Add Isolated Network",
"label.add.l2.guest.network":"Add L2 Guest Network",
"label.add.ldap.account": "Add LDAP account",
"label.add.list.name": "ACL List Name",
"label.add.load.balancer": "Add Load Balancer",

View File

@ -338,6 +338,7 @@ var dictionary = {
"label.add.isolated.guest.network": "Isoliertes Gastnetzwerk hinzufügen",
"label.add.isolated.guest.network.with.sourcenat": "Isoliertes Gastnetzwerk mit Source-NAT hinzufügen",
"label.add.isolated.network": "Isoliertes Netzwerk hinzufügen",
"label.add.l2.guest.network":"Add L2 Guest Network",
"label.add.ldap.account": "LDAP-Konto hinzufügen",
"label.add.list.name": "ACL-Listename",
"label.add.load.balancer": "Lastverteiler hinzufügen",

View File

@ -340,6 +340,7 @@ var dictionary = {"ICMP.code":"ICMP Code",
"label.add.isolated.guest.network":"Add Isolated Guest Network",
"label.add.isolated.guest.network.with.sourcenat":"Add Isolated Guest Network with SourceNat",
"label.add.isolated.network":"Add Isolated Network",
"label.add.l2.guest.network":"Add L2 Guest Network",
"label.add.ldap.account":"Add LDAP account",
"label.add.list.name":"ACL List Name",
"label.add.load.balancer":"Add Load Balancer",

View File

@ -338,6 +338,7 @@ var dictionary = {
"label.add.isolated.guest.network": "Añadir Red Invitado Aislada",
"label.add.isolated.guest.network.with.sourcenat": "Agregar Red de Invitado Aislada con NatOrigen",
"label.add.isolated.network": "Agregar Red Aislada",
"label.add.l2.guest.network":"Add L2 Guest Network",
"label.add.ldap.account": "Agregar cuenta LDAP",
"label.add.list.name": "Nombre de la Lista ACL",
"label.add.load.balancer": "Añadir balanceador de carga",

View File

@ -338,6 +338,7 @@ var dictionary = {
"label.add.isolated.guest.network": "Ajouter un réseau d'invité isolé",
"label.add.isolated.guest.network.with.sourcenat": "Ajouter un réseau d'invité isolé avec SourceNat",
"label.add.isolated.network": "Ajouter un réseau isolé",
"label.add.l2.guest.network":"Add L2 Guest Network",
"label.add.ldap.account": "Ajouter un compte LDAP",
"label.add.list.name": "Nom Liste ACL",
"label.add.load.balancer": "Ajouter un répartiteur de charge",

View File

@ -338,6 +338,7 @@ var dictionary = {
"label.add.isolated.guest.network": "Izolált vendég hálózat felvétele",
"label.add.isolated.guest.network.with.sourcenat": "Add Isolated Guest Network with SourceNat",
"label.add.isolated.network": "Izolált hálózat felvétele",
"label.add.l2.guest.network":"Add L2 Guest Network",
"label.add.ldap.account": "LDAP hozzáférés felvétele",
"label.add.list.name": "ACL lista név",
"label.add.load.balancer": "Terheléselosztó felvétele",

View File

@ -338,6 +338,7 @@ var dictionary = {
"label.add.isolated.guest.network": "Add Isolated Guest Network",
"label.add.isolated.guest.network.with.sourcenat": "Add Isolated Guest Network with SourceNat",
"label.add.isolated.network": "Add Isolated Network",
"label.add.l2.guest.network":"Add L2 Guest Network",
"label.add.ldap.account": "Aggiungi un account LDAP",
"label.add.list.name": "ACL List Name",
"label.add.load.balancer": "Aggiungere un Load Balancer",

View File

@ -338,6 +338,7 @@ var dictionary = {
"label.add.isolated.guest.network": "分離ゲストネットワークの追加",
"label.add.isolated.guest.network.with.sourcenat": "分離ゲストネットワーク(送信元NAT)の追加",
"label.add.isolated.network": "分離されたネットワークの追加",
"label.add.l2.guest.network":"Add L2 Guest Network",
"label.add.ldap.account": "LDAP アカウントの追加",
"label.add.list.name": "ACL 一覧名",
"label.add.load.balancer": "ロード バランサーの追加",

View File

@ -338,6 +338,7 @@ var dictionary = {
"label.add.isolated.guest.network": "Add Isolated Guest Network",
"label.add.isolated.guest.network.with.sourcenat": "Add Isolated Guest Network with SourceNat",
"label.add.isolated.network": "Add Isolated Network",
"label.add.l2.guest.network":"Add L2 Guest Network",
"label.add.ldap.account": "Add LDAP account",
"label.add.list.name": "ACL List Name",
"label.add.load.balancer": "네트워크 로드 공유 장치 추가",

View File

@ -338,6 +338,7 @@ var dictionary = {
"label.add.isolated.guest.network": "Legg til Isolert gjestenettverk",
"label.add.isolated.guest.network.with.sourcenat": "Legg til isolert gjestenettverk med kilde-NAT",
"label.add.isolated.network": "Legg Til Isolert Nettverk",
"label.add.l2.guest.network":"Add L2 Guest Network",
"label.add.ldap.account": "Legg til LDAP-konto",
"label.add.list.name": "ACL listenavn",
"label.add.load.balancer": "Legg til lastbalanserer",

View File

@ -338,6 +338,7 @@ var dictionary = {
"label.add.isolated.guest.network": "Voeg een geïsoleerd netwerk toe",
"label.add.isolated.guest.network.with.sourcenat": "voeg en geïsoleerd gast netwerk met bron-NAT toe",
"label.add.isolated.network": "Geisoleerd Netwerk Toevoegen",
"label.add.l2.guest.network":"Add L2 Guest Network",
"label.add.ldap.account": "Voeg LDAP account toe",
"label.add.list.name": "ACL lijst naam",
"label.add.load.balancer": "Voeg Load Balancer toe",

View File

@ -338,6 +338,7 @@ var dictionary = {
"label.add.isolated.guest.network": "Add Isolated Guest Network",
"label.add.isolated.guest.network.with.sourcenat": "Add Isolated Guest Network with SourceNat",
"label.add.isolated.network": "Add Isolated Network",
"label.add.l2.guest.network":"Add L2 Guest Network",
"label.add.ldap.account": "Add LDAP account",
"label.add.list.name": "ACL List Name",
"label.add.load.balancer": "Add Load Balancer",

View File

@ -338,6 +338,7 @@ var dictionary = {
"label.add.isolated.guest.network": "Adiciona Rede Guest Isolada",
"label.add.isolated.guest.network.with.sourcenat": "Adicionar rede Guest isolada com SourceNat",
"label.add.isolated.network": "Adiciona Rede Isolada",
"label.add.l2.guest.network":"Add L2 Guest Network",
"label.add.ldap.account": "Adicionar Conta LDAP",
"label.add.list.name": "Nome de Lista ACL",
"label.add.load.balancer": "Adicionar Load Balance",

View File

@ -338,6 +338,7 @@ var dictionary = {
"label.add.isolated.guest.network": "Добавить изолированную гостевую сеть",
"label.add.isolated.guest.network.with.sourcenat": "Add Isolated Guest Network with SourceNat",
"label.add.isolated.network": "Добавить изолированную сеть",
"label.add.l2.guest.network":"Add L2 Guest Network",
"label.add.ldap.account": "Добавить LDAP аккаунт",
"label.add.list.name": "ACL List Name",
"label.add.load.balancer": "Добавить балансировщик нагрузки",

View File

@ -338,6 +338,7 @@ var dictionary = {
"label.add.isolated.guest.network": "添加隔离的来宾网络",
"label.add.isolated.guest.network.with.sourcenat": "添加隔离的来宾网络并启用 SourceNat",
"label.add.isolated.network": "添加隔离网络",
"label.add.l2.guest.network":"Add L2 Guest Network",
"label.add.ldap.account": "添加 LDAP 账户",
"label.add.list.name": "ACL 列表名称",
"label.add.load.balancer": "添加负载平衡器",

View File

@ -2403,13 +2403,19 @@
//*** VPC checkbox ***
var $useVpc = args.$form.find('.form-item[rel=\"useVpc\"]');
var $useVpcCb = $useVpc.find("input[type=checkbox]");
var $supportedServices = args.$form.find('.form-item[rel=\"supportedServices\"]');
if ($guestTypeField.val() == 'Shared') { //Shared network offering
$useVpc.hide();
$supportedServices.css('display', 'inline-block');
if ($useVpcCb.is(':checked')) { //if useVpc is checked,
$useVpcCb.removeAttr("checked"); //remove "checked" attribute in useVpc
}
} else { //Isolated network offering
} else if ($guestTypeField.val() == 'Isolated') { //Isolated network offering
$useVpc.css('display', 'inline-block');
$supportedServices.css('display', 'inline-block');
} else if ($guestTypeField.val() == 'L2') {
$useVpc.hide();
$supportedServices.hide();
}
var $providers = $useVpcCb.closest('form').find('.dynamic-input select[name!="service.Connectivity.provider"]');
var $optionsOfProviders = $providers.find('option');
@ -2754,6 +2760,9 @@
}, {
id: 'Shared',
description: 'Shared'
}, {
id: 'L2',
description: 'L2'
}]
});
@ -2766,10 +2775,9 @@
$form.find('.form-item[rel=isPersistent]').find('input[type=checkbox]').attr("disabled", "disabled");
} else { //$(this).val() == "Isolated"
} else if ($(this).val() == "Isolated" || $(this).val() == "L2") {
$form.find('.form-item[rel=specifyVlan]').find('input[type=checkbox]').removeAttr("disabled"); //make it editable
$form.find('.form-item[rel=isPersistent]').find('input[type=checkbox]').removeAttr("disabled");
}
});
}
@ -3370,6 +3378,12 @@
} else { //Isolated Network with Non-persistent network
delete inputData.isPersistent; //if Persistent checkbox is unchecked, do not pass isPersistent parameter to API call since we need to keep API call's size as small as possible (p.s. isPersistent is defaulted as false at server-side)
}
} else if (inputData['guestIpType'] == "L2") {
if (inputData['specifyVlan'] == 'on') { //specifyVlan checkbox is checked
inputData['specifyVlan'] = true;
} else { //specifyVlan checkbox is unchecked
delete inputData.specifyVlan; //if specifyVlan checkbox is unchecked, do not pass specifyVlan parameter to API call since we need to keep API call's size as small as possible (p.s. specifyVlan is defaulted as false at server-side)
}
}
if (inputData['forvpc'] == 'on') {

View File

@ -431,6 +431,9 @@
var items = json.listvirtualmachinesresponse.virtualmachine;
if (items) {
$.each(items, function(idx, vm) {
if (! vm.ipaddress) {
vm['ipaddress'] = "N/A";
}
if (vm.nic && vm.nic.length > 0 && vm.nic[0].ipaddress) {
items[idx].ipaddress = vm.nic[0].ipaddress;
}

View File

@ -800,6 +800,10 @@
rootAdminAddGuestNetwork: $.extend({}, addGuestNetworkDialog.def, {
isHeader: true
}),
rootAdminAddL2Network: $.extend({}, addL2GuestNetwork.def, {
isHeader: true
})
},
@ -954,7 +958,8 @@
path: 'network.ipAddresses',
label: 'label.menu.ipaddresses',
preFilter: function(args) {
if (args.context.networks[0].state == 'Destroyed')
if (args.context.networks[0].state == 'Destroyed' ||
args.context.networks[0].type == 'L2')
return false;
return true;
@ -971,6 +976,11 @@
return 'label.edit.network.details';
}
},
preFilter: function(args) {
if (args.context.networks[0].state == 'Destroyed')
return false;
return true;
},
action: function(args) {
var data = {
id: args.context.networks[0].id,
@ -1051,8 +1061,13 @@
}
},
'restart': {
restart: {
label: 'label.restart.network',
preFilter: function(args) {
if (args.context.networks[0].state == 'Destroyed')
return false;
return true;
},
createForm: {
title: 'label.restart.network',
desc: 'message.restart.network',
@ -1108,6 +1123,11 @@
remove: {
label: 'label.action.delete.network',
preFilter: function(args) {
if (args.context.networks[0].state == 'Destroyed')
return false;
return true;
},
messages: {
confirm: function(args) {
return 'message.action.delete.network';

View File

@ -818,6 +818,263 @@ var addGuestNetworkDialog = {
}
}
var addL2GuestNetwork = {
zoneObjs: [],
physicalNetworkObjs: [],
networkOfferingObjs: [],
def: {
label: 'label.add.l2.guest.network',
messages: {
notification: function(args) {
return 'label.add.l2.guest.network';
}
},
preFilter: function(args) {
if (isAdmin())
return true;
else
return false;
},
createForm: {
title: 'label.add.l2.guest.network',
fields: {
name: {
label: 'label.name',
validation: {
required: true
},
docID: 'helpGuestNetworkName'
},
displayText: {
label: 'label.display.text',
validation: {
required: true
},
docID: 'helpGuestNetworkDisplayText'
},
zoneId: {
label: 'label.zone',
validation: {
required: true
},
docID: 'helpGuestNetworkZone',
select: function(args) {
$.ajax({
url: createURL('listZones'),
success: function(json) {
var zones = $.grep(json.listzonesresponse.zone, function(zone) {
return (zone.networktype == 'Advanced' && zone.securitygroupsenabled != true); //Isolated networks can only be created in Advanced SG-disabled zone (but not in Basic zone nor Advanced SG-enabled zone)
});
args.response.success({
data: $.map(zones, function(zone) {
return {
id: zone.id,
description: zone.name
};
})
});
}
});
}
},
networkOfferingId: {
label: 'label.network.offering',
validation: {
required: true
},
dependsOn: 'zoneId',
docID: 'helpGuestNetworkNetworkOffering',
select: function(args) {
var data = {
zoneid: args.zoneId,
guestiptype: 'L2',
state: 'Enabled'
};
if ('vpc' in args.context) { //from VPC section
$.extend(data, {
forVpc: true
});
}
else { //from guest network section
var vpcs;
$.ajax({
url: createURL('listVPCs'),
data: {
listAll: true
},
async: false,
success: function(json) {
vpcs = json.listvpcsresponse.vpc;
}
});
if (vpcs == null || vpcs.length == 0) { //if there is no VPC in the system
$.extend(data, {
forVpc: false
});
}
}
if(!isAdmin()) { //normal user is not aware of the VLANs in the system, so normal user is not allowed to create network with network offerings whose specifyvlan = true
$.extend(data, {
specifyvlan: false
});
}
$.ajax({
url: createURL('listNetworkOfferings'),
data: data,
success: function(json) {
networkOfferingObjs = json.listnetworkofferingsresponse.networkoffering;
args.$select.change(function() {
var $vlan = args.$select.closest('form').find('[rel=vlan]');
var networkOffering = $.grep(
networkOfferingObjs, function(netoffer) {
return netoffer.id == args.$select.val();
}
)[0];
if (networkOffering.specifyvlan) {
$vlan.css('display', 'inline-block');
} else {
$vlan.hide();
}
});
args.response.success({
data: $.map(networkOfferingObjs, function(zone) {
return {
id: zone.id,
description: zone.name
};
})
});
}
});
}
},
vlan: {
label: 'label.vlan',
validation: {
required: true
},
isHidden: true
},
domain: {
label: 'label.domain',
isHidden: function(args) {
if (isAdmin() || isDomainAdmin())
return false;
else
return true;
},
select: function(args) {
if (isAdmin() || isDomainAdmin()) {
$.ajax({
url: createURL("listDomains&listAll=true"),
success: function(json) {
var items = [];
items.push({
id: "",
description: ""
});
var domainObjs = json.listdomainsresponse.domain;
$(domainObjs).each(function() {
items.push({
id: this.id,
description: this.path
});
});
items.sort(function(a, b) {
return a.description.localeCompare(b.description);
});
args.response.success({
data: items
});
}
});
args.$select.change(function() {
var $form = $(this).closest('form');
if ($(this).val() == "") {
$form.find('.form-item[rel=account]').hide();
} else {
$form.find('.form-item[rel=account]').css('display', 'inline-block');
}
});
} else {
args.response.success({
data: null
});
}
}
},
account: {
label: 'label.account',
validation: {
required: true
},
isHidden: function(args) {
if (isAdmin() || isDomainAdmin())
return false;
else
return true;
}
}
}
},
action: function(args) {
var dataObj = {
zoneId: args.data.zoneId,
name: args.data.name,
displayText: args.data.displayText,
networkOfferingId: args.data.networkOfferingId
};
if (args.$form.find('.form-item[rel=vlan]').css('display') != 'none') {
$.extend(dataObj, {
vlan: args.data.vlan
});
}
if (args.data.domain != null && args.data.domain.length > 0) {
$.extend(dataObj, {
domainid: args.data.domain
});
if (args.data.account != null && args.data.account.length > 0) {
$.extend(dataObj, {
account: args.data.account
});
}
}
$.ajax({
url: createURL('createNetwork'),
data: dataObj,
success: function(json) {
args.response.success({
data: json.createnetworkresponse.network
});
},
error: function(json) {
args.response.error(parseXMLHttpResponse(json));
}
});
},
notification: {
poll: function(args) {
args.complete();
}
}
}
}
function isLdapEnabled() {
var result;