Merge branch 'master' of ssh://git.cloud.com/var/lib/git/cloudstack-oss

This commit is contained in:
Jessica Wang 2010-10-26 14:51:52 -07:00
commit 4a1b1fe5ce
13 changed files with 233 additions and 64 deletions

View File

@ -113,29 +113,29 @@ public class ConsoleProxyResource extends ServerResourceBase implements ServerRe
String certificate = cmd.getCertificate();
//write the cert to /etc/cloud/consoleproxy/cert/
String strDirectoy ="/etc/cloud/consoleproxy/cert/";
boolean dirCreated = (new File(strDirectoy)).mkdir();
if (dirCreated) {
s_logger.info("Directory: " + strDirectoy + " created");
//copy cert to the dir
try {
boolean dirCreated = false;
String strDirectoy = "/etc/cloud/consoleproxy/cert/";
dirCreated = (new File(strDirectoy)).mkdirs();
if (dirCreated)
{
if(s_logger.isDebugEnabled())
s_logger.info("Directory: " + strDirectoy + " created");
//copy cert to the dir
FileWriter fstream = new FileWriter("/etc/cloud/consoleproxy/cert/customcert");
BufferedWriter out = new BufferedWriter(fstream);
out.write(certificate);
//Close the output stream
out.close();
}catch (Exception e){
s_logger.warn("Unable to write file to /etc/cloud/consoleproxy/cert/ on console proxy", e);
}
success = true;
}
success = true;
return new Answer(cmd, success, "Cert string in the console proxy resource status:");
return new Answer(cmd, success, "Custom certificate update required status");
}catch (Exception e)
{
s_logger.error("Unable to read the cert string in console proxy resource");
s_logger.error("Unable to read the cert string in console proxy resource",e);
success = false;
}
return new Answer(cmd, success, "Cert string in the console proxy resource status:");
return new Answer(cmd, success, "Custom certificate response from the updatecertificate flow");
}
protected Answer execute(final CheckConsoleProxyLoadCommand cmd) {

View File

@ -135,7 +135,9 @@ public interface HostDao extends GenericDao<HostVO, Long> {
long getNextSequence(long hostId);
void loadDetails(HostVO host);
void loadDetails(HostVO host);
HostVO findConsoleProxyHost(String name, Type type);
}

View File

@ -79,6 +79,7 @@ public class HostDaoImpl extends GenericDaoBase<HostVO, Long> implements HostDao
protected final SearchBuilder<HostVO> UnmanagedDirectConnectSearch;
protected final SearchBuilder<HostVO> MaintenanceCountSearch;
protected final SearchBuilder<HostVO> ClusterSearch;
protected final SearchBuilder<HostVO> ConsoleProxyHostSearch;
protected final Attribute _statusAttr;
protected final Attribute _msIdAttr;
@ -154,6 +155,11 @@ public class HostDaoImpl extends GenericDaoBase<HostVO, Long> implements HostDao
ClusterSearch = createSearchBuilder();
ClusterSearch.and("cluster", ClusterSearch.entity().getClusterId(), SearchCriteria.Op.EQ);
ClusterSearch.done();
ConsoleProxyHostSearch = createSearchBuilder();
ConsoleProxyHostSearch.and("name", ConsoleProxyHostSearch.entity().getName(), SearchCriteria.Op.EQ);
ConsoleProxyHostSearch.and("type", ConsoleProxyHostSearch.entity().getType(), SearchCriteria.Op.EQ);
ConsoleProxyHostSearch.done();
PodSearch = createSearchBuilder();
PodSearch.and("pod", PodSearch.entity().getPodId(), SearchCriteria.Op.EQ);
@ -442,7 +448,20 @@ public class HostDaoImpl extends GenericDaoBase<HostVO, Long> implements HostDao
SearchCriteria<HostVO> sc = DcSearch.create("dc", dcId);
return listBy(sc);
}
@Override
public HostVO findConsoleProxyHost(String name, Type type) {
SearchCriteria<HostVO> sc = ConsoleProxyHostSearch.create();
sc.setParameters("name", name);
sc.setParameters("type", type);
List<HostVO>hostList = listBy(sc);
if(hostList==null || hostList.size() == 0)
return null;
else
return hostList.get(0);
}
public List<HostVO> listByHostPod(long podId) {
SearchCriteria<HostVO> sc = PodSearch.create("pod", podId);
return listBy(sc);

View File

@ -36,6 +36,7 @@ import com.cloud.configuration.ConfigurationManager;
import com.cloud.consoleproxy.ConsoleProxyManager;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.PermissionDeniedException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.network.DomainRouterService;
import com.cloud.network.NetworkManager;
import com.cloud.network.security.NetworkGroupManager;
@ -154,6 +155,8 @@ public class ApiDispatcher {
throw new ServerApiException(BaseCmd.PARAM_ERROR, cause.getMessage());
} else if (cause instanceof PermissionDeniedException) {
throw new ServerApiException(BaseCmd.ACCOUNT_ERROR, cause.getMessage());
} else if (cause instanceof ResourceAllocationException){
throw new ServerApiException(BaseCmd.UNSUPPORTED_ACTION_ERROR, cause.getMessage());
}
s_logger.warn("Exception executing method " + methodName + " for command " + cmd.getClass().getSimpleName(), ite);
throw new ServerApiException(BaseCmd.INTERNAL_ERROR, "Unable to execute method " + methodName + " for command " + cmd.getClass().getSimpleName() + ", internal error in the implementation.");

View File

@ -19,13 +19,15 @@ package com.cloud.api.commands;
import org.apache.log4j.Logger;
import com.cloud.api.BaseCmd;
import com.cloud.api.BaseAsyncCmd;
import com.cloud.api.Implementation;
import com.cloud.api.Parameter;
import com.cloud.api.response.StatusResponse;
import com.cloud.event.EventTypes;
import com.cloud.user.Account;
@Implementation(method="uploadCertificate")
public class UploadCustomCertificateCmd extends BaseCmd {
public class UploadCustomCertificateCmd extends BaseAsyncCmd {
public static final Logger s_logger = Logger.getLogger(UploadCustomCertificateCmd.class.getName());
private static final String s_name = "uploadcustomcertificateresponse";
@ -37,11 +39,7 @@ public class UploadCustomCertificateCmd extends BaseCmd {
return path;
}
@Override
public String getName() {
return s_name;
}
@Override @SuppressWarnings("unchecked")
public StatusResponse getResponse() {
Boolean status = (Boolean)getResponseObject();
@ -51,4 +49,29 @@ public class UploadCustomCertificateCmd extends BaseCmd {
response.setResponseName(getName());
return response;
}
@Override
public String getEventType() {
return EventTypes.EVENT_VOLUME_CREATE;
}
@Override
public String getEventDescription() {
return ("Uploading custom certificate to the db, and applying it to the cpvm");
}
@Override
public String getName() {
return s_name;
}
public static String getResultObjectName() {
return "volume";
}
@Override
public long getAccountId() {
return Account.ACCOUNT_ID_SYSTEM; // no account info given, parent this command to SYSTEM so ERROR events are tracked
}
}

View File

@ -34,6 +34,7 @@ import com.cloud.agent.api.ConsoleProxyLoadReportCommand;
import com.cloud.agent.api.GetVncPortAnswer;
import com.cloud.agent.api.GetVncPortCommand;
import com.cloud.agent.api.StartupCommand;
import com.cloud.agent.api.StartupProxyCommand;
import com.cloud.agent.api.StopCommand;
import com.cloud.api.ServerApiException;
import com.cloud.api.commands.DestroyConsoleProxyCmd;
@ -54,9 +55,10 @@ import com.cloud.utils.component.Inject;
import com.cloud.vm.ConsoleProxyVO;
import com.cloud.vm.UserVmVO;
import com.cloud.vm.VMInstanceVO;
import com.cloud.vm.VirtualMachine.Type;
import com.cloud.vm.VirtualMachineManager;
import com.cloud.vm.VirtualMachineName;
import com.cloud.vm.VirtualMachine.Type;
import com.cloud.vm.dao.ConsoleProxyDao;
import com.cloud.vm.dao.UserVmDao;
import com.cloud.vm.dao.VMInstanceDao;
@ -74,13 +76,13 @@ public class AgentBasedConsoleProxyManager implements ConsoleProxyManager, Virtu
@Inject
private VMInstanceDao _instanceDao;
private ConsoleProxyListener _listener;
protected int _consoleProxyUrlPort = ConsoleProxyManager.DEFAULT_PROXY_URL_PORT;
protected int _consoleProxyPort = ConsoleProxyManager.DEFAULT_PROXY_VNC_PORT;
protected boolean _sslEnabled = false;
@Inject
AgentManager _agentMgr;
@Inject
protected ConsoleProxyDao _cpDao;
public int getVncPort(VMInstanceVO vm) {
if (vm.getHostId() == null) {
return -1;
@ -324,5 +326,11 @@ public class AgentBasedConsoleProxyManager implements ConsoleProxyManager, Virtu
@Override
public boolean destroyConsoleProxy(DestroyConsoleProxyCmd cmd) throws ServerApiException {
return false;
}
}
@Override
public boolean applyCustomCertToNewProxy(StartupProxyCommand cmd) {
// TODO Auto-generated method stub
return false;
}
}

View File

@ -7,6 +7,7 @@ import com.cloud.agent.api.AgentControlAnswer;
import com.cloud.agent.api.ConsoleAccessAuthenticationCommand;
import com.cloud.agent.api.ConsoleProxyLoadReportCommand;
import com.cloud.agent.api.StartupCommand;
import com.cloud.agent.api.StartupProxyCommand;
import com.cloud.host.HostVO;
import com.cloud.host.Status;
@ -16,4 +17,5 @@ public interface AgentHook {
void onAgentConnect(HostVO host, StartupCommand cmd);
public void onAgentDisconnect(long agentId, Status state);
boolean applyCustomCertToNewProxy(StartupProxyCommand cmd);
}

View File

@ -25,6 +25,7 @@ import com.cloud.agent.api.Command;
import com.cloud.agent.api.ConsoleAccessAuthenticationCommand;
import com.cloud.agent.api.ConsoleProxyLoadReportCommand;
import com.cloud.agent.api.StartupCommand;
import com.cloud.agent.api.StartupProxyCommand;
import com.cloud.host.HostVO;
import com.cloud.host.Status;
@ -66,6 +67,10 @@ public class ConsoleProxyListener implements Listener {
@Override
public void processConnect(HostVO host, StartupCommand cmd) {
_proxyMgr.onAgentConnect(host, cmd);
if (cmd instanceof StartupProxyCommand) {
_proxyMgr.applyCustomCertToNewProxy((StartupProxyCommand)cmd);
}
}
@Override

View File

@ -56,9 +56,11 @@ import com.cloud.agent.api.Start2Command;
import com.cloud.agent.api.StartConsoleProxyAnswer;
import com.cloud.agent.api.StartConsoleProxyCommand;
import com.cloud.agent.api.StartupCommand;
import com.cloud.agent.api.StartupProxyCommand;
import com.cloud.agent.api.StopAnswer;
import com.cloud.agent.api.StopCommand;
import com.cloud.agent.api.proxy.ConsoleProxyLoadAnswer;
import com.cloud.agent.api.proxy.UpdateCertificateCommand;
import com.cloud.agent.api.to.NicTO;
import com.cloud.agent.api.to.VirtualMachineTO;
import com.cloud.agent.api.to.VirtualMachineTO.SshMonitor;
@ -70,13 +72,15 @@ import com.cloud.async.AsyncJobExecutor;
import com.cloud.async.AsyncJobManager;
import com.cloud.async.AsyncJobVO;
import com.cloud.async.BaseAsyncJobExecutor;
import com.cloud.certificate.CertificateVO;
import com.cloud.certificate.dao.CertificateDao;
import com.cloud.cluster.ClusterManager;
import com.cloud.configuration.Config;
import com.cloud.configuration.dao.ConfigurationDao;
import com.cloud.dc.DataCenterVO;
import com.cloud.dc.HostPodVO;
import com.cloud.dc.Vlan.VlanType;
import com.cloud.dc.VlanVO;
import com.cloud.dc.Vlan.VlanType;
import com.cloud.dc.dao.DataCenterDao;
import com.cloud.dc.dao.HostPodDao;
import com.cloud.dc.dao.VlanDao;
@ -97,8 +101,8 @@ import com.cloud.exception.ResourceUnavailableException;
import com.cloud.exception.StorageUnavailableException;
import com.cloud.ha.HighAvailabilityManager;
import com.cloud.host.Host;
import com.cloud.host.Host.Type;
import com.cloud.host.HostVO;
import com.cloud.host.Host.Type;
import com.cloud.host.dao.HostDao;
import com.cloud.info.ConsoleProxyConnectionInfo;
import com.cloud.info.ConsoleProxyInfo;
@ -109,10 +113,10 @@ import com.cloud.info.RunningHostInfoAgregator;
import com.cloud.info.RunningHostInfoAgregator.ZoneHostInfo;
import com.cloud.maid.StackMaid;
import com.cloud.network.IpAddrAllocator;
import com.cloud.network.IpAddrAllocator.networkInfo;
import com.cloud.network.Network.TrafficType;
import com.cloud.network.NetworkConfigurationVO;
import com.cloud.network.NetworkManager;
import com.cloud.network.IpAddrAllocator.networkInfo;
import com.cloud.network.Network.TrafficType;
import com.cloud.network.dao.IPAddressDao;
import com.cloud.offering.NetworkOffering;
import com.cloud.offerings.NetworkOfferingVO;
@ -124,9 +128,9 @@ import com.cloud.storage.GuestOSVO;
import com.cloud.storage.StorageManager;
import com.cloud.storage.StoragePoolVO;
import com.cloud.storage.VMTemplateHostVO;
import com.cloud.storage.VMTemplateStorageResourceAssoc.Status;
import com.cloud.storage.VMTemplateVO;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.VMTemplateStorageResourceAssoc.Status;
import com.cloud.storage.dao.GuestOSDao;
import com.cloud.storage.dao.VMTemplateDao;
import com.cloud.storage.dao.VMTemplateHostDao;
@ -155,12 +159,12 @@ import com.cloud.vm.NicProfile;
import com.cloud.vm.State;
import com.cloud.vm.VMInstanceVO;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.VirtualMachine.Event;
import com.cloud.vm.VirtualMachineGuru;
import com.cloud.vm.VirtualMachineManager;
import com.cloud.vm.VirtualMachineName;
import com.cloud.vm.VirtualMachineProfile;
import com.cloud.vm.VmManager;
import com.cloud.vm.VirtualMachine.Event;
import com.cloud.vm.dao.ConsoleProxyDao;
import com.cloud.vm.dao.VMInstanceDao;
import com.google.gson.Gson;
@ -229,12 +233,12 @@ public class ConsoleProxyManagerImpl implements ConsoleProxyManager, VirtualMach
private HostDao _hostDao;
@Inject
private ConfigurationDao _configDao;
@Inject
private CertificateDao _certDao;
@Inject
private VMInstanceDao _instanceDao;
@Inject
private AccountDao _accountDao;
@Inject private VMTemplateHostDao _vmTemplateHostDao;
@Inject private AgentManager _agentMgr;
@Inject private StorageManager _storageMgr;
@ -2432,4 +2436,57 @@ public class ConsoleProxyManagerImpl implements ConsoleProxyManager, VirtualMach
public boolean processDeploymentResult(Commands cmds, ConsoleProxyVO proxy, VirtualMachineProfile profile, DeployDestination dest) {
return true;
}
@Override
public boolean applyCustomCertToNewProxy(StartupProxyCommand cmd){
//this is the case for updating cust cert on each new starting proxy, if such cert exists
//get cert from db
List<CertificateVO> certList = _certDao.listAll();
if(certList.size()>0){
CertificateVO cert = certList.get(0);//there will only be 1 cert in db for now
String certStr = cert.getCertificate();
long proxyVmId = ((StartupProxyCommand)cmd).getProxyVmId();
ConsoleProxyVO consoleProxy = _consoleProxyDao.findById(proxyVmId);
//find corresponding host
if(consoleProxy!=null){
HostVO consoleProxyHost = _hostDao.findConsoleProxyHost(consoleProxy.getName(), Type.ConsoleProxy);
//now send a command to console proxy
UpdateCertificateCommand certCmd = new UpdateCertificateCommand(certStr);
try {
Answer updateCertAns = _agentMgr.send(consoleProxyHost.getId(), certCmd);
if(updateCertAns.getResult() == true)
{
//we have the cert copied over on cpvm
long eventId = saveScheduledEvent(User.UID_SYSTEM, Account.ACCOUNT_ID_SYSTEM, EventTypes.EVENT_PROXY_REBOOT, "rebooting console proxy with Id: "+consoleProxy.getId());
rebootProxy(consoleProxy.getId(), eventId);
//when cp reboots, the context will be reinit with the new cert
s_logger.info("Successfully rebooted console proxy resource after custom certificate application");
return true;
}
} catch (AgentUnavailableException e) {
s_logger.warn("Unable to send update certificate command to the console proxy resource", e);
return false;
} catch (OperationTimedoutException e) {
s_logger.warn("Unable to send update certificate command to the console proxy resource", e);
return false;
}
}
}else{
return false;//no cert
}
return false;
}
private Long saveScheduledEvent(Long userId, Long accountId, String type, String description)
{
EventVO event = new EventVO();
event.setUserId(userId);
event.setAccountId(accountId);
event.setType(type);
event.setState(EventState.Scheduled);
event.setDescription("Scheduled async job for "+description);
event = _eventDao.persist(event);
return event.getId();
}
}

View File

@ -1219,5 +1219,5 @@ public interface ManagementServer {
*/
String[] getHypervisors(ListHypervisorsCmd cmd);
boolean uploadCertificate(UploadCustomCertificateCmd cmd);
boolean uploadCertificate(UploadCustomCertificateCmd cmd) throws ResourceAllocationException;
}

View File

@ -26,6 +26,7 @@ import java.net.URLEncoder;
import java.net.UnknownHostException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
@ -148,6 +149,7 @@ import com.cloud.async.dao.AsyncJobDao;
import com.cloud.async.executor.ExtractJobResultObject;
import com.cloud.capacity.CapacityVO;
import com.cloud.capacity.dao.CapacityDao;
import com.cloud.certificate.CertificateVO;
import com.cloud.certificate.dao.CertificateDao;
import com.cloud.configuration.Config;
import com.cloud.configuration.ConfigurationManager;
@ -174,6 +176,7 @@ import com.cloud.dc.dao.PodVlanMapDao;
import com.cloud.dc.dao.VlanDao;
import com.cloud.domain.DomainVO;
import com.cloud.domain.dao.DomainDao;
import com.cloud.event.EventState;
import com.cloud.event.EventTypes;
import com.cloud.event.EventUtils;
import com.cloud.event.EventVO;
@ -6931,41 +6934,64 @@ public class ManagementServerImpl implements ManagementServer {
}
return version;
}
private Long saveScheduledEvent(Long userId, Long accountId, String type, String description)
{
EventVO event = new EventVO();
event.setUserId(userId);
event.setAccountId(accountId);
event.setType(type);
event.setState(EventState.Scheduled);
event.setDescription("Scheduled async job for "+description);
event = _eventDao.persist(event);
return event.getId();
}
@Override
public boolean uploadCertificate(UploadCustomCertificateCmd cmd) {
public boolean uploadCertificate(UploadCustomCertificateCmd cmd) throws ResourceAllocationException {
//limit no.of certs uploaded to 1
if(_certDao.listAll().size()>0){
throw new ResourceAllocationException("There is already a custom certificate in the db");
}
String certificatePath = cmd.getPath();
Long certVOId = _certDao.persistCustomCertToDb(certificatePath);//0 implies failure
if (certVOId!=null && certVOId!=0) {
//certficate uploaded to db successfully
if (certVOId!=null && certVOId!=0)
{
//certficate uploaded to db successfully
//get a list of all Console proxies from the cp table
List<ConsoleProxyVO> cpList = _consoleProxyDao.listAll();
//get a list of all hosts from host table
List<HostVO> hosts = _hostDao.listAll();
List<HostVO> consoleProxyList = new ArrayList<HostVO>();
//find the console proxies, and send the command to them
for(HostVO host : hosts) {
if(host.getType().equals(com.cloud.host.Host.Type.ConsoleProxy)){
consoleProxyList.add(host);
}
}
for(HostVO consoleProxy : consoleProxyList){
for(ConsoleProxyVO cp : cpList)
{
HostVO cpHost = _hostDao.findConsoleProxyHost(cp.getName(), com.cloud.host.Host.Type.ConsoleProxy);
//now send a command to each console proxy
UpdateCertificateCommand certCmd = new UpdateCertificateCommand(_certDao.findById(certVOId).getCertificate());
try {
Answer updateCertAns = _agentMgr.send(consoleProxy.getId(), certCmd);
Answer updateCertAns = _agentMgr.send(cpHost.getId(), certCmd);
if(updateCertAns.getResult() == true)
{
//we have the cert copied over on cpvm
long eventId = saveScheduledEvent(User.UID_SYSTEM, Account.ACCOUNT_ID_SYSTEM, EventTypes.EVENT_PROXY_REBOOT, "rebooting console proxy with Id: "+cp.getId());
_consoleProxyMgr.rebootProxy(cp.getId(), eventId);
//when cp reboots, the context will be reinit with the new cert
}
} catch (AgentUnavailableException e) {
s_logger.warn("Unable to send command to the console proxy resource", e);
s_logger.warn("Unable to send update certificate command to the console proxy resource", e);
} catch (OperationTimedoutException e) {
s_logger.warn("Unable to send command to the console proxy resource", e);
s_logger.warn("Unable to send update certificate command to the console proxy resource", e);
}
}
return true;
}
else
{
return false;
}
return false;
}
@Override

View File

@ -2448,6 +2448,30 @@ a:hover.search_button {
color:#333;
font-size:11px;
}
.grid_row_cell .error_text {
width:92%;
height:16px;
float:left;
margin:0 0 0 10px;
display:inline;
padding:0 0 0 2px;
border:1px solid #999;
background:#ffe5e5;
color:#333;
font-size:11px;
}
.errormsg {
width:80%;
height:auto;
float:left;
margin:3px 0 0 10px;
display:inline;
padding:0 0 0 2px;
color:#a90000;
font-size:11px;
font-weight:normal;
}
.grid_row_cell .select {
width:92%;

View File

@ -166,15 +166,15 @@
</div>
<div class="grid_rows even" id="create_port_forwarding_row">
<div class="grid_row_cell" style="width: 15%;">
<input id="public_port" class="text" style="width: 90%;" type="text" />
<input id="public_port" class="text" style="width: 70%;" type="text" />
<div id="public_port_errormsg" class="errormsg" style="display: none;">Error msg will appear here</div>
</div>
<div class="grid_row_cell" style="width: 15%;">
<input id="private_port" class="text" style="width: 90%;" type="text" />
<input id="private_port" class="text" style="width: 70%;" type="text" />
<div id="private_port_errormsg" class="errormsg" style="display: none;">Error msg will appear here</div>
</div>
<div class="grid_row_cell" style="width: 15%;">
<select class="select" id="protocol">
<select class="select" id="protocol" style="width:70%;">
<option value="TCP">TCP</option>
<option value="UDP">UDP</option>
</select>
@ -220,19 +220,19 @@
</div>
<div class="grid_rows even" id="create_load_balancer_row">
<div class="grid_row_cell" style="width: 25%;">
<input id="name" class="text" style="width: 90%;" type="text" />
<input id="name" class="text" style="width: 70%;" type="text" />
<div id="name_errormsg" class="errormsg" style="display: none;">Error msg will appear here</div>
</div>
<div class="grid_row_cell" style="width: 15%;">
<input id="public_port" class="text" style="width: 90%;" type="text" />
<input id="public_port" class="text" style="width: 70%;" type="text" />
<div id="public_port_errormsg" class="errormsg" style="display: none;">Error msg will appear here</div>
</div>
<div class="grid_row_cell" style="width: 15%;">
<input id="private_port" class="text" style="width: 90%;" type="text" />
<input id="private_port" class="text" style="width: 70%;" type="text" />
<div id="private_port_errormsg" class="errormsg" style="display: none;">Error msg will appear here</div>
</div>
<div class="grid_row_cell" style="width: 15%;">
<select id="algorithm_select" class="select" style="width: 90%;">
<select id="algorithm_select" class="select" style="width: 70%;">
<option value="roundrobin">roundrobin</option>
<option value="leastconn">leastconn</option>
<option value="source">source</option>