Updated Branches: refs/heads/master ed5697f3e -> 2d6369c82
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/2d6369c8/plugins/network-elements/stratosphere-ssp/src/com/cloud/network/guru/SspGuestNetworkGuru.java ---------------------------------------------------------------------- diff --git a/plugins/network-elements/stratosphere-ssp/src/com/cloud/network/guru/SspGuestNetworkGuru.java b/plugins/network-elements/stratosphere-ssp/src/com/cloud/network/guru/SspGuestNetworkGuru.java new file mode 100644 index 0000000..b504a4b --- /dev/null +++ b/plugins/network-elements/stratosphere-ssp/src/com/cloud/network/guru/SspGuestNetworkGuru.java @@ -0,0 +1,171 @@ +// 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.guru; + +import javax.ejb.Local; +import javax.inject.Inject; + +import org.apache.log4j.Logger; + +import com.cloud.dc.DataCenter.NetworkType; +import com.cloud.deploy.DeployDestination; +import com.cloud.exception.InsufficientAddressCapacityException; +import com.cloud.exception.InsufficientVirtualNetworkCapcityException; +import com.cloud.network.Network; +import com.cloud.network.NetworkMigrationResponder; +import com.cloud.network.NetworkProfile; +import com.cloud.network.PhysicalNetwork; +import com.cloud.network.PhysicalNetwork.IsolationMethod; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.element.SspElement; +import com.cloud.network.element.SspManager; +import com.cloud.offering.NetworkOffering; +import com.cloud.offerings.dao.NetworkOfferingServiceMapDao; +import com.cloud.vm.NicProfile; +import com.cloud.vm.ReservationContext; +import com.cloud.vm.ReservationContextImpl; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachineProfile; + +/** + * Stratosphere SDN Platform NetworkGuru + */ +@Local(value=NetworkGuru.class) +public class SspGuestNetworkGuru extends GuestNetworkGuru implements NetworkMigrationResponder { + private static final Logger s_logger = Logger.getLogger(SspGuestNetworkGuru.class); + + @Inject + SspManager _sspMgr; + @Inject + NetworkDao _networkDao; + @Inject + NetworkOfferingServiceMapDao _ntwkOfferingSrvcDao; + + public SspGuestNetworkGuru() { + super(); + _isolationMethods = new IsolationMethod[] { IsolationMethod.SSP }; + } + + @Override + protected boolean canHandle(NetworkOffering offering, + NetworkType networkType, PhysicalNetwork physicalNetwork) { + s_logger.trace("canHandle"); + + String setting = null; + if(physicalNetwork != null && physicalNetwork.getIsolationMethods().contains("SSP")){ + // Be careful, PhysicalNetwork#getIsolationMethods() returns List<String>, not List<IsolationMethod> + setting = "physicalnetwork setting"; + }else if(_ntwkOfferingSrvcDao.isProviderForNetworkOffering(offering.getId(), Network.Provider.getProvider(SspElement.s_SSP_NAME))){ + setting = "network offering setting"; + } + if(setting != null){ + if (networkType != NetworkType.Advanced){ + s_logger.info("SSP enebled by "+setting+" but not active because networkType was "+networkType); + }else if(!isMyTrafficType(offering.getTrafficType())){ + s_logger.info("SSP enabled by "+setting+" but not active because traffic type not Guest"); + }else if(offering.getGuestType() != Network.GuestType.Isolated){ + s_logger.info("SSP works for network isolatation."); + }else if(!_sspMgr.canHandle(physicalNetwork)){ + s_logger.info("SSP manager not ready"); + }else{ + return true; + } + }else{ + s_logger.debug("SSP not configured to be active"); + } + return false; + } + + /* (non-Javadoc) + * FYI: What is done in parent class is allocateVnet(vlan). + * Effective return object members are: cidr, broadcastUri, gateway, mode, physicalNetworkId + * The other members will be silently ignored. + * This method is called at DeployVMCmd#execute (running phase) - NetworkManagerImpl#prepare + * @see com.cloud.network.guru.GuestNetworkGuru#implement(com.cloud.network.Network, com.cloud.offering.NetworkOffering, com.cloud.deploy.DeployDestination, com.cloud.vm.ReservationContext) + */ + @Override + public Network implement(Network network, NetworkOffering offering, + DeployDestination dest, ReservationContext context) + throws InsufficientVirtualNetworkCapcityException { + s_logger.trace("implement "+network.toString()); + super.implement(network, offering, dest, context); + _sspMgr.createNetwork(network, offering, dest, context); + return network; + } + + + @Override + public void shutdown(NetworkProfile profile, NetworkOffering offering) { + s_logger.trace("shutdown "+profile.toString()); + _sspMgr.deleteNetwork(profile); + super.shutdown(profile, offering); + } + + @Override + public void reserve(NicProfile nic, Network network, + VirtualMachineProfile<? extends VirtualMachine> vm, + DeployDestination dest, ReservationContext context) + throws InsufficientVirtualNetworkCapcityException, + InsufficientAddressCapacityException { + super.reserve(nic, network, vm, dest, context); + _sspMgr.createNicEnv(network, nic, dest, context); + } + + @Override + public boolean release(NicProfile nic, + VirtualMachineProfile<? extends VirtualMachine> vm, + String reservationId) { + Network network = _networkDao.findById(nic.getNetworkId()); + _sspMgr.deleteNicEnv(network, nic, new ReservationContextImpl(reservationId, null, null)); + return super.release(nic, vm, reservationId); + } + + @Override + public void updateNicProfile(NicProfile profile, Network network) { + super.updateNicProfile(profile, network); + } + + @Override + public boolean prepareMigration(NicProfile nic, Network network, + VirtualMachineProfile<? extends VirtualMachine> vm, + DeployDestination dest, ReservationContext context) { + try { + reserve(nic, network, vm, dest, context); + } catch (InsufficientVirtualNetworkCapcityException e) { + s_logger.error("prepareForMigration failed", e); + return false; + } catch (InsufficientAddressCapacityException e) { + s_logger.error("prepareForMigration failed", e); + return false; + } + return true; + } + + @Override + public void rollbackMigration(NicProfile nic, Network network, + VirtualMachineProfile<? extends VirtualMachine> vm, + ReservationContext src, ReservationContext dst) { + release(nic, vm, dst.getReservationId()); + } + + @Override + public void commitMigration(NicProfile nic, Network network, + VirtualMachineProfile<? extends VirtualMachine> vm, + ReservationContext src, ReservationContext dst) { + release(nic, vm, src.getReservationId()); + } +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/2d6369c8/plugins/network-elements/stratosphere-ssp/sspmock/sspmock.py ---------------------------------------------------------------------- diff --git a/plugins/network-elements/stratosphere-ssp/sspmock/sspmock.py b/plugins/network-elements/stratosphere-ssp/sspmock/sspmock.py new file mode 100644 index 0000000..c764d25 --- /dev/null +++ b/plugins/network-elements/stratosphere-ssp/sspmock/sspmock.py @@ -0,0 +1,68 @@ +import json +import uuid +from flask import Flask +app = Flask(__name__) + +tenant_networks = [] +tenant_ports = [] + +@app.route("/ws.v1/login", methods=["POST",]) +def login(): + response.content_type = "application/json" + return "" + +@app.route("/ssp.v1/tenant-networks", methods=["POST",]) +def create_tenant_network(): + response.content_type = "application/json" + response.status = 201 + obj = request.json + obj["uuid"] = str(uuid.uuid1()) + tenant_networks.append(obj) + return json.dumps(obj) + +@app.route("/ssp.v1/tenant-networks/<tenant_net_uuid>", methods=["DELETE",]) +def delete_tenant_network(tenant_net_uuid): + for net in tenant_networks: + if net["uuid"] == tenant_net_uuid: + tenant_networks.remove(net) + response.status = 204 + return "" + response.status = 404 + return "" + +@app.route("/ssp.v1/tenant-ports", methods=["POST",]) +def create_tenant_port(): + response.content_type = "application/json" + response.status = 201 + obj = request.json + obj["uuid"] = str(uuid.uuid1()) + tenant_ports.append(obj) + return json.dumps(obj) + +@app.route("/ssp.v1/tenant-ports/<tenant_port_uuid>", methods=["DELETE",]) +def delete_tenant_port(tenant_port_uuid): + for port in tenant_ports: + if port["uuid"] == tenant_port_uuid: + tenant_ports.remove(port) + response.status = 204 + return "" + response.status = 404 + return "" + +@app.route("/ssp.v1/tenant-ports/<tenant_port_uuid>", methods=["PUT",]) +def update_tenant_port(tenant_port_uuid): + response.content_type = "application/json" + for port in tenant_ports: + if port["uuid"] == tenant_port_uuid: + obj = request.json + obj["uuid"] = tenant_port_uuid + obj["vlan_id"] = 100 + tenant_ports.remove(port) + tenant_ports.append(obj) + response.status = 200 + return json.dumps(obj) + response.status = 404 + return "" + +if __name__=="__main__": + app.run(host="0.0.0.0", port=9080, debug=True) http://git-wip-us.apache.org/repos/asf/cloudstack/blob/2d6369c8/plugins/network-elements/stratosphere-ssp/test/com/cloud/network/element/SspClientTest.java ---------------------------------------------------------------------- diff --git a/plugins/network-elements/stratosphere-ssp/test/com/cloud/network/element/SspClientTest.java b/plugins/network-elements/stratosphere-ssp/test/com/cloud/network/element/SspClientTest.java new file mode 100644 index 0000000..4a2b352 --- /dev/null +++ b/plugins/network-elements/stratosphere-ssp/test/com/cloud/network/element/SspClientTest.java @@ -0,0 +1,92 @@ +// 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.element; + +import java.util.UUID; + +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.httpclient.URI; +import org.apache.commons.httpclient.methods.DeleteMethod; +import org.apache.commons.httpclient.methods.PostMethod; +import org.apache.commons.httpclient.methods.PutMethod; +import org.junit.Test; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +public class SspClientTest { + HttpClient _client = mock(HttpClient.class); + PostMethod _postMethod = mock(PostMethod.class); + PutMethod _putMethod = mock(PutMethod.class); + DeleteMethod _deleteMethod = mock(DeleteMethod.class); + + String uuid = UUID.randomUUID().toString(); + + String apiUrl = "http://a.example.jp/"; + String username = "foo"; + String password = "bar"; + SspClient sspClient = new SspClient(apiUrl, username, password){ + { + client = _client; + postMethod = _postMethod; + putMethod = _putMethod; + deleteMethod = _deleteMethod; + } + }; + + @SuppressWarnings("deprecation") + private URI getUri() throws Exception{ + return new URI(apiUrl); + } + + @Test + public void loginTest() throws Exception { + when(_postMethod.getURI()).thenReturn(getUri()); + when(_postMethod.getStatusCode()).thenReturn(HttpStatus.SC_OK); + assertTrue(sspClient.login()); + assertTrue(sspClient.login()); + assertTrue(sspClient.login()); + } + + @Test + public void createNetworkTest() throws Exception { + String networkName = "example network 1"; + String tenant_net_uuid = UUID.randomUUID().toString(); + + when(_postMethod.getURI()).thenReturn(getUri()); + when(_postMethod.getStatusCode()).thenReturn(HttpStatus.SC_CREATED); + when(_postMethod.getResponseBodyAsString()).thenReturn( + "{\"uuid\":\""+tenant_net_uuid+ + "\",\"name\":\""+networkName+ + "\",\"tenant_uuid\":\""+uuid+"\"}"); + SspClient.TenantNetwork tnet = sspClient.createTenantNetwork(uuid, networkName); + assertEquals(tnet.name, networkName); + assertEquals(tnet.uuid, tenant_net_uuid); + assertEquals(tnet.tenantUuid, uuid); + } + + @Test + public void deleteNetworkTest() throws Exception { + String tenant_net_uuid = UUID.randomUUID().toString(); + + when(_deleteMethod.getURI()).thenReturn(getUri()); + when(_deleteMethod.getStatusCode()).thenReturn(HttpStatus.SC_NO_CONTENT); + + sspClient.deleteTenantNetwork(tenant_net_uuid); + } +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/2d6369c8/plugins/network-elements/stratosphere-ssp/test/com/cloud/network/element/SspElementTest.java ---------------------------------------------------------------------- diff --git a/plugins/network-elements/stratosphere-ssp/test/com/cloud/network/element/SspElementTest.java b/plugins/network-elements/stratosphere-ssp/test/com/cloud/network/element/SspElementTest.java new file mode 100644 index 0000000..0020e20 --- /dev/null +++ b/plugins/network-elements/stratosphere-ssp/test/com/cloud/network/element/SspElementTest.java @@ -0,0 +1,152 @@ +// 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.element; + +import java.util.Arrays; +import java.util.HashMap; + +import org.junit.Before; +import org.junit.Test; + +import com.cloud.configuration.dao.ConfigurationDao; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.host.Host; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.network.Network; +import com.cloud.network.NetworkManager; +import com.cloud.network.NetworkModel; +import com.cloud.network.PhysicalNetworkServiceProvider; +import com.cloud.network.dao.NetworkServiceMapDao; +import com.cloud.network.dao.PhysicalNetworkDao; +import com.cloud.network.dao.PhysicalNetworkServiceProviderDao; +import com.cloud.network.dao.PhysicalNetworkServiceProviderVO; +import com.cloud.network.dao.PhysicalNetworkVO; +import com.cloud.network.dao.SspCredentialDao; +import com.cloud.network.dao.SspCredentialVO; +import com.cloud.network.dao.SspTenantDao; +import com.cloud.network.dao.SspUuidDao; +import com.cloud.resource.ResourceManager; +import com.cloud.vm.dao.NicDao; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.*; + +public class SspElementTest { + SspElement _element = new SspElement(); + + @Before + public void setUp(){ + _element._configDao = mock(ConfigurationDao.class); + _element._dcDao = mock(DataCenterDao.class); + _element._hostDao = mock(HostDao.class); + _element._networkMgr = mock(NetworkManager.class); + _element._networkModel = mock(NetworkModel.class); + _element._nicDao = mock(NicDao.class); + _element._physicalNetworkDao = mock(PhysicalNetworkDao.class); + _element._physicalNetworkServiceProviderDao = mock(PhysicalNetworkServiceProviderDao.class); + _element._resourceMgr = mock(ResourceManager.class); + _element._ntwkSrvcDao = mock(NetworkServiceMapDao.class); + + _element._sspCredentialDao = mock(SspCredentialDao.class); + _element._sspTenantDao = mock(SspTenantDao.class); + _element._sspUuidDao = mock(SspUuidDao.class); + } + + Long dataCenterId = new Long(21); + Long physicalNetworkId = new Long(22); + Long networkId = new Long(23); + PhysicalNetworkVO psvo = mock(PhysicalNetworkVO.class); + PhysicalNetworkServiceProviderVO nspvo = mock(PhysicalNetworkServiceProviderVO.class); + SspCredentialVO credential = mock(SspCredentialVO.class); + HostVO host = mock(HostVO.class); + + public void fullyConfigured(){ + // when physicalNetworkServiceProvider is configured + when(psvo.getId()).thenReturn(physicalNetworkId); + when(psvo.getDataCenterId()).thenReturn(dataCenterId); + + when(_element._physicalNetworkDao.findById(physicalNetworkId)).thenReturn(psvo); + + when(nspvo.getState()).thenReturn(PhysicalNetworkServiceProvider.State.Enabled); + when(nspvo.getPhysicalNetworkId()).thenReturn(physicalNetworkId); + + when(_element._physicalNetworkServiceProviderDao.findByServiceProvider(physicalNetworkId, "StratosphereSsp")).thenReturn(nspvo); + + // and zone api server, credentail is configured + when(credential.getUsername()).thenReturn("foo"); + when(credential.getPassword()).thenReturn("bar"); + + when(_element._sspCredentialDao.findByZone(dataCenterId.longValue())).thenReturn(credential); + + HashMap<String,String> details = new HashMap<String, String>(); + details.put("sspHost", "v1Api"); + details.put("url", "http://a.example.jp/"); + + when(host.getDataCenterId()).thenReturn(dataCenterId); + when(host.getDetails()).thenReturn(details); + when(host.getDetail("sspHost")).thenReturn(details.get("sspHost")); + when(host.getDetail("url")).thenReturn(details.get("url")); + + when(_element._resourceMgr.listAllHostsInOneZoneByType(Host.Type.L2Networking, dataCenterId)).thenReturn(Arrays.<HostVO>asList(host)); + + when(_element._ntwkSrvcDao.canProviderSupportServiceInNetwork(networkId, Network.Service.Connectivity, _element.getProvider())).thenReturn(true); + } + + @Test + public void isReadyTest(){ + fullyConfigured(); + + // isReady is called in changing the networkserviceprovider state to Enabled. + when(nspvo.getState()).thenReturn(PhysicalNetworkServiceProvider.State.Disabled); + + // ssp is ready + assertTrue(_element.isReady(nspvo)); + + // If you don't call addstratospheressp api, ssp won't be ready + when(_element._sspCredentialDao.findByZone(dataCenterId.longValue())).thenReturn(null); + when(_element._resourceMgr.listAllHostsInOneZoneByType(Host.Type.L2Networking, dataCenterId)).thenReturn(Arrays.<HostVO>asList()); + assertFalse(_element.isReady(nspvo)); + } + + @Test + public void canHandleTest() { + fullyConfigured(); + + // ssp is active + assertTrue(_element.canHandle(psvo)); + + // You can disable ssp temporary by truning the state disabled + when(nspvo.getState()).thenReturn(PhysicalNetworkServiceProvider.State.Disabled); + assertFalse(_element.canHandle(psvo)); + + // If you don't want ssp for a specific physicalnetwork, you don't need to + // setup physicalNetworkProvider. + when(_element._physicalNetworkServiceProviderDao.findByServiceProvider(physicalNetworkId, "StratosphereSsp")).thenReturn(null); + assertFalse(_element.canHandle(psvo)); + + // restore... + when(nspvo.getState()).thenReturn(PhysicalNetworkServiceProvider.State.Enabled); + when(_element._physicalNetworkServiceProviderDao.findByServiceProvider(physicalNetworkId, "StratosphereSsp")).thenReturn(nspvo); + + // If you don't call addstratospheressp api, ssp won't be active + when(_element._sspCredentialDao.findByZone(dataCenterId.longValue())).thenReturn(null); + when(_element._resourceMgr.listAllHostsInOneZoneByType(Host.Type.L2Networking, dataCenterId)).thenReturn(Arrays.<HostVO>asList()); + assertFalse(_element.canHandle(psvo)); + } +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/2d6369c8/plugins/pom.xml ---------------------------------------------------------------------- diff --git a/plugins/pom.xml b/plugins/pom.xml index d8ae97a..9ad56c6 100755 --- a/plugins/pom.xml +++ b/plugins/pom.xml @@ -54,6 +54,7 @@ <module>network-elements/nicira-nvp</module> <module>network-elements/bigswitch-vns</module> <module>network-elements/midonet</module> + <module>network-elements/stratosphere-ssp</module> <module>storage-allocators/random</module> <module>user-authenticators/ldap</module> <module>user-authenticators/md5</module> http://git-wip-us.apache.org/repos/asf/cloudstack/blob/2d6369c8/setup/db/db/schema-410to420.sql ---------------------------------------------------------------------- diff --git a/setup/db/db/schema-410to420.sql b/setup/db/db/schema-410to420.sql index 325924b..d2add32 100644 --- a/setup/db/db/schema-410to420.sql +++ b/setup/db/db/schema-410to420.sql @@ -2148,3 +2148,25 @@ ALTER TABLE `cloud`.`baremetal_pxe_devices` ADD CONSTRAINT `fk_external_pxe_devi ALTER TABLE `cloud`.`baremetal_pxe_devices` ADD CONSTRAINT `fk_external_pxe_devices_physical_network_id` FOREIGN KEY (`physical_network_id`) REFERENCES `physical_network`(`id`) ON DELETE CASCADE; alter table `cloud`.`network_offerings` add column egress_default_policy boolean default false; + +-- Add stratospher ssp tables +CREATE TABLE `cloud`.`external_stratosphere_ssp_uuids` ( + `id` bigint(20) UNSIGNED PRIMARY KEY AUTO_INCREMENT, + `uuid` varchar(255) NOT NULL COMMENT "uuid provided by SSP", + `obj_class` varchar(255) NOT NULL, + `obj_id` bigint(20) NOT NULL, + `reservation_id` varchar(255) +) Engine=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE `cloud`.`external_stratosphere_ssp_tenants` ( + `id` bigint(20) UNSIGNED PRIMARY KEY AUTO_INCREMENT, + `uuid` varchar(255) NOT NULL COMMENT "SSP tenant uuid", + `zone_id` bigint(20) NOT NULL COMMENT "cloudstack zone_id" +) Engine=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE `cloud`.`external_stratosphere_ssp_credentials` ( + `id` bigint(20) UNSIGNED PRIMARY KEY AUTO_INCREMENT, + `data_center_id` bigint(20) unsigned NOT NULL, + `username` varchar(255) NOT NULL, + `password` varchar(255) NOT NULL +) Engine=InnoDB DEFAULT CHARSET=utf8; http://git-wip-us.apache.org/repos/asf/cloudstack/blob/2d6369c8/ui/scripts/ui-custom/zoneWizard.js ---------------------------------------------------------------------- diff --git a/ui/scripts/ui-custom/zoneWizard.js b/ui/scripts/ui-custom/zoneWizard.js index 718b454..016a91e 100644 --- a/ui/scripts/ui-custom/zoneWizard.js +++ b/ui/scripts/ui-custom/zoneWizard.js @@ -620,14 +620,17 @@ }).html('VLAN'), $('<option>').attr({ value: 'GRE' - }).html('GRE'), + }).html('GRE'), $('<option>').attr({ value: 'STT' }).html('STT'), $('<option>').attr({ value: 'VNS' - }).html('VNS') + }).html('VNS'), + $('<option>').attr({ + value: 'SSP' + }).html('SSP') ) ) );