본 문서(node.js API 서비스 미터링 애플리케이션 개발 가이드)는 파스-타 플랫폼 프로젝트의 미터링 플러그인과 Node.js API 애플리케이션을 연동시켜 API 서비스를 미터링하는 방법에 대해 기술 하였다.
1.2 범위
본 문서의 범위는 파스-타 플랫폼 프로젝트의 Node.js API 서비스 애플리케이션 개발과 CF-Abacus 연동에 대한 내용으로 한정되어 있다.
1.3 참고자료
2. Node.js API 미터링 개발가이드
2.1 개요
API 서비스 및 해당 API 서비스를 사용하는 애플리케이션을 Node.js 언어로 작성 한다. API 서비스를 사용하는 애플리케이션과 API 서비스를 바인딩하고 해당 애플리케이션에 바인딩된 환경정보(VCAP_SERVICES)를 이용해 각 서비스별 접속정보를 획득하여 애플리케이션에 적용하여 API 서비스를 호출하는 애플리케이션을 작성 한다. 또한 API 서비스는 서비스 요청을 처리함과 동시에 API 사용 내역을 CF-ABACUS에 전송하는 애플리케이션을 작성 한다.
기능
설명
Runtime
미터링/등급/과금 정책
서비스 브로커 API
서비스 API
서비스 제공자가 제공하는 API 서비스 기능 및 API 사용량을 CF-ABACUS에 전송하는 기능으로 구성되었다.
대시보드
서비스를 제공하기 위한 인증, 서비스 모니터링 등을 위한 대시보드 기능으로 서비스 제공자가 개발해야 한다.
CF-ABACUS
※ 본 개발 가이드는 API**서비스** 개발에 대해서만 기술하며, 다른 컴포넌트의 개발 또는 설치에 대해서 링크한 사이트를 참조한다.
2.2 개발환경 구성
Node.js 애플리케이션 개발을 위해 다음과 같은 환경으로 개발환경을 구성 한다.
CF release: v226 이상
nodejs_buildpack: nodejs_buildpack-cached-v1.5.18.zip 이상
Node.js: v5.11.1
npm: v3.8.6
2.2.1 Node.js 및 npm 설치
1. Node.js 및 npm 설치
$ sudo apt-get install curl
## Node.js version 6.x를 설치할 경우
## Source Repository 등록
$ curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash –
## Node.js & Npm 설치
$ sudo apt-get install -y nodejs
## Node.js & Nmp 설치 확인
$ node -v
$ npm -v
※ 개발도구
Node.js는 javascript기반의 언어로 Notepad++, Sublim Text, EditPlus등 문서편집기를 개발도구로 사용할 수 있다. 또한 Eclipse의 플러그인 Nodeclipse를 설치하여 사용할 수 있다. 그리고 상용 개발 도구로써는 WebStome 등이 있다.
2.2.2 CF-Abacus 설치
별도 제공하는 Abacus 설치 가이드를 참고하여 CF-Abacus를 설치한다.
2.3 샘플 API 서비스 브로커 및 대시보드 개발
2.4 샘플 API 서비스 개발
샘플 api 서비스는 서비스 요청이 있는 경우, 해당 요청에 대한 응답 처리와 api 서비스 요청에 대한 미터링 정보를 CF-ABACUS에 전송하는 처리를 한다.
Git을 통한 형상 관리 시, 형상 관리를 할 필요가 없는 파일 또는 디렉토리를 설정한다.
manifest.yml
애플리케이션을 파스-타 플랫폼에 배포 시 적용하는 애플리케이션에 대한 환경 설정 정보
애플리케이션의 이름, 배포 경로, 인스턴스 수 등을 정의할 수 있다.
.npmrc
Npm 실행 환경 설정 파일
package.json
node.js 어플리케이션에 필요한 npm의 의존성 정보를 기술하는데 사용 한다.
npm install 명령을 실행시 install 뒤에 아무런 정보를 입력하지 않으면 이 파일의 정보를 이용하여 npm을 설치한다.
sampleApiService
서비스 앱 실행 스크립트
app.js
서비스 앱
서비스 요청에 대한 라우팅 정보와 미터링 정보 전송 처리를 정의한다.
test.js
서비스 앱 단위 테스트 모듈
mocha를 통한 서비스 앱의 단위 테스트를 정의한다.
2.4.1 샘플 API 서비스 애플리케이션 코드 구현
1. Package.json 샘플 애플리케이션의 코드 구성에 대해 기술한다.
{
"name": "sample-api-node-service", ## 앱 명
"version": "0.0.1", ## 앱 버전
"description": "CF API Usage Service Metering Sample",
"main": "lib/app.js", ## 개발 소스의 메인
"bin": {
"sampleApiService": "./sampleApiService"
},
"files": [ ## 형상 관리 대상의 파일 또는 디렉토리를 기술
".apprc",
".npmrc",
"manifest.yml",
"src/",
"sampleApiService"
],
"scripts": { ## npm run 명령어 또는 npm 명령어로 실행
"start": "./sampleApiService start", ## npm start: 앱 실행
"stop": "./sampleApiService stop", ## npm stop: 앱 중지
"babel": "babel", ## npm run babel: 개발 소스를 컴파일
"test": "eslint && mocha", ## npm test: 개발 소스를 테스트
"lint": "eslint", ## npm run lint: 개발 소스 체크
"pub": "publish", ## npm run publish: 개발 소스를 퍼블리시
"cfpack": "cfpack", ## npm run cfpack: 컴파일한 개발 소스를 패키지화
"cfpush": "cfpush" ## npm run cfpush: 패키지화 한 개발 소스를 cf에 push
},
"author": "PAASTA",
"license": "Apache-2.0", ## 라이선스 선언
"dependencies": {
"body-parser": "^1.15.2", ## json parser 모듈
"cors": "^2.8.1", ## cross domain request 허용 모듈
"express": "^4.14.0", ## node.js 웹 프레임워크
"abacus-oauth": "^0.0.6-dev.8", ## Secured Abacus와 통신을 위한 Oauth 모듈
"request": "^2.74.0", ## request 모듈
"babel-preset-es2015": "^6.6.0", ## ECMA5를 ECMA6으로 변환하기 위한 모듈
"commander": "^2.8.1", ## 명령어 실행 모듈
"underscore": "^1.8.3" ## javascript에 사용할 수 있는 함수가 정의된 모듈
},
"devDependencies": { ## 개발 환경에서 의존하는 패키지
"abacus-babel": "file:../../tools/babel", ## 개발 소스를 ECMA5 -> ECMA6으로 변환
"abacus-cfpack": "file:../../tools/cfpack", ## 개발 소스를 cf에 push 할 수 있도록 패키지화 하는 패키지
"abacus-cfpush": "file:../../tools/cfpush", ## 개발 소스를 cf에 push하는 패키지
"abacus-coverage": "file:../../tools/coverage", ## 개발 소스에 대해 테스트 소스의 coverage율 체크 패키지
"abacus-eslint": "file:../../tools/eslint", ## 개발 소스 코드 체크 패키지
"abacus-mocha": "file:../../tools/mocha", ## mocha 테스트 실행 패키지
"abacus-publish": "file:../../tools/publish" ## 개발 소스 퍼블리시 패키지
},
"engines": {
"node": ">=5.11.1", ## nodejs 버전
"npm": ">=3.8.6" ## npm 버전
}
}
2. Manifest.yml 앱을 CF에 배포할 때 필요한 설정 정보 및 앱 실행 환경에 필요한 설정 정보를 기술한다.
applications:
- name: sample-api-node-service # 애플리케이션 이름
host: sample-api-node-service # 애플리케이션 호스트명
memory: 512M # 애플리케이션 메모리 사이즈
disk_quota: 512M # 애플리케이션 디스크 사이즈
instances: 1 # 애플리케이션 인스턴스 개수
command: npm start # CF에서의 애플리케이션 시작 명령어
path: ./.cfpack/app.zip # 배포될 애플리케이션의 위치
env:
CONF: default # 명령어 실행 환경 설정 정보
DEBUG: s* # 디버그 출력 대상 설정
NODE_TLS_REJECT_UNAUTHORIZED: 0 # SSL flag off
API: https://api.bosh-lite.com # CF API 서비스 엔드포인트
COLLECTOR: https://localhost/v1/metering/collected/usage # api 사용량 전송 엔드포인드
SECURED: true # Secured Abacus 설정: false or true
AUTH_SERVER: https://api.bosh-lite.com:443 # oauth 서비스 엔드포인트
CLIENT_ID: abacus # oauth 권한 id
CLIENT_SECRET: secret # oauth id 비밀번호
JWTKEY: |+ # 앱을 secured mode로 서비스 하기 위해 유효성 체크를 위한 인증 서비스 공개키
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDHFr+KICms+tuT1OXJwhCUmR2d
KVy7psa8xzElSyzqx7oJyfJ1JZyOzToj9T5SfTIq396agbHJWVfYphNahvZ/7uMX
qHxf+ZH9BL1gk9Y6kCnbM5R60gfwjyW1/dQPjOzn9N394zd2FJoFHwdq9Qs0wBug
spULZVNRxq7veq/fzwIDAQAB
-----END PUBLIC KEY-----
JWTALGO: RS256
ENV 항목
아래에 기술한 항목 이외에 서비스에 필요한 항목을 추가할 수 있다.
ENV 항목
설명
DEBUG
애플리케이션 디버그 로그 출력 대상 설정
NODE_TLS_REJECT_UNAUTHORIZED
-
API
CF API URL
https://api.
COLLECTOR
Abacus Collector 앱의 사용량 수집 서비스 URL
https:// /v1/metering/collected/usage
SECURED
Secured abacus를 운용할 경우, 반드시 true를 설정한다.
AUTH_SERVER
SECURED가 true인 경우 설정한다.
- CF UAA를 Auth_server로 설정할 경우, https://api.
- Abacus의 AuthServer를 Auth_server로 설정할 경우, abacus-authserver-plugin
CLIENT_ID
SECURED가 true인 경우 설정한다.
Abacus.usage 권한 id
CLIENT_SECRET
SECURED가 true인 경우 설정한다.
Abacus.usage 권한 비밀번호
JWTKEY
SECURED가 true인 경우 설정한다.
- CF UAA를 Auth_server로 설정할 경우, CF 배포 manifest의 properties.jwt.verification_key 값을 설정
- Abacus의 AuthServer를 Auth_server로 설정할 경우, Key 값을 설정
JWTALGO
SECURED가 true인 경우 설정한다.
- CF UAA를 Auth_server로 설정할 경우, RS256 - Abacus의 AuthServer를 Auth_server로 설정할 경우, HS256
3. App.js 애플리케이션의 기능 및 미터링 정보 전송 기능을 구현한다. 샘플 애플리케이션은 다음과 같이 기능을 구현하였다.
의존 모듈 선언
'use strict';
// Implemented in ES5 for now
/* eslint no-var: 0 */ // ECMA5로 개발할 경우, eslint 체크에서 var 사용 제한을 풀어준다.
var express = require('express'); // 웹 프레임워크 모듈
var request = require('request'); // request 모듈
var bodyParser = require('body-parser'); // request 정보를 json으로 변환하는 모듈
var cp = require('child_process');
var commander = require('commander'); // command 사용 가능 모듈
var oauth = require('abacus-oauth'); // oauth 모듈
변수 선언
// Create router
var routes = express.Router(); // 앱 서비스 엔드포인트를 설정 할 미들웨어
// Abacus Collector App's URL
var abacusCollectorUrl = process.env.COLLECTOR; // api 사용량 전송 url (abacus)
// Abacus System Token Scope
var scope = 'abacus.usage.write abacus.usage.read'; // abacus를 secured로 설정할 경우, api 사용량 정보의 전송을 위한
token scope 설정 (scope 설정에 대해서는 abacus 설치 가이드 참조)
// 크로스 도메인 허용을 위한 헤더 설정
var accessControlAllowHeader = 'Origin,X-Requested-With,Content-Type,Accept';
// 아래의 항목은 더미로 설정한 미터링 대상 정보이다.
// 실제 서비스를 구현할 경우, 해당 항목을 제공하는 API 호출이나 프로퍼티 값 등을 통해
// 설정한다.
var resourceId = 'object-storage';
var measure1 = 'storage';
var measure2 = 'light_api_calls';
var measure3 = 'heavy_api_calls';
var serviceKey = '[cloudfoundry]';
Secure Abacus 보안 구현
// abacus 토큰 초기화
var abacusToken = void 0;
// Secure 설정
var secured = function secured() {
return process.env.SECURED === 'true' ? true : false;
};
// abacus에 api usage를 전송할 때, request header 설정
var authHeader = function authHeader(token) {
return token ? { authorization: token() } : {};
};
… 중략 …
var sampleApiService = function sampleApiService() {
var app = express();
// Secured abacue의 경우, 앱 서비스를 시작할 때, abacus 토큰을 구한다.
if (secured()) {
/*
AUTH_SERVER: 인증 서버 엔드포인트 https://hostname:port or
https://hostname
CLIENT_ID: 인증 서버 권한 아이디
CLIENT_SECRET: 인증 서버 권한 비밀번호
SCOPE: 토큰 scope
*/
abacusToken = oauth.cache(process.env.AUTH_SERVER, process.env.CLIENT_ID,
process.env.CLIENT_SECRET, scope);
// abacus Token을 주기적으로 갱신한다.
abacusToken.start();
};
// api 서비스 또한 secured 모드로 실행 할 수 있다.
// secured 모드로 실행할 경우, route에 등록한 모든 서비스에 대해 유효성 체크를 실행하도록 구현한다.
// 본 샘플에서는 abacus의 oauth 서비스의 유효성 체크를 사용하였다.
// if (secured())
// app.use(/^\/plan[0-9]/,
// oauth.validator(process.env.JWTKEY, process.env.JWTALGO));
app.use(routes);
return app;
};
… 후략 …
COR 설정
// api 서비스는 요청한 서비스의 응답 처리 이외에 abacus에 대해 request 처리가 추가로 필요하므로 크로스 도메인을 설정 한다.
routes.use(function(req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
res.header('Access-Control-Allow-Headers', accessControlAllowHeader);
next();
});
서비스 API 구현
/*
Sample API 'Plan1'
1. Caller의 요청에 API 서비스를 처리 응답 (Sample에서는 생략)
2. abacus에 api usage 전송
*/
routes.post('/plan1', function(req, res, next) {
// api 요청 시각 설정
var d = new Date();
var eventTime = Date.parse(d);
// 사용량 미터링에 필요한 메타 정보 설정
var orgid = req.body.organization_id;
var spaceid = req.body.space_id;
var instanceid = reqs.body.instance_id ? reqs.body.instance_id : reqs.body.consumer_id;
var appid = req.body.consumer_id;
var planid = req.body.plan_id;
var credential = req.body.credential;
// 실제 서비스에 실행에 필요한 메타 정보는 inputs에 설정
// var inputs = req.body.inputs;
// 서비스 이용 권한 체크, 해당 체크 내용은 제공할 서비스에 맞게 변형한다.
if (credential.serviceKey != serviceKey)
return res.status(401).send();
// 필수 항목 체크
if (!orgid || !spaceid || !appid)
return res.status(400).send();
// abacus에 리포팅할 api 사용량 정보를 작성한다. (JSON 형식)
var usage =
buildAppUsage(orgid, spaceid, appid, instanceid, planid, eventTime);
// api 사용량 정보 체크
if (usage.usage === null) return res.status(400).send();
// api 사용량 전송을 위한 요청 정보 설정
var options = {
uri: abacusCollectorUrl,
headers: authHeader(abacusToken),
json: usage.usage
};
// api usage를 abacus에 전송
request.post(options, function(error, response, body) {
// abacus 전송 처리 판정
if (error) console.log(error);
else if (response.statusCode === 201 || response.statusCode === 200) {
// console.log('Successfully reported usage %j with headers %j',
// usage, response.headers);
res.status(201).send(response.body);
return;
}
// abacus에 api 사용량을 중복 송신한 경우 발생
else if (response.statusCode === 409) {
// console.log('Conflicting usage %j. Response: %j',
// usage, response);
res.sendStatus(409);
return;
}
// 기타 400 / 500 계열의 오류 체크
else {
// console.log('failed report usage %j with headers %j',
// usage, response.headers);
res.sendStatus(response.statusCode);
return;
}
});
return res;
});
Abacus 전송 Json 생성
// abacus로 전송할 데이터 포맷을 만든다.
var buildAppUsage =
function buildAppUsage(orgid, spaceid, appid, insid, planid, eventTime) {
var appUsage = { usage: null };
// sample api plan id가 'standard'
if (planid == 'standard')
appUsage = {
usage: {
start: eventTime,
end: eventTime,
organization_id: orgid,
space_id: spaceid,
consumer_id: 'app:' + appid,
resource_id: resourceId,
plan_id: planid,
resource_instance_id: insid,
measured_usage: [
{
measure: measure1,
quantity: 1073741824
},
{
measure: measure2,
quantity: 1000
},
{
measure: measure3,
quantity: 0
}
]
}
};
return appUsage;
};
기타
// 앱 서비스를 위한 설정 정보
// PORT, HOST명 등을 설정한다.
var conf = function conf() {
process.env.PORT = commander.port || process.env.PORT || 9602;
};
// Command line 인터페이스 설정
// package에 기술한 start, stop 스크립트를 실행할 수 있다.
// 앱 시작: npm start
// 앱 중지: npm stop
var runCLI = function runCLI() {
commander
.option('-p, --port <port>', 'port number [9602]')
.option('start', 'start the server')
.option('stop', 'stop the server')
.parse(process.argv);
// Start API server
if (commander.start) {
conf();
// Create app and listen on the configured port
var app = sampleApiService();
app.listen({
port: parseInt(process.env.PORT)
});
}
else if (commander.stop)
// Stop API App server
cp.exec(
'pkill -f "node ./sampleApiService"', function(err, stdout, stderr) {
if (err) console.log('Stop error %o', err);
});
};
// Export our public functions
module.exports = sampleApiService;
module.exports.runCLI = runCLI
앱을 CF에 배포할 때 필요한 설정 정보 및 앱 실행 환경에 필요한 설정 정보를 기술한다.
---
applications:
- name: sample-api-node-caller # 애플리케이션 이름
memory: 512M # 애플리케이션 메모리 사이즈
disk_quota: 512M
instances: 1 # 애플리케이션 인스턴스 개수
path: ./.cfpack/app.zip # 배포될 애플리케이션의 위치
command: npm start # CF에서의 애플리케이션 시작 명령어
env:
DEBUG: a*
ORG_ID: d6ce3670-ab9c-4453-b993-f2821f54846b
SECURED: false
#AUTH_SERVER: https://api.bosh-lite.com:443
#CLIENT_ID: abacus
#CLIENT_SECRET: secret
ENV 항목
아래에 기술한 항목 이외에 서비스에 필요한 항목을 추가할 수 있다.
ENV 항목
설명
DEBUG
애플리케이션 디버그 로그 출력 대상 설정
ORG_ID
Caller 애플리케이션을 배포할 조직 ID
SECURED
API 서비스를 Secured로 운용할 경우, 반드시 true를 설정한다.
AUTH_SERVER
SECURED가 true인 경우 설정한다. - CF UAA를 Auth_server로 설정할 경우, https://api. - Abacus의 AuthServer를 Auth_server로 설정할 경우, abacus-authserver-plugin
CLIENT_ID
SECURED가 true인 경우 설정한다.
Abacus.usage 권한 id
CLIENT_SECRET
SECURED가 true인 경우 설정한다.Abacus.usage 권한 비밀번호
3. App.js
Api 서비스를 요청하는 애플리케이션을 구현한다.
의존 모듈 선언
'use strict';
// Implemented in ES5 for now
/* eslint no-var: 0 */
var express = require('express');
var request = require('request');
var bodyParser = require('body-parser');
var cp = require('child_process');
var commander = require('commander');
var handlebars = require('express-handlebars').create({ defaultLayout:'main' }); // 웹 서비스 뷰 처리 모듈
변수 선언
// 크로스 도메인 요청 헤더
var accessControlAllowHeader = 'Origin,X-Requested-With,Content-Type,Accept';
var vcapApp = undefined;
var vcapService = undefined;
var dummyOrgId = undefined;
var vcapBindServices = undefined;
// vcap_application 정보 설정
vcapApp = JSON.parse(process.env.VCAP_APPLICATION);
// vcap_service 정보 설정
vcapService = JSON.parse(process.env.VCAP_SERVICES);
// 조직 guid 설정
dummyOrgId = process.env.ORG_ID;
Secure Abacus 보안 구현
// api 서비스를 참고 하여 api service 요청 시, header에 token을 설정하는 처리를 구현한다.
COR 설정
// api 서비스는 요청한 서비스의 응답 처리 이외에 abacus에 대해 request 처리가 추가로 필요하므로 크로스 도메인을 설정 한다.
routes.use(function(req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
res.header('Access-Control-Allow-Headers', accessControlAllowHeader);
next();
});
서비스 API 구현
// VCAP_SERVICE에서 앱과 바인드한 서비스 정보를 가져온다.
// 복수의 서비스가 앱에 바인드 될 수 있다.
vcapBindServices = vcapService[Object.keys(vcapService)[0]];
var sampleApiCaller = function sampleApiCaller() {
var app = express();
// 웹 서비스 뷰 처리
app.engine('handlebars', handlebars.engine);
app.set('view engine', 'handlebars');
// 크로스 도메인 처리
app.use(function(req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
res.header('Access-Control-Allow-Headers', accessControlAllowHeader);
next();
});
// 뷰에서 참조하는 js 모듈을 미들웨어에 마운트 한다.
app.use('/bower_components',
express.static(__dirname + '/bower_components'));
// 웹 서비스 메인을 미들웨어에 마운트 한다.
app.get('/', function(req, res) {
res.type('text/html');
res.render('apiCaller'); // 뷰 생성
});
// to support JSON-encoded bodies
app.use(bodyParser.json());
// to support URL-encoded bodies
app.use(bodyParser.urlencoded({
extended: true
}));
/*
Sample Web Service
1. API 서비스를 요청
*/
app.post('/sampleApiSerivceCall', function(req, res, next) {
// 앱에 Bind한 서비스가 없는 경우
if (typeof vcapBindServices !== 'object') {
return res.status(502).send('unbind service called');
next();
};
// API Service와 연동 할 서비스를 구한다.
// 바인드된 복수의 서비스 중에서 연동 할 서비스 정보를 구한다.
var bindApiService = vcapBindServices[0];
// 위에서 구한 서비스 정보에서 서비스 url를 구한다.
var serviceUrl = bindApiService.credentials.uri ?
bindApiService.credentials.uri : bindApiService.credentials.url;
// API Service에 요청 할 JSON 을 작성한다.
var callEvent = buildSendData(req.body, bindApiService);
// Set request options
var options = {
uri: serviceUrl,
json: callEvent
};
// api Service에 요청하고 결과를 응답한다.
request.post(options, function(error, response, body) {
if (error) console.log(error);
else if (response.statusCode === 201 || response.statusCode === 200) {
// console.log('Successfully reported usage %j with headers %j',
// usage, response.headers);
res.status(201).send(response.body);
return;
}
else {
// console.log('failed report usage %j with headers %j',
// usage, response.headers);
res.sendStatus(response.statusCode);
return;
}
});
return res;
});
// Not found
app.use(function(req, res) {
res.type('text/plain');
res.status(404);
res.send('404 not found');
});
return app;
};
Abacus 전송 Json 생성
// 서비스로 보낼 데이터를 JSON 형식으로 작성한다.
var buildSendData = function buildSendData(args, bindApiService) {
return {
organization_id: dummyOrgId,
space_id: vcapApp.space_id,
consumer_id: vcapApp.application_id,
instance_id: vcapApp.instance_id ?
vcapApp.instance_id : vcapApp.application_id,
plan_id: bindApiService.plan,
credential: bindApiService.credentials,
inputs: args
};
};
기타
// 앱 서비스를 위한 설정 정보
// PORT, HOST명 등을 설정한다.
var conf = function conf() {
process.env.PORT = commander.port || process.env.PORT || 9601;
};
// Command line interface
var runCLI = function runCLI() {
commander
.option('-p, --port <port>', 'port number [9601]')
.option('start', 'start the server')
.option('stop', 'stop the server')
.parse(process.argv);
// Start Caller server
if (commander.start) {
conf();
// Create app and listen on the configured port
var app = sampleApiCaller();
app.listen({
port: parseInt(process.env.PORT)
});
}
else if (commander.stop)
// Stop Caller App server
cp.exec(
'pkill -f "node ./sampleApiCaller"', function(err, stdout, stderr) {
if (err) console.log('Stop error %o', err);
});
};
// Export our public functions
module.exports = sampleApiCaller;
module.exports.runCLI = runCLI;
4. Views/apiCaller.handlebars
Api 서비스를 요청하는 웹 화면
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="bower_components/jquery/dist/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
$('#result_div').html('');
var appUrl = document.URL;
// 요청 서비스 입력 항목 설정
var input_param1 = '서울';
var input_param2 = '무교동';
$('#send_btn').click(function(){
$('#result_div').html('');
var data = sendData( input_param1, input_param2 );
$.ajax({
url: appUrl + 'sampleApiSerivceCall',
type:"POST",
data:data,
success:function(data)
{
$('#result_div').html("posted.");
},
error:function(jqXHR,textStatus,errorThrown)
{
$('#result_div').html(errorThrown);
}
});
});
});
// 요청 서비스 입력 항목 설정, JSON 형식으로 작성한다.
var sendData = function buildJSON( input_param1, input_param2 ) {
return {
input_param1: input_param1,
input_param2: input_param2
};
};
</script>
</head>
<body>
<form id="form">
</form>
<h3>CF REMOTE SERVICE API CALL TEST</h3>
<button id="send_btn">SEND SERVICE API CALL</button>
<br>
<div id="result_div"></div>
<!--<h4>{{vcapService}}</h4>-->
</body>
</html>
파스-타 플랫폼에 애플리케이션을 배포하면 배포한 애플리케이션과 파스-타 플랫폼이 제공하는 서비스를 연결하여 사용할 수 있다. 파스-타 플랫폼상에서 실행을 해야만 파스-타 플랫폼의 애플리케이션 환경변수에 접근하여 서비스에 접속할 수 있다.
2.7.1 파스-타 플랫폼 로그인
아래의 과정을 수행하기 위해서 파스-타 플랫폼에 로그인
$ cf api --skip-ssl-validation https://api.<파스-타 도메인> # 파스-타 플랫폼 TARGET 지정
$ cf login -u <user name> -o <org name> -s <space name> # 로그인 요청
2.7.2 API 서비스 브로커 생성
애플리케이션에서 사용할 서비스를 파스-타 플랫폼을 통하여 생성한다. 별도의 서비스 설치과정 없이 생성할 수 있으며, 애플리케이션과 바인딩과정을 통해 접속정보를 얻을 수있다.
서비스 생성 (cf marketplace 명령을 통해 서비스 목록과 각 서비스의 플랜을 조회할 수 있다.)
Gradle 버전: 2.2
## 서비스 브로커 CF 배포
$ cd <샘플 서비스 브로커 경로>/sample_api_java_broker
$ gradle build -x test
:compileJava
Note: ~/PAASTA-API-METERING-SAMPLE/lib/sample_api_java_broker/src/main/java/org/openpaas/servicebroker/api/config/CatalogConfig.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
:processResources
:classes
:findMainClass
:jar
:bootRepackage
:assemble
:check
:build
BUILD SUCCESSFUL
Total time: 28.905 secs
This build could be faster, please consider using the Gradle Daemon: https://docs.gradle.org/2.14.1/userguide/gradle_daemon.html
$ cf push
## 서비스 브로커 생성
$ cf create-service-broker <서비스 브로커 명> <인증ID> <인증Password> <서비스 브로커 주소>
예)
$ cf create-service-broker sample-api-broker admin cloudfoundry http://sample-api-java-broker.bosh-lite.com
## 서비스 브로커 확인
$ cf service-brokers
Getting service brokers as admin...
name url
sample-api-broker http://sample-api-java-broker.bosh-lite.com
## 서비스 카탈로그 확인
$ cf service-access
Getting service access as admin...
broker: sample-api-broker
service plan access orgs
standard_obejct_storage_light_api_calls standard none
standard_obejct_storage_heavy_api_calls basic none
## 등록한 서비스 접근 허용
$ cf enable-service-access <서비스명> -p <플랜명>
예)
$ cf enable-service-access standard_obejct_storage_light_api_calls -p standard
2.7.3 API 서비스 애플리케이션 배포 및 서비스 등록
API 서비스 애플리케이션을 파스-타 플랫폼에 배포한다. 서비스 등록한 API는 다른 애플리케이션과 바인드하여 API 서비스를 할 수 있다.
1. 애플리케이션 배포
cf push 명령으로 배포한다. 별도의 값을 넣지않으면 manifest.yml의 설정을 사용한다.
## API 서비스 배포
$ cd <샘플 api 서비스 경로>/sample_api_node_service
$ npm install && npm run babel && npm run cfpack && cf push
## 서비스 생성
$ cf create-service <서비스명> <플랜명> <서비스 인스턴스명>
예)
$ cf create-service standard_obejct_storage_light_api_calls standard sampleNodejslightCallApi
## 서비스 확인
$ cf services
Getting services in org real / space ops as admin...
OK
name service plan bound apps last operation
sampleNodejslightCallApi standard_obejct_storage_light_api_calls standard create succeeded
2.7.4 API 서비스 연동 샘플 애플리케이션 배포 및 서비스 연결
애플리케이션과 서비스를 연결하는 과정을 '바인드(bind)라고 하며, 이 과정을 통해 서비스에 접근할 수 있는 접속정보를 생성한다.
애플리케이션과 서비스 연결
## API 서비스 연동 샘플 애플리케이션 배포
$ cd <샘플 애플리케이션 경로>/sample_api_node_caller
$ npm install && npm run babel && npm run cfpack && ./cfpush.sh
## 서비스 바인드
$ cf bind-service <APP_NAME> <SERVICE_INSTANCE> -c <PARAMETERS_AS_JSON>
예)
$ cf bind-service sample-api-node-caller sampleNodejslightCallApi -c '{"serviceKey": "[cloudfoundry]"}'
## 서비스 연결 확인
$ cf services
Getting services in org real / space ops as admin...
OK
name service plan bound apps last operation
sampleNodejslightCallApi standard_obejct_storage_light_api_calls standard sample-api-node-caller create succeeded
## 애플리케이션 실행
$ cf start <APP_NAME>
예)
$ cf start sample-api-node-caller
## 형상 확인
$ cf a
Getting apps in org real / space ops as admin...
OK
name requested state instances memory disk urls
sample-api-node-service started 1/1 512M 512M sample-api-node-service.bosh-lite.com
sample-api-java-broker started 1/1 512M 1G sample-api-java-broker.bosh-lite.com
sample-api-node-caller started 1/1 512M 512M sample-api-node-caller.bosh-lite.com
2.8 테스트
샘플 애플리케이션은 REST 서비스로 구현되어 있으며, 코드 체크, 테스트 및 커버리스 체크를 위해서 eslint/mocha/Istanbul 모듈을 사용하였다. 테스트를 진행하기 위해서는 mocha 모듈을 포함한 package.json 안의 devDependencies모듈이 설치 되어 있어야한다. (npm install)
1. 코드 테스트를 위한 개발 환경 구성
테스트를 위한 형상
<앱>/
├── .eslint // 코드 체크 대상 설정
├── .eslintignore // 코드 체크에서 제외할 파일 또는 디렉토리 정의
├── package.json // 앱 구성 요소 정의
├── lib // npm run babel을 실행하면 자동으로 생성된다. cfpack 및 mocha 테스트 는 lib에 있는 모듈을 대상 한다.
│ ├── app.js // ECMA5로 변환된 app.js
│ ├── bower_components
│ └── test
│ └── test.js
├── src // 코드 변환 및 코드 체크 대상 디렉토리
│ ├── app.js
│ ├── bower_components
│ └── test // mocha 테스트 모듈은 반드시 src/test 디렉토리에 있어야 한다.
│ └── test.js
└── views // src/lib 이외에 위치한 디렉토리와 파일은 체크 및 mocha 테스트 대상이 아니다.
├── apiCaller.handlebars
└── layouts
└── main.handlebars
테스트를 위한 package.json 구성
{
... 중략
"scripts": {
"babel": "babel", // 코드 변환
"test": "eslint && mocha", // 코드 체크 및 테스트
"lint": "eslint" // 코드 체크
},
... 중략
"devDependencies": {
"abacus-babel": "^0.0.6-dev.8", // 코드 변환 (ECMA6 -> ECMA5)
"abacus-coverage": "^0.0.6-dev.8", // 테스트 커버리지 체크
"abacus-eslint": "^0.0.6-dev.8", // 코드 체크
"abacus-mocha": "^0.0.6-dev.8" // 코드 테스트
},
... 후략
}
Eslint 설정
// false / 0 / disable을 설정한 항목에 대해서만 체크를 생략한다.
// 규칙에 대한 자세한 내용은 http://eslint.org/docs/rules/ 을 참고 한다.
---
parser: espree
env:
browser: true
node: true
es6: false
jasmine: true
mocha: true
globals:
__DEV__: true
jest: true
sinon: true
chai: true
spy: true
stub: true
parserOptions:
sourceType: "module"
rules:
# ERRORS
space-before-blocks: 2 // 공백 문자가 2개 이상 연속
indent: [2, 2, { "SwitchCase": 1 }] // 들여쓰기 형식 설정 (공백문자 2개만 허용)
#strict: [2, "global"]
semi: [2, "always"] // ‘;’ 누락
comma-dangle: [2, "never"]
no-unused-expressions: 2
block-scoped-var: 2
dot-notation: 2
consistent-return: 2 // 함수의 응답 결과 형식 불일치
no-unused-vars: [2, args: none] // 소스내 참조되지 않는 변수 선언
quotes: [2, 'single'] // 문자열에 ‘“’ 사용 금지
space-infix-ops: 2
no-else-return: 2
no-extra-parens: 2
no-eq-null: 2
no-floating-decimal: 2
no-param-reassign: 2
no-self-compare: 2
wrap-iife: [2, "inside"]
brace-style: [2, "stroustrup", { "allowSingleLine": false }]
object-curly-spacing: [1, "always"]
func-style: [2, "expression"]
no-lonely-if: 2
space-in-parens: [2, "never"]
space-before-function-paren: [2, "never"]
generator-star-spacing: [2, "before"]
spaced-comment: [2, "always"]
eol-last: 2 // 소스 코드의 마지막에 개행문자 없음
no-multi-spaces: 2
curly: [2, "multi"]
camelcase: [2, {properties: "never"}] // 변수명에 ‘_’ 사용 허용
no-eval: 2
#require-yield: 2
no-var: 2 // 변수 선언에 var 금지 (ECMA6)
max-len: [2, 80] // 한줄에 80자까지 허용
complexity: [2, 6]
arrow-parens: [2, "always"]
# WARNINGS
# We use this for functions that reference each other
no-use-before-define: 1
# WISHLIST.
# valid-jsdoc: 1
# DISABLED. These aren't compatible with our style
# We use this for private/internal variables
no-underscore-dangle: 0
# We pass constructors around / access them from members
new-cap: 0
# We do this in a few places to align values
key-spacing: 0
# We do this a lot
space-after-keywords: 0
# We do this mostly for callbacks
no-shadow: 0
# We do not use spaces in brackets but use spaces in braces
space-in-brackets: 0
2. 테스트 실행
Test 디렉토리 아래에 있는 모듈을 테스트 한다.
$ cd <샘플 애플리케이션 경로>/<앱>
$ npm install && npm run babel && npm test
$ npm install && npm run babel && npm test
sample-api-node-service@0.0.1 ~/PAASTA-API-METERING-SAMPLE/lib/sample_api_node_service
├─┬ abacus-babel@0.0.6-dev.8
... 설치 패키지 트리 출력 ...
npm WARN sample-api-node-service@0.0.1 No repository field.
> sample-api-node-service@0.0.1 babel /home/cloud4u/workspace/PAASTA-API-METERING-SAMPLE/lib/sample_api_node_service
> babel
src/app.js -> lib/app.js
src/test/test.js -> lib/test/test.js
> sample-api-node-service@0.0.1 test /home/cloud4u/workspace/PAASTA-API-METERING-SAMPLE/lib/sample_api_node_service
> eslint && mocha
Testing...
Running Istanbul instrumentation on node_modules/abacus-oauth/lib/index.js
Running Istanbul instrumentation on node_modules/abacus-request/lib/index.js
Running Istanbul instrumentation on node_modules/abacus-transform/lib/index.js
Running Istanbul instrumentation on node_modules/abacus-yieldable/lib/index.js
Running Istanbul instrumentation on node_modules/abacus-debug/lib/index.js
Running Istanbul instrumentation on node_modules/abacus-events/lib/index.js
Running Istanbul instrumentation on node_modules/abacus-lock/lib/index.js
Running Istanbul instrumentation on node_modules/abacus-lrucache/lib/index.js
Running Istanbul instrumentation on node_modules/abacus-retry/lib/index.js
sample-api-node-service
Running Istanbul instrumentation on lib/app.js
✓ Send API usage data to Abacus (59ms)
✓ Missing metering parameter
✓ Not serviced plan
✓ Missing credentials
✓ Send duplicated data to Abacus
✓ Abacus is not serviced
6 passing (159ms)
Source lib/app.js
'use strict';
// Implemented in ES5 for now
/* eslint no-var: 0 */
... 중략 ...
// Export our public functions
module.exports = sampleApiService;
module.exports.runCLI = runCLI;
Coverage lines 88.89% statements 86.9% lib/app.js
Run time 686ms
2.9 API 및 CF-Abacus 연동 테스트
API 연동 샘플 애플리케이션의 url을 통해 웹 브라우저에서 접속하면 API 연동 및 API 사용량에 대한 CF-Abacus 연동 테스트를 진행 할 수 있다.
1. CF-Abacus 연동 확인
## 조직 guid 확인
$ cf org <샘플 애플리케이션을 배포한 조직> --guid
예)
$ cf org real --guid
877d01b2-d177-4209-95b0-00de794d9bba
## 샘플 애플리케이션 guid 확인
$ cf env <샘플 애플리케이션 명>
예)
$ cf env sample-api-node-caller
Getting env variables for app sample-api-node-caller in org real / space ops as admin...
OK
<<중략>>
{
"VCAP_APPLICATION": {
"application_id": "58872d8a-edfc-44df-97f0-df67cf9033a7",
"application_name": "sample-api-node-caller",
"application_uris": [
"sample-api-node-caller.bosh-lite.com"
],
"application_version": "55678102-584c-4fca-8304-82f727506b1d",
"limits": {
"disk": 512,
"fds": 16384,
"mem": 512
},
"name": "sample-api-node-caller",
"space_id": "2ce08996-f463-406c-a971-adbbaf4e4ca5",
"space_name": "ops",
"uris": [
"sample-api-node-caller.bosh-lite.com"
],
"users": null,
"version": "55678102-584c-4fca-8304-82f727506b1d"
}
}
<<후략>>
## API 사용량 확인
$ curl 'http://abacus-usage-reporting.<파스-타 도메인>/v1/metering/organizations/<샘플 애플리케이션을 배포한 조직>/aggregated/usage'
예)
$ curl 'http://abacus-usage-reporting.bosh-lite.com/v1/metering/organizations/877d01b2-d177-4209-95b0-00de794d9bba/aggregated/usage'
2.10. 샘플코드
샘플코드는 아래의 사이트에 다운로드 할 수 있다.
API 서비스 제공자가 제공하는 서비스에 대한 각종 정책 정의 정보. JSON 형식으로 되었으며, 해당 정책을 CF-ABACUS에 등록하면 정책에 정의한 내용에 따라 API 사용량을 집계 한다.
정책은 서비스 제공자가 정의해야 하며, JSON 스키마는 다음을 참조한다.
Cloud Controller와 Service Broker 사이의 규약으로써 서비스 브로커 API 개발에 대해서는 다음을 참조한다.
CF-ABACUS 핵심 기능으로써 수집한 사용량 정보를 집계한다.
CF-ABACUS은 CF 설치 후, CF에 마이크로 서비스 형태로 설치한다. 자세한 사항은 다음을 참조한다.
※ Windows 용은 다음 사이트에서 Node.js를 다운 받는다.
별도 제공하는 서비스 브로커 개발 가이드를 참고하여 서비스 브로커 및 대시보드를 개발한다.