성능 테스트 설계서 - SeungpilPark/uEngine-bill GitHub Wiki
package org.uengine.garuda.bill.kbapi;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.util.EntityUtils;
import org.uengine.garuda.util.HttpUtils;
import org.uengine.garuda.util.JsonUtils;
import sun.misc.BASE64Encoder;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* Created by uengine on 2016. 6. 16..
*/
public class KillbillApi {
private HttpUtils httpUtils;
private String host;
private int port;
private String user;
private String password;
private String apiKey;
private String apiSecret;
public HttpUtils getHttpUtils() {
return httpUtils;
}
public void setHttpUtils(HttpUtils httpUtils) {
this.httpUtils = httpUtils;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getApiKey() {
return apiKey;
}
public void setApiKey(String apiKey) {
this.apiKey = apiKey;
}
public String getApiSecret() {
return apiSecret;
}
public void setApiSecret(String apiSecret) {
this.apiSecret = apiSecret;
}
public KillbillApi(String host, int port, String user, String password, String apiKey, String apiSecret) {
this.host = host;
this.port = port;
this.user = user;
this.password = password;
this.apiKey = apiKey;
this.apiSecret = apiSecret;
this.httpUtils = new HttpUtils();
}
public void createTenant(String externalKey) {
String method = "POST";
String path = "/1.0/kb/tenants";
Map params = new HashMap();
params.put("externalKey", externalKey);
params.put("apiKey", externalKey);
params.put("apiSecret", externalKey);
Map headers = new HashMap();
try {
this.apiRequest(method, path, JsonUtils.marshal(params), headers);
} catch (IOException ex) {
ex.printStackTrace();
}
}
public Map getTenantByApiKey(String apiKey) {
String method = "GET";
String path = "/1.0/kb/tenants";
Map params = new HashMap();
params.put("apiKey", apiKey);
String getQueryString = HttpUtils.createGETQueryString(params);
Map headers = new HashMap();
try {
HttpResponse httpResponse = this.apiRequest(method, path + getQueryString, null, headers);
if (httpResponse.getStatusLine().getStatusCode() == 200) {
String s = EntityUtils.toString(httpResponse.getEntity());
return JsonUtils.unmarshal(s);
} else {
return null;
}
} catch (IOException ex) {
ex.printStackTrace();
return null;
}
}
public void createAccount(String name, String email, String currency, int billCycleDayLocal, String externalKey) {
String method = "POST";
String path = "/1.0/kb/accounts";
Map params = new HashMap();
params.put("externalKey", externalKey);
params.put("currency", currency);
params.put("name", name);
params.put("email", email);
params.put("billCycleDayLocal", billCycleDayLocal);
Map headers = new HashMap();
try {
this.apiRequest(method, path, JsonUtils.marshal(params), headers);
} catch (IOException ex) {
ex.printStackTrace();
}
}
public void updateCatalog(String catalogXml) {
String method = "POST";
String path = "/1.0/kb/catalog";
Map headers = new HashMap();
headers.put("Content-Type", "application/xml");
try {
this.apiRequest(method, path, catalogXml, headers);
} catch (IOException ex) {
ex.printStackTrace();
}
}
public void createSubscription(String accountId, String planName) {
String method = "POST";
String path = "/1.0/kb/subscriptions";
Map params = new HashMap();
params.put("accountId", accountId);
params.put("planName", planName);
Map headers = new HashMap();
try {
this.apiRequest(method, path, JsonUtils.marshal(params), headers);
} catch (IOException ex) {
ex.printStackTrace();
}
}
public Map getUser(String externalKey){
String method = "GET";
String path = "/1.0/kb/accounts";
Map params = new HashMap();
params.put("externalKey", externalKey);
String getQueryString = HttpUtils.createGETQueryString(params);
Map headers = new HashMap();
try {
HttpResponse httpResponse = this.apiRequest(method, path + getQueryString, null, headers);
if (httpResponse.getStatusLine().getStatusCode() == 200) {
String s = EntityUtils.toString(httpResponse.getEntity());
return JsonUtils.unmarshal(s);
} else {
return null;
}
} catch (IOException ex) {
ex.printStackTrace();
return null;
}
}
private HttpResponse apiRequest(String method, String path, String data, Map headers) throws IOException {
String auth = this.user + ":" + this.password;
BASE64Encoder encoder = new BASE64Encoder();
String encode = encoder.encode(auth.getBytes());
Map mergeHeaders = new HashMap();
mergeHeaders.put("Authorization", "Basic " + encode);
mergeHeaders.put("Content-Type", "application/json");
mergeHeaders.put("Accept", "application/json");
mergeHeaders.put("X-Killbill-CreatedBy", "OpenBill");
mergeHeaders.put("X-Killbill-ApiKey", this.apiKey);
mergeHeaders.put("X-Killbill-ApiSecret", this.apiSecret);
mergeHeaders.putAll(headers);
String url = "http://" + this.host + ":" + this.port + path;
HttpResponse httpResponse = httpUtils.makeRequest(method, url, data, mergeHeaders);
return httpResponse;
}
}
대용량 카달로그 업데이트시 소요시간을 측정한다.
대용량 카달로그 생성 코드
package org.uengine.garuda.test;
import org.joda.time.DateTime;
import org.killbill.billing.catalog.*;
import org.killbill.billing.catalog.api.*;
import org.killbill.billing.catalog.api.Currency;
import org.killbill.xmlloader.ValidatingConfig;
import org.killbill.xmlloader.XMLLoader;
import org.killbill.xmlloader.XMLSchemaGenerator;
import org.springframework.util.FileCopyUtils;
import org.uengine.garuda.bill.kbapi.KillbillApi;
import org.xml.sax.SAXException;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.transform.TransformerException;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.math.BigDecimal;
import java.net.URI;
import java.util.*;
/**
* Created by uengine on 2016. 12. 13..
*/
public class CatalogGenerateTest {
public static Marshaller marshaller(final Class<?> clazz) throws JAXBException, SAXException, IOException, TransformerException {
final JAXBContext context = JAXBContext.newInstance(clazz);
final SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
final Marshaller um = context.createMarshaller();
final Schema schema = factory.newSchema(new StreamSource(XMLSchemaGenerator.xmlSchema(clazz)));
um.setSchema(schema);
return um;
}
/**
* 한화 1만원, 달러 10달러 프라이스 리스트
*
* @return
*/
public static DefaultInternationalPrice internationalPrice() {
DefaultInternationalPrice internationalPrice = new DefaultInternationalPrice();
DefaultPrice price1 = new DefaultPrice();
price1.setCurrency(Currency.USD);
price1.setValue(new BigDecimal(10));
DefaultPrice price2 = new DefaultPrice();
price2.setCurrency(Currency.KRW);
price2.setValue(new BigDecimal(10000));
DefaultPrice[] prices = new DefaultPrice[]{price1, price2};
internationalPrice.setPrices(prices);
return internationalPrice;
}
public static void main(String[] args) throws Exception {
String user = "admin";
String password = "password";
String apiKey = "forcs";
String apiSecret = "forcs";
String host = "localhost";
int port = 8080;
//포시에스 테넌트 가져오기
KillbillApi api = new KillbillApi(host, port, user, password, apiKey, apiSecret);
Map tenant = api.getTenantByApiKey("forcs");
//포시에스 테넌트에 카달로그 생성(기본 상품 4개 보유 + 서드파티 상품 1만개 등록)
XMLLoader xmlLoader = new XMLLoader();
StandaloneCatalog catalog = xmlLoader.getObjectFromUri(new URI("catalog/FORCS.xml"), StandaloneCatalog.class);
catalog.getCatalogName();
Collection<Product> currentProducts = catalog.getCurrentProducts();
Collection<Plan> currentPlans = catalog.getCurrentPlans();
List<Product> currentProductsList = new ArrayList<Product>(currentProducts);
List<Plan> currentPlansList = new ArrayList<Plan>(currentPlans);
DefaultPriceListSet priceLists = catalog.getPriceLists();
DefaultPriceList defaultPricelist = priceLists.getDefaultPricelist();
Collection<Plan> plans = defaultPricelist.getPlans();
List<Plan> plansList = new ArrayList<Plan>(plans);
String subProductName = "ADD_ON_";
int subProductCount = 20000;
for (int i = 0; i < subProductCount; i++) {
//신규 프로덕트 이름
String productName = subProductName + i;
//신규 프로덕트
Product product = new DefaultProduct(productName, ProductCategory.STANDALONE);
//신규 플랜
String planName = subProductName + i + "_PLAN";
//finalPhase 를 지정한다.
DefaultPlanPhase finalPhase = new DefaultPlanPhase();
//finalPhase 의 phaseType
finalPhase.setPhaseType(PhaseType.EVERGREEN);
//finalPhase 의 Duration
finalPhase.setDuration(new DefaultDuration().setUnit(TimeUnit.UNLIMITED));
//finalPhase 의 recurring
DefaultRecurring recurring = new DefaultRecurring();
recurring.setBillingPeriod(BillingPeriod.MONTHLY);
recurring.setRecurringPrice(internationalPrice());
finalPhase.setRecurring(recurring);
//플랜 생성
DefaultPlan defaultPlan = new DefaultPlan();
defaultPlan.setProduct(product);
defaultPlan.setFinalPhase(finalPhase);
defaultPlan.setName(planName);
//생성한 프로덕트, 플랜, 디폴트 프라이스 리스트 등록
currentProductsList.add(product);
currentPlansList.add(defaultPlan);
plansList.add(new DefaultPlan().setName(planName));
}
//카탈로그에 프로덕트 리스트, 플랜리스트, 프라이스 리스트 오버라이드
currentProducts = new HashSet<Product>(currentProductsList);
currentPlans = new HashSet<Plan>(currentPlansList);
defaultPricelist.setPlans(new HashSet<Plan>(plansList));
catalog.setProducts(currentProducts);
catalog.setPlans(currentPlans);
//xml 변환
Marshaller m = marshaller(StandaloneCatalog.class);
File file = new File("/Users/uengine/IdeaProjects/OpenBill/openbill-test/src/main/resources/catalog/override.xml");
m.marshal(catalog, file);
//킬빌에 xml 전송
String catalogXml = FileCopyUtils.copyToString(new FileReader(file));
api.updateCatalog(catalogXml);
//포시에스 테넌트에 유저(구매자) 100만명 등록
}
}
데일리마다 한번씩 대용량 카달로그가 업데이트 된다는 가정하에, 카달로그 버젼을 업데이트 해가며 서브스크립션 차지 계산 및 인보이스 생성 능력을 테스트함.
서브스크립션 생성 코드
package org.uengine.garuda.test;
import org.uengine.garuda.bill.kbapi.KillbillApi;
import java.util.Map;
import java.util.Random;
/**
* Created by uengine on 2016. 12. 13..
*/
public class SubscriptionGenerateTest {
public static void main(String[] args) throws Exception {
//서브스크립션 1만유저 * 2개 = 2만개 생성
//1명당 기본 1개 + 추가상품중 하나 구독.
//기본은 basic-monthly, 추가상품은 ADD_ON_랜덤숫자_PLAN
int userNumbers = 100;
int thredsNumbers = 50;
for (int i = 40; i < thredsNumbers; i++) {
int startCount = i * userNumbers + 1;
int endCount = startCount + userNumbers;
CreateSubscription test = new CreateSubscription(startCount, endCount);
test.start();
}
}
public static class CreateSubscription extends Thread {
int startCount;
int endCount;
public CreateSubscription(int startCount, int endCount) {
this.startCount = startCount;
this.endCount = endCount;
}
public void run() {
String user = "admin";
String password = "password";
String apiKey = "forcs";
String apiSecret = "forcs";
String host = "localhost";
int port = 8080;
int max = 20000;
Random rand = new Random();
int addonIndex = rand.nextInt(max);
KillbillApi api = new KillbillApi(host, port, user, password, apiKey, apiSecret);
for (int c = startCount; c < endCount; c++) {
String userName = "test-user-" + c;
String externalKey = userName;
Map userMap = api.getUser(externalKey);
if(userMap != null && userMap.containsKey("accountId")){
String accountId = (String) userMap.get("accountId");
String planName = "ADD_ON_" + addonIndex + "_PLAN";
api.createSubscription(accountId, planName);
String basicPlanName = "basic-monthly";
api.createSubscription(accountId, basicPlanName);
}
}
}
}
}
사용자 생성 api 호출을 다음과 같이 돌렸을 때 성능을 측정한다.
사용자 생성 코드
package org.uengine.garuda.test;
import org.killbill.billing.catalog.*;
import org.killbill.billing.catalog.api.*;
import org.killbill.billing.catalog.api.Currency;
import org.killbill.xmlloader.XMLLoader;
import org.killbill.xmlloader.XMLSchemaGenerator;
import org.springframework.util.FileCopyUtils;
import org.uengine.garuda.bill.kbapi.KillbillApi;
import org.xml.sax.SAXException;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.transform.TransformerException;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.math.BigDecimal;
import java.net.URI;
import java.util.*;
/**
* Created by uengine on 2016. 12. 13..
*/
public class AccountGenerateTest {
public static void main(String[] args) throws Exception {
LogOff.logOff();
//포시에스 테넌트에 유저(구매자) 1만명 등록
//100명씩 100개 쓰레드로 접속.
int userNumbers = 100;
int thredsNumbers = 100;
for (int i = 80; i < thredsNumbers; i++) {
int startCount = i * userNumbers + 1;
int endCount = startCount + userNumbers;
CreateAcount test = new CreateAcount(startCount, endCount);
test.start();
}
}
public static class CreateAcount extends Thread {
int startCount;
int endCount;
public CreateAcount(int startCount, int endCount) {
this.startCount = startCount;
this.endCount = endCount;
}
public void run() {
String user = "admin";
String password = "password";
String apiKey = "forcs";
String apiSecret = "forcs";
String host = "localhost";
int port = 8080;
KillbillApi api = new KillbillApi(host, port, user, password, apiKey, apiSecret);
for (int c = startCount; c < endCount; c++) {
String name = "test-user-" + c;
String email = name + "@gmail.com";
String currency = "USD";
String externalKey = name;
int billCycleDayLocal = 5;
api.createAccount(name, email, currency, billCycleDayLocal, externalKey);
}
}
}
}