/** * Copyright (C) 2012-2015 Dell, Inc * See annotations for authorship information * * ==================================================================== * Licensed 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.dasein.cloud.google.network; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.Locale; import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.apache.log4j.Logger; import org.dasein.cloud.CloudErrorType; import org.dasein.cloud.CloudException; import org.dasein.cloud.InternalException; import org.dasein.cloud.OperationNotSupportedException; import org.dasein.cloud.ProviderContext; import org.dasein.cloud.Requirement; import org.dasein.cloud.ResourceStatus; import org.dasein.cloud.compute.VirtualMachine; import org.dasein.cloud.google.GoogleException; import org.dasein.cloud.google.GoogleMethod; import org.dasein.cloud.google.GoogleOperationType; import org.dasein.cloud.google.Google; import org.dasein.cloud.google.capabilities.GCEIPAddressCapabilities; import org.dasein.cloud.identity.ServiceAction; import org.dasein.cloud.network.AbstractIpAddressSupport; import org.dasein.cloud.network.AddressType; import org.dasein.cloud.network.IPVersion; import org.dasein.cloud.network.IpAddress; import org.dasein.cloud.network.IpForwardingRule; import org.dasein.cloud.network.Protocol; import org.dasein.cloud.util.APITrace; import com.google.api.client.googleapis.json.GoogleJsonResponseException; import com.google.api.services.compute.Compute; import com.google.api.services.compute.model.AccessConfig; import com.google.api.services.compute.model.Address; import com.google.api.services.compute.model.AddressAggregatedList; import com.google.api.services.compute.model.AddressList; import com.google.api.services.compute.model.Operation; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.io.IOException; import java.util.*; import java.util.concurrent.Future; import java.util.regex.Matcher; import java.util.regex.Pattern; public class IPAddressSupport extends AbstractIpAddressSupport<Google> { static private final ExecutorService threadPool = Executors.newFixedThreadPool(10); static private final Logger logger = Google.getLogger(IPAddressSupport.class); protected IPAddressSupport(Google provider) { super(provider); } @Override public void assign(@Nonnull String addressId, @Nonnull String serverId) throws InternalException, CloudException { APITrace.begin(getProvider(), "IpAddress.assign"); try{ Compute gce = getProvider().getGoogleCompute(); IpAddress ipAddress = getIpAddress(addressId); VirtualMachine vm = getProvider().getComputeServices().getVirtualMachineSupport().getVirtualMachine(serverId); AccessConfig accessConfig = new AccessConfig(); accessConfig.setName("External NAT"); accessConfig.setKind("compute#accessConfig"); accessConfig.setType("ONE_TO_ONE_NAT"); accessConfig.setNatIP(ipAddress.getRawAddress().getIpAddress()); try{ GoogleMethod method = new GoogleMethod(getProvider()); //need to try and delete the existing access config if an ephemeral one exists try{ Operation job = gce.instances().deleteAccessConfig(getContext().getAccountNumber(), vm.getProviderDataCenterId(), vm.getName(), "External NAT", "nic0").execute(); method.getOperationComplete(getContext(), job, GoogleOperationType.ZONE_OPERATION, "", vm.getProviderDataCenterId()); } catch(Exception ex){/* Don't care if there's an exception here */} Operation job = gce.instances().addAccessConfig(getContext().getAccountNumber(), vm.getProviderDataCenterId(), serverId, "nic0", accessConfig).execute(); if(!method.getOperationComplete(getContext(), job, GoogleOperationType.ZONE_OPERATION, "", vm.getProviderDataCenterId())){ throw new CloudException("An error occurred assigning the IP: " + addressId + ": Operation timed out"); } } catch (Exception ex) { logger.error(ex.getMessage()); if (ex.getClass() == GoogleJsonResponseException.class) { GoogleJsonResponseException gjre = (GoogleJsonResponseException)ex; throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(), gjre.getContent(), gjre.getDetails().getMessage()); } else throw new CloudException("An error occurred assigning the IP: " + addressId + ": " + ex.getMessage()); } } finally { APITrace.end(); } } @Override public void assignToNetworkInterface(@Nonnull String addressId, @Nonnull String nicId) throws InternalException, CloudException { throw new OperationNotSupportedException("GCE does not support NICs"); } @Nonnull @Override public String forward(@Nonnull String addressId, int publicPort, @Nonnull Protocol protocol, int privatePort, @Nonnull String onServerId) throws InternalException, CloudException { throw new OperationNotSupportedException("Forwarding rules are not supported by GCE"); } private transient volatile GCEIPAddressCapabilities capabilities; private ListIpPoolCallable task; @Override public @Nonnull GCEIPAddressCapabilities getCapabilities(){ if(capabilities == null){ capabilities = new GCEIPAddressCapabilities(getProvider()); } return capabilities; } @Nullable @Override public IpAddress getIpAddress(@Nonnull String addressId) throws InternalException, CloudException { APITrace.begin(getProvider(), "IpAddress.getIpAddress"); try{ try{ Compute gce = getProvider().getGoogleCompute(); AddressAggregatedList addressList = gce.addresses().aggregatedList(getContext().getAccountNumber()).setFilter("name eq " + addressId).execute(); if(addressList != null && addressList.getItems() != null && !addressList.getItems().isEmpty()) { Iterator<String> regions = addressList.getItems().keySet().iterator(); while(regions.hasNext()){ String region = regions.next(); if(addressList.getItems() != null && addressList.getItems().get(region) != null && addressList.getItems().get(region).getAddresses() != null && !addressList.getItems().get(region).getAddresses().isEmpty()){ for(Address address : addressList.getItems().get(region).getAddresses()){ if(address.getName().equals(addressId))return toIpAddress(address); } } } } } catch (IOException ex) { logger.error(ex.getMessage()); if (ex.getClass() == GoogleJsonResponseException.class) { GoogleJsonResponseException gjre = (GoogleJsonResponseException)ex; throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(), gjre.getContent(), gjre.getDetails().getMessage()); } else throw new CloudException("An error occurred getting the IPAddress: " + ex.getMessage()); } throw new InternalException("Could not find IPAddress: " + addressId); } finally { APITrace.end(); } } @Nullable public String getIpAddressIdFromIP(@Nonnull String ipAddress, @Nonnull String regionId)throws InternalException, CloudException{ try{ Compute gce = getProvider().getGoogleCompute(); AddressList addressList = gce.addresses().list(getContext().getAccountNumber(), regionId).execute(); if(addressList != null && addressList.getItems() != null && !addressList.getItems().isEmpty()){ for(Address address : addressList.getItems()){ if(ipAddress.equals(address.getAddress()))return address.getName(); } } throw new InternalException("An address could not be found matching " + ipAddress + " in " + regionId); } catch (IOException ex) { logger.error(ex.getMessage()); if (ex.getClass() == GoogleJsonResponseException.class) { GoogleJsonResponseException gjre = (GoogleJsonResponseException)ex; throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(), gjre.getContent(), gjre.getDetails().getMessage()); } else throw new CloudException("An error occurred finding the specified IPAddress: " + ex.getMessage()); } } @Override @Deprecated public @Nonnull String getProviderTermForIpAddress(@Nonnull Locale locale) { return "Address"; } @Override @Deprecated public @Nonnull Requirement identifyVlanForVlanIPRequirement() throws CloudException, InternalException { return Requirement.NONE; } @Override @Deprecated public boolean isAssigned(@Nonnull AddressType type) { if(type.equals(AddressType.PUBLIC))return true; return false; } @Override @Deprecated public boolean isAssigned(@Nonnull IPVersion version) throws CloudException, InternalException { if(version.equals(IPVersion.IPV4))return true; return false; } @Override @Deprecated public boolean isAssignablePostLaunch(@Nonnull IPVersion version) throws CloudException, InternalException { return true; } @Override @Deprecated public boolean isForwarding() { return false; } @Override @Deprecated public boolean isForwarding(IPVersion version) throws CloudException, InternalException { if(version.equals(IPVersion.IPV4))return true; return false; } @Override @Deprecated public boolean isRequestable(@Nonnull AddressType type) { return true; } @Override @Deprecated public boolean isRequestable(@Nonnull IPVersion version) throws CloudException, InternalException { if(version.equals(IPVersion.IPV4))return true; return false; } @Override public boolean isSubscribed() throws CloudException, InternalException { return true; } @Nonnull @Override public Iterable<IpAddress> listPrivateIpPool(boolean unassignedOnly) throws InternalException, CloudException { return Collections.emptyList(); } @Nonnull @Override public Iterable<IpAddress> listPublicIpPool(boolean unassignedOnly) throws InternalException, CloudException { return listIpPool(IPVersion.IPV4, unassignedOnly); } @Nonnull @Override public Iterable<IpAddress> listIpPool(@Nonnull IPVersion version, boolean unassignedOnly) throws InternalException, CloudException { APITrace.begin(getProvider(), "IpAddress.listIpPool"); try{ if( !version.equals(IPVersion.IPV4) ) { return Collections.emptyList(); } List<IpAddress> addresses = new ArrayList<IpAddress>(); try{ Compute gce = getProvider().getGoogleCompute(); AddressList addressList = gce.addresses().list(getContext().getAccountNumber(), getContext().getRegionId()).execute(); if(addressList != null && addressList.getItems() != null && !addressList.getItems().isEmpty()){ for(Address address : addressList.getItems()){ IpAddress ipAddress = toIpAddress(address); if(ipAddress != null)addresses.add(ipAddress); } } return addresses; } catch (IOException ex) { logger.error(ex.getMessage()); if (ex.getClass() == GoogleJsonResponseException.class) { GoogleJsonResponseException gjre = (GoogleJsonResponseException)ex; throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(), gjre.getContent(), gjre.getDetails().getMessage()); } else throw new CloudException("An error occurred listing IPs: " + ex.getMessage()); } } finally { APITrace.end(); } } @Nonnull @Override public Iterable<ResourceStatus> listIpPoolStatus(@Nonnull IPVersion version) throws InternalException, CloudException { APITrace.begin(getProvider(), "IpAddress.listIpPoolStatus"); try{ if( !version.equals(IPVersion.IPV4) ) { return Collections.emptyList(); } List<ResourceStatus> statuses = new ArrayList<ResourceStatus>(); try{ Compute gce = getProvider().getGoogleCompute(); AddressAggregatedList addressList = gce.addresses().aggregatedList(getContext().getAccountNumber()).execute(); Iterator<String> regions = addressList.getItems().keySet().iterator(); while(regions.hasNext()){ String region = regions.next(); if ((null != addressList) && (null != addressList.getItems()) && (null != addressList.getItems().get(region)) && (null != addressList.getItems().get(region).getAddresses())) { for(Address address : addressList.getItems().get(region).getAddresses()){ ResourceStatus status = toStatus(address); if (status != null) { statuses.add(status); } } } } return statuses; } catch (IOException ex) { logger.error(ex.getMessage()); if (ex.getClass() == GoogleJsonResponseException.class) { GoogleJsonResponseException gjre = (GoogleJsonResponseException)ex; throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(), gjre.getContent(), gjre.getDetails().getMessage()); } else throw new CloudException("An error occurred listing IPs: " + ex.getMessage()); } } finally { APITrace.end(); } } @Nonnull @Override public Iterable<IpForwardingRule> listRules(@Nonnull String addressId) throws InternalException, CloudException { throw new OperationNotSupportedException("Forwarding rules are not supported by GCE"); } @Override public void releaseFromPool(@Nonnull String addressId) throws InternalException, CloudException { APITrace.begin(getProvider(), "IpAddress.releaseFromPool"); try{ try{ IpAddress ipAddress = getIpAddress(addressId); Compute gce = getProvider().getGoogleCompute(); Operation job = gce.addresses().delete(getContext().getAccountNumber(), ipAddress.getRegionId(), addressId).execute(); GoogleMethod method = new GoogleMethod(getProvider()); if(!method.getOperationComplete(getContext(), job, GoogleOperationType.REGION_OPERATION, ipAddress.getRegionId(), "")){ throw new CloudException("An error occurred releasing address: " + addressId + ": Operation timed out"); } } catch (IOException ex) { logger.error(ex.getMessage()); if (ex.getClass() == GoogleJsonResponseException.class) { GoogleJsonResponseException gjre = (GoogleJsonResponseException)ex; throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(), gjre.getContent(), gjre.getDetails().getMessage()); } else throw new CloudException("An error occurred releasing address: " + addressId + ": " + ex.getMessage()); } } finally { APITrace.end(); } } @Override public void releaseFromServer(@Nonnull String addressId) throws InternalException, CloudException { APITrace.begin(getProvider(), "IpAddress.releaseFromServer"); try{ Compute gce = getProvider().getGoogleCompute(); try{ Address address = gce.addresses().get(getContext().getAccountNumber(), getContext().getRegionId(), addressId).execute(); String zone = ""; String instance = ""; if (null != address.getUsers()) { for(String vm : address.getUsers()){ Pattern p = Pattern.compile("/zones/(.*?)/instances"); Matcher m = p.matcher(vm); if(m.find()){ zone = m.group(); instance = vm; break; } } } zone = zone.replace("/zones/", ""); zone = zone.replace("/instances", ""); instance = instance.substring(instance.lastIndexOf("/") + 1); Operation job = gce.instances().deleteAccessConfig(getContext().getAccountNumber(), zone, instance, "External NAT", "nic0").execute(); GoogleMethod method = new GoogleMethod(getProvider()); if(!method.getOperationComplete(getContext(), job, GoogleOperationType.ZONE_OPERATION, "", zone)){ throw new CloudException("An error occurred releasing the address from the server: Operation timed out"); } } catch (IOException ex) { logger.error(ex.getMessage()); if (ex.getClass() == GoogleJsonResponseException.class) { GoogleJsonResponseException gjre = (GoogleJsonResponseException)ex; throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(), gjre.getContent(), gjre.getDetails().getMessage()); } else throw new CloudException("An error occurred releasing the address from the server: " + ex.getMessage()); } catch (Exception ex) { logger.error(ex.getMessage()); } } finally { APITrace.end(); } } @Nonnull @Override public String request(@Nonnull AddressType typeOfAddress) throws InternalException, CloudException { if (typeOfAddress.equals(AddressType.PUBLIC)) { return request(IPVersion.IPV4); } else { throw new OperationNotSupportedException("GCE only supports creation of public IP Addresses"); } } @Nonnull @Override public String request(@Nonnull IPVersion version) throws InternalException, CloudException { APITrace.begin(getProvider(), "IpAddress.request"); try{ if(version.equals(IPVersion.IPV4)){ Compute gce = getProvider().getGoogleCompute(); try{ Address address = new Address(); address.setName("a" + UUID.randomUUID().toString()); Operation job = gce.addresses().insert(getContext().getAccountNumber(), getContext().getRegionId(), address).execute(); GoogleMethod method = new GoogleMethod(getProvider()); return method.getOperationTarget(getContext(), job, GoogleOperationType.REGION_OPERATION, getContext().getRegionId(), "", false); } catch (IOException ex) { logger.error(ex.getMessage()); if (ex.getClass() == GoogleJsonResponseException.class) { GoogleJsonResponseException gjre = (GoogleJsonResponseException)ex; throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(), gjre.getContent(), gjre.getDetails().getMessage()); } else throw new CloudException("An error occurred requesting an IPAddress: " + ex.getMessage()); } } else { throw new OperationNotSupportedException("GCE currently only supports IPv4"); } } finally { APITrace.end(); } } @Nonnull @Override public String requestForVLAN(@Nonnull IPVersion version) throws InternalException, CloudException { throw new OperationNotSupportedException("GCE does not support manual creation of IP Addresses for VLANs"); } @Nonnull @Override public String requestForVLAN(@Nonnull IPVersion version, @Nonnull String vlanId) throws InternalException, CloudException { throw new OperationNotSupportedException("GCE does not support manual creation of IP Addresses for VLANs"); } @Override public void stopForward(@Nonnull String ruleId) throws InternalException, CloudException { throw new OperationNotSupportedException("Forwarding rules are not supported by GCE"); } @Override public boolean supportsVLANAddresses(@Nonnull IPVersion ofVersion) throws InternalException, CloudException { return false; } private IpAddress toIpAddress(Address address){ IpAddress ipAddress = new IpAddress(); ipAddress.setIpAddressId(address.getName()); ipAddress.setAddress(address.getAddress()); ipAddress.setRegionId(address.getRegion().substring(address.getRegion().lastIndexOf("/") + 1)); ipAddress.setAddressType(AddressType.PUBLIC); ipAddress.setVersion(IPVersion.IPV4); ipAddress.setForVlan(false); if(address.getUsers() != null && address.getUsers().size() > 0){ for(String user : address.getUsers()){ user = user.substring(user.lastIndexOf("/") + 1); ipAddress.setServerId(user); } } return ipAddress; } private ResourceStatus toStatus(Address address){ return new ResourceStatus(address.getName(), address.getStatus().equals("RESERVED") ? false : true); } @Override public @Nonnull String[] mapServiceAction(@Nonnull ServiceAction action) { return new String[]{}; } @Override public Future<Iterable<IpAddress>> listIpPoolConcurrently(IPVersion version, boolean unassignedOnly) throws InternalException, CloudException { task = new ListIpPoolCallable(version, unassignedOnly); return threadPool.submit(task); } public class ListIpPoolCallable implements Callable<Iterable<IpAddress>> { IPVersion version; boolean unassignedOnly; public ListIpPoolCallable( IPVersion version, boolean unassignedOnly ) { this.version = version; this.unassignedOnly = unassignedOnly; } public Iterable<IpAddress> call() throws CloudException, InternalException { if (!version.equals(IPVersion.IPV4)) { return Collections.emptyList(); } ProviderContext ctx = getProvider().getContext(); if (ctx == null) { throw new CloudException("No context was set for this request"); } ArrayList<IpAddress> list = new ArrayList<IpAddress>(); Compute gce = getProvider().getGoogleCompute(); try { AddressList foo = gce.addresses().list(getProvider().getContext().getAccountNumber(), null).execute(); for (Address item : foo.getItems()) { IpAddress ipAddress = new IpAddress(); ipAddress.setAddress(item.getAddress()); ipAddress.setIpAddressId(item.getName()); // item.getId() ipAddress.setVersion(IPVersion.IPV4); ipAddress.setRegionId(item.getRegion()); list.add(ipAddress); } } catch ( IOException e ) { throw new OperationNotSupportedException("Problem obtaining results for listIpPoolConcurrently"); } return list; } } }