Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,55 @@
package com.cloud.network.element;

import com.cloud.exception.ResourceUnavailableException;
import com.cloud.network.IpAddress;
import com.cloud.network.Site2SiteCustomerGateway;
import com.cloud.network.Site2SiteVpnConnection;
import com.cloud.network.Site2SiteVpnGateway;
import com.cloud.network.vpc.Vpc;
import com.cloud.utils.component.Adapter;

public interface Site2SiteVpnServiceProvider extends Adapter {
default void validateSite2SiteVpnCustomerGateway(Site2SiteCustomerGateway customerGateway) {
}

boolean startSite2SiteVpn(Site2SiteVpnConnection conn) throws ResourceUnavailableException;

boolean stopSite2SiteVpn(Site2SiteVpnConnection conn) throws ResourceUnavailableException;

/**
* Permanently removes a provider-side connection. This is distinct from stop: providers
* may disable a tunnel while retaining its profiles for an immediate reconnect, but deletion
* must remove all objects owned by the CloudStack connection.
*/
default boolean deleteSite2SiteVpn(Site2SiteVpnConnection conn) throws ResourceUnavailableException {
return stopSite2SiteVpn(conn);
}

/**
* Lets the provider supply the public IP the VPN gateway should terminate on, instead of the
* VPC source NAT IP. Providers that terminate VPN on an external gateway (e.g. NSX Tier-1)
* acquire and return a dedicated IP here; requestedIp, when not null, is the IP the caller
* asked for and must be validated by the provider. Returning null means the provider has no
* preference and the manager falls back to the default IP selection.
*/
default IpAddress acquireVpnGatewayIp(Vpc vpc, IpAddress requestedIp) {
return null;
}

/**
* Counterpart of {@link #acquireVpnGatewayIp(Vpc, IpAddress)}: invoked when a VPN gateway is
* deleted so the provider can tear down external VPN resources and release the gateway IP if
* it was acquired by the provider.
*/
default void releaseVpnGatewayIp(Site2SiteVpnGateway gateway) {
}

/**
* Identifies a gateway previously owned by this provider. This is used during teardown when
* an offering has been edited since the gateway was created and the current service map no
* longer advertises the provider.
*/
default boolean ownsVpnGateway(Site2SiteVpnGateway gateway) {
return false;
}
}
12 changes: 12 additions & 0 deletions api/src/main/java/com/cloud/network/nsx/NsxService.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
// under the License.
package com.cloud.network.nsx;

import java.util.List;

import org.apache.cloudstack.framework.config.ConfigKey;

import com.cloud.network.IpAddress;
Expand All @@ -35,4 +37,14 @@ public interface NsxService {
boolean createVpcNetwork(Long zoneId, long accountId, long domainId, Long vpcId, String vpcName, boolean sourceNatEnabled);
boolean updateVpcSourceNatIp(Vpc vpc, IpAddress address);
String getSegmentId(long domainId, long accountId, long zoneId, Long vpcId, long networkId);

NsxVpnGatewayResult createVpnGateway(Vpc vpc, String localEndpointIp);
boolean deleteVpnGateway(Vpc vpc);
boolean createVpnConnection(Vpc vpc, String connectionUuid, String peerAddress, String psk,
String ikePolicy, String espPolicy, Long ikeLifetime, Long espLifetime,
boolean dpdEnabled, String ikeVersion, boolean passive, List<String> peerCidrs,
String vtiLocalIp, String vtiPeerIp, int vtiPrefixLength, String localEndpointIp);
boolean deleteVpnConnection(Vpc vpc, String connectionUuid);
boolean updateVpnConnectionState(Vpc vpc, String connectionUuid, boolean enabled);
String getVpnConnectionStatus(Vpc vpc, String connectionUuid);
}
36 changes: 36 additions & 0 deletions api/src/main/java/com/cloud/network/nsx/NsxVpnGatewayResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// 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.nsx;

public class NsxVpnGatewayResult {

private final boolean successful;
private final boolean endpointMayBeInUse;

public NsxVpnGatewayResult(boolean successful, boolean endpointMayBeInUse) {
this.successful = successful;
this.endpointMayBeInUse = endpointMayBeInUse;
}

public boolean isSuccessful() {
return successful;
}

public boolean isEndpointMayBeInUse() {
return endpointMayBeInUse;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import com.cloud.event.EventTypes;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.network.Site2SiteVpnConnection;
import com.cloud.network.Site2SiteVpnGateway;
import com.cloud.user.Account;

@APICommand(name = "deleteVpnConnection", description = "Delete site to site VPN connection", responseObject = SuccessResponse.class, entityType = {Site2SiteVpnConnection.class},
Expand Down Expand Up @@ -73,6 +74,21 @@ public String getEventType() {
return EventTypes.EVENT_S2S_VPN_CONNECTION_DELETE;
}

@Override
public String getSyncObjType() {
return BaseAsyncCmd.vpcSyncObject;
}

@Override
public Long getSyncObjId() {
Site2SiteVpnConnection connection = _entityMgr.findById(Site2SiteVpnConnection.class, id);
if (connection == null) {
return null;
}
Site2SiteVpnGateway gateway = _s2sVpnService.getVpnGateway(connection.getVpnGatewayId());
return gateway == null ? null : gateway.getVpcId();
}

@Override
public void execute() {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,17 @@ public String getEventType() {
return EventTypes.EVENT_S2S_VPN_GATEWAY_DELETE;
}

@Override
public String getSyncObjType() {
return BaseAsyncCmd.vpcSyncObject;
}

@Override
public Long getSyncObjId() {
Site2SiteVpnGateway gateway = _entityMgr.findById(Site2SiteVpnGateway.class, id);
return gateway == null ? null : gateway.getVpcId();
}

@Override
public void execute() {
boolean result = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import com.cloud.event.EventTypes;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.network.Site2SiteVpnConnection;
import com.cloud.network.Site2SiteVpnGateway;
import com.cloud.user.Account;

@APICommand(name = "resetVpnConnection", description = "Reset site to site VPN connection", responseObject = Site2SiteVpnConnectionResponse.class, entityType = {Site2SiteVpnConnection.class},
Expand Down Expand Up @@ -91,6 +92,21 @@ public String getEventType() {
return EventTypes.EVENT_S2S_VPN_CONNECTION_RESET;
}

@Override
public String getSyncObjType() {
return BaseAsyncCmd.vpcSyncObject;
}

@Override
public Long getSyncObjId() {
Site2SiteVpnConnection connection = _entityMgr.findById(Site2SiteVpnConnection.class, id);
if (connection == null) {
return null;
}
Site2SiteVpnGateway gateway = _s2sVpnService.getVpnGateway(connection.getVpnGatewayId());
return gateway == null ? null : gateway.getVpcId();
}

@Override
public void execute() {
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.api.command.user.vpn;

import com.cloud.network.Site2SiteVpnConnection;
import com.cloud.network.Site2SiteVpnGateway;
import com.cloud.network.vpn.Site2SiteVpnService;
import com.cloud.utils.db.EntityManager;
import org.apache.cloudstack.api.BaseAsyncCmd;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.when;

@RunWith(MockitoJUnitRunner.class)
public class VpnConnectionLifecycleCmdTest {

private static final Long CONNECTION_ID = 1L;
private static final Long VPN_GATEWAY_ID = 2L;
private static final Long VPC_ID = 3L;

@Mock
private EntityManager entityManager;
@Mock
private Site2SiteVpnService vpnService;
@Mock
private Site2SiteVpnConnection connection;
@Mock
private Site2SiteVpnGateway gateway;

private ResetVpnConnectionCmd resetCmd;
private DeleteVpnConnectionCmd deleteCmd;
private DeleteVpnGatewayCmd deleteGatewayCmd;

@Before
public void setUp() {
resetCmd = new ResetVpnConnectionCmd();
resetCmd._entityMgr = entityManager;
resetCmd._s2sVpnService = vpnService;
ReflectionTestUtils.setField(resetCmd, "id", CONNECTION_ID);

deleteCmd = new DeleteVpnConnectionCmd();
deleteCmd._entityMgr = entityManager;
deleteCmd._s2sVpnService = vpnService;
ReflectionTestUtils.setField(deleteCmd, "id", CONNECTION_ID);

deleteGatewayCmd = new DeleteVpnGatewayCmd();
deleteGatewayCmd._entityMgr = entityManager;
ReflectionTestUtils.setField(deleteGatewayCmd, "id", VPN_GATEWAY_ID);
}

@Test
public void testResetUsesVpcSynchronization() {
configureExistingConnection();

assertEquals(BaseAsyncCmd.vpcSyncObject, resetCmd.getSyncObjType());
assertEquals(VPC_ID, resetCmd.getSyncObjId());
}

@Test
public void testDeleteUsesVpcSynchronization() {
configureExistingConnection();

assertEquals(BaseAsyncCmd.vpcSyncObject, deleteCmd.getSyncObjType());
assertEquals(VPC_ID, deleteCmd.getSyncObjId());
}

@Test
public void testDeleteGatewayUsesVpcSynchronization() {
when(entityManager.findById(Site2SiteVpnGateway.class, VPN_GATEWAY_ID)).thenReturn(gateway);
when(gateway.getVpcId()).thenReturn(VPC_ID);

assertEquals(BaseAsyncCmd.vpcSyncObject, deleteGatewayCmd.getSyncObjType());
assertEquals(VPC_ID, deleteGatewayCmd.getSyncObjId());
}

@Test
public void testResetWithMissingConnectionHasNoSynchronizationId() {
when(entityManager.findById(Site2SiteVpnConnection.class, CONNECTION_ID)).thenReturn(null);

assertNull(resetCmd.getSyncObjId());
}

@Test
public void testDeleteWithMissingConnectionHasNoSynchronizationId() {
when(entityManager.findById(Site2SiteVpnConnection.class, CONNECTION_ID)).thenReturn(null);

assertNull(deleteCmd.getSyncObjId());
}

@Test
public void testDeleteGatewayWithMissingGatewayHasNoSynchronizationId() {
when(entityManager.findById(Site2SiteVpnGateway.class, VPN_GATEWAY_ID)).thenReturn(null);

assertNull(deleteGatewayCmd.getSyncObjId());
}

@Test
public void testResetWithMissingGatewayHasNoSynchronizationId() {
when(entityManager.findById(Site2SiteVpnConnection.class, CONNECTION_ID)).thenReturn(connection);
when(connection.getVpnGatewayId()).thenReturn(VPN_GATEWAY_ID);
when(vpnService.getVpnGateway(VPN_GATEWAY_ID)).thenReturn(null);

assertNull(resetCmd.getSyncObjId());
}

@Test
public void testDeleteWithMissingGatewayHasNoSynchronizationId() {
when(entityManager.findById(Site2SiteVpnConnection.class, CONNECTION_ID)).thenReturn(connection);
when(connection.getVpnGatewayId()).thenReturn(VPN_GATEWAY_ID);
when(vpnService.getVpnGateway(VPN_GATEWAY_ID)).thenReturn(null);

assertNull(deleteCmd.getSyncObjId());
}

private void configureExistingConnection() {
when(entityManager.findById(Site2SiteVpnConnection.class, CONNECTION_ID)).thenReturn(connection);
when(connection.getVpnGatewayId()).thenReturn(VPN_GATEWAY_ID);
when(vpnService.getVpnGateway(VPN_GATEWAY_ID)).thenReturn(gateway);
when(gateway.getVpcId()).thenReturn(VPC_ID);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
public class NsxAnswer extends Answer {

private boolean objectExists;
private boolean endpointMayBeInUse;

public NsxAnswer(final Command command, final boolean success, final String details) {
super(command, success, details);
Expand All @@ -38,4 +39,12 @@ public boolean isObjectExistent() {
public void setObjectExists(boolean objectExisted) {
this.objectExists = objectExisted;
}

public boolean isEndpointMayBeInUse() {
return endpointMayBeInUse;
}

public void setEndpointMayBeInUse(boolean endpointMayBeInUse) {
this.endpointMayBeInUse = endpointMayBeInUse;
}
}
Loading