<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.4.5">Jekyll</generator><link href="https://oddpoet.net/feed.xml" rel="self" type="application/atom+xml" /><link href="https://oddpoet.net/" rel="alternate" type="text/html" /><updated>2017-07-11T16:39:26+09:00</updated><id>https://oddpoet.net/</id><title type="html">oddpoet’s étude</title><subtitle>C로 詩를 쓸 수 있다 믿었던 바보개발자의 삽질이력...</subtitle><author><name>Yunsang Choi</name><email>oddpoet@gmail.com</email></author><entry><title type="html">Graceful Shutdown Server in Docker</title><link href="https://oddpoet.net/blog/2017/07/11/graceful-shutdown-server-in-docker/" rel="alternate" type="text/html" title="Graceful Shutdown Server in Docker" /><published>2017-07-11T16:15:22+09:00</published><updated>2017-07-11T16:15:22+09:00</updated><id>https://oddpoet.net/blog/2017/07/11/graceful-shutdown-server-in-docker</id><content type="html" xml:base="https://oddpoet.net/blog/2017/07/11/graceful-shutdown-server-in-docker/">&lt;p&gt;대부분의 서버 프로그램들은 &lt;code class=&quot;highlighter-rouge&quot;&gt;SIGTERM&lt;/code&gt;을 받으면 더이상의 연결은 받지 않지만, 현재 연결에 의한 작업은 마치고 종료된다.&lt;/p&gt;

&lt;p&gt;그런데 docker로 이미지를 만들어 server를 구동하는 경우 &lt;code class=&quot;highlighter-rouge&quot;&gt;docker stop&lt;/code&gt; 명령으로
server를 graceful하게 shutdown할 수 없는 경우가 있다.&lt;/p&gt;

&lt;!-- more --&gt;

&lt;p&gt;이런 문제의 발생여부는 해당 docker image의 &lt;code class=&quot;highlighter-rouge&quot;&gt;ENTRYPOINT&lt;/code&gt;(&lt;code class=&quot;highlighter-rouge&quot;&gt;Dockerfile&lt;/code&gt;에서 정의된)가 무엇이냐 달려있다.&lt;/p&gt;

&lt;p&gt;예를 들어 아래와 같이 &lt;code class=&quot;highlighter-rouge&quot;&gt;ENTRYPOINT&lt;/code&gt;를 정의한 경우 &lt;code class=&quot;highlighter-rouge&quot;&gt;docker stop&lt;/code&gt;을 실행하면 서버는 graceful shutdown된다.&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ENTRYPOINT [&quot;java&quot;, &quot;-jar&quot;, &quot;server.war&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;그러나 &lt;code class=&quot;highlighter-rouge&quot;&gt;ENTRYPOINT&lt;/code&gt;가 shell script인 경우에는 &lt;code class=&quot;highlighter-rouge&quot;&gt;TERM&lt;/code&gt; 시그널을 shell script가 받기 때문에
서버 프로세스는 signal을 받지 못하고 최종적으로 docker container는 일정 시간 후 강제종료(&lt;code class=&quot;highlighter-rouge&quot;&gt;KILL&lt;/code&gt;)된다.&lt;/p&gt;

&lt;p&gt;따라서 아래와 같이 &lt;code class=&quot;highlighter-rouge&quot;&gt;ENTRYPOINT&lt;/code&gt;를 shell script로 설정했다면 추가적인 작업을 해야한다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ENTRYPOINT [&quot;start.sh&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# file: start.sh&lt;/span&gt;
...&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;전략&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;...

&lt;span class=&quot;nb&quot;&gt;trap&lt;/span&gt; &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;exitHandler&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt; EXIT
exitHandler&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Server will be shutdown&quot;&lt;/span&gt; 1&amp;gt;&amp;amp;2
    &lt;span class=&quot;nb&quot;&gt;kill&lt;/span&gt; -TERM &lt;span class=&quot;k&quot;&gt;$(&lt;/span&gt;jps | grep war | awk &lt;span class=&quot;s1&quot;&gt;'{print $1}'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

java &lt;span class=&quot;nv&quot;&gt;$JAVA_OPTS&lt;/span&gt; -jar server.war
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;ENTRYPOINT&lt;/code&gt; script를 위와 같이 작성하면,
1번 프로세스이자 메인 프로세스인 &lt;code class=&quot;highlighter-rouge&quot;&gt;start.sh&lt;/code&gt;이 docker container에 대한 SIGNAL을 받고,
&lt;code class=&quot;highlighter-rouge&quot;&gt;trap&lt;/code&gt;에 의해 &lt;code class=&quot;highlighter-rouge&quot;&gt;exitHandler&lt;/code&gt;에서 종료 시그널에 대한 작업을 할 수 있게 된다.&lt;/p&gt;

&lt;p&gt;따라서 최종적으로 &lt;code class=&quot;highlighter-rouge&quot;&gt;java&lt;/code&gt; 프로세스는 정상적으로 &lt;code class=&quot;highlighter-rouge&quot;&gt;TERM&lt;/code&gt; 시그널을 받아 gracefully 종료된다.&lt;/p&gt;</content><author><name>Yunsang Choi</name><email>oddpoet@gmail.com</email></author><category term="docker" /><summary type="html">대부분의 서버 프로그램들은 SIGTERM을 받으면 더이상의 연결은 받지 않지만, 현재 연결에 의한 작업은 마치고 종료된다. 그런데 docker로 이미지를 만들어 server를 구동하는 경우 docker stop 명령으로 server를 graceful하게 shutdown할 수 없는 경우가 있다.</summary></entry><entry><title type="html">CORS 설정과 Spring Security</title><link href="https://oddpoet.net/blog/2017/04/27/cors-with-spring-security/" rel="alternate" type="text/html" title="CORS 설정과 Spring Security" /><published>2017-04-27T02:52:33+09:00</published><updated>2017-04-27T02:52:33+09:00</updated><id>https://oddpoet.net/blog/2017/04/27/cors-with-spring-security</id><content type="html" xml:base="https://oddpoet.net/blog/2017/04/27/cors-with-spring-security/">&lt;p&gt;API 서버와 Web FrontEnd 서버를 나누어서 구성할 때 CORS(&lt;em&gt;Cross Origin Resource Sharing&lt;/em&gt;) 설정을 해야하는데,
&lt;em&gt;spring-security&lt;/em&gt;를 적용 중이라면 servlet filter로 CORS를 적용하면 원치 않은 결과를 얻을 수 있다.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;spring-security&lt;/em&gt;를 사용 중일 때 &lt;em&gt;CORS&lt;/em&gt;를 적용하는 방법을 정리한다.&lt;/p&gt;

&lt;!-- more --&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@EnableWebSecurity&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@Configuation&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SecurityConfiguration&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;WebSecurityConfigurerAdapter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;configure&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HttpSecurity&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;http&lt;/span&gt;
          &lt;span class=&quot;cm&quot;&gt;/*... 중략 ...*/&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;authorizeRequests&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;requestMatchers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;CorsUtils:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;isPreFlightRequest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;permitAll&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// - (1)&lt;/span&gt;
            &lt;span class=&quot;cm&quot;&gt;/* 중략 */&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;anyRequest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;authenticated&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;and&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;cors&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;and&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// - (2)&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Bean&lt;/span&gt;
   &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CorsConfigurationSource&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;corsConfigurationSource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;n&quot;&gt;CorsConfiguration&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;configuration&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CorsConfiguration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
       &lt;span class=&quot;c1&quot;&gt;// - (3)&lt;/span&gt;
       &lt;span class=&quot;n&quot;&gt;configuration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;addAllowedOrigin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;*&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
       &lt;span class=&quot;n&quot;&gt;configuration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;addAllowedMethod&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;*&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
       &lt;span class=&quot;n&quot;&gt;configuration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;addAllowedHeader&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;*&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
       &lt;span class=&quot;n&quot;&gt;configuration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setAllowCredentials&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
       &lt;span class=&quot;n&quot;&gt;configuration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setMaxAge&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3600L&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
       &lt;span class=&quot;n&quot;&gt;UrlBasedCorsConfigurationSource&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;UrlBasedCorsConfigurationSource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
       &lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;registerCorsConfiguration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/**&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;configuration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
       &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
   &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;위 코드에서 &lt;code class=&quot;highlighter-rouge&quot;&gt;(1)&lt;/code&gt; 부분이 중요하다.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;CORS preflight&lt;/em&gt; 요청은 인증처리를 하지 않겠다는 것인데,
CORS semantic 상으로 CORS prefight에는 &lt;code class=&quot;highlighter-rouge&quot;&gt;Authorization&lt;/code&gt; 헤더를 줄 이유가 없으므로
&lt;em&gt;CORS preflight&lt;/em&gt; 요청에 대해서는 &lt;code class=&quot;highlighter-rouge&quot;&gt;401&lt;/code&gt; 응답을 하면 안된다.&lt;/p&gt;

&lt;p&gt;이 부분의 설정이 없으면 spring-security 설정에 의해서
&lt;em&gt;CORS preflight&lt;/em&gt; 요청이 정상적으로 처리되지 못해서 실제 CORS 요청이 정상적으로 이루어지지 않는다.
(&lt;em&gt;CORS preflight&lt;/em&gt;에 대해서는 아래에서 설명한다)&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;(2)&lt;/code&gt; 부분은 spring-security에서 cors를 적용한다는 설정이다.
인증 성공 여부와 무관하게 &lt;code class=&quot;highlighter-rouge&quot;&gt;Origin&lt;/code&gt; 헤더가 있는 모든 요청에 대해 CORS 헤더를 포함한 응답을 해준다.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;(3)&lt;/code&gt;은 CORS 정책설정이다.
입맛 대로 설정하자.&lt;/p&gt;

&lt;h3 id=&quot;cors-preflight&quot;&gt;CORS preflight&lt;/h3&gt;

&lt;p&gt;사실 기본적인 내용들은 &lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/HTTP/Access_control_CORS&quot;&gt;모질라의 HTTP 접근제어(CORS)&lt;/a&gt; 문서에 잘 설명되어 있다.&lt;/p&gt;

&lt;p&gt;상기 문서에 설명되어 있듯이 브라우저는 현재 웹페이지 이외의 사이트에 xhr 요청할 때 &lt;strong&gt;CORS preflight&lt;/strong&gt; 라는 요청을 보낸다.
이 것은 실제 해당 서버에 CORS 정책을 확인하기 위한 요청이며 &lt;code class=&quot;highlighter-rouge&quot;&gt;OPTIONS&lt;/code&gt; 메소드를 사용하여 요청을 보낸다
(&lt;code class=&quot;highlighter-rouge&quot;&gt;GET /hello&lt;/code&gt; 요청에 대해 &lt;code class=&quot;highlighter-rouge&quot;&gt;OPTIONS /hello&lt;/code&gt; 요청을 &lt;em&gt;preflight&lt;/em&gt;로 보낸다).&lt;/p&gt;

&lt;p&gt;이 &lt;code class=&quot;highlighter-rouge&quot;&gt;OPTIONS&lt;/code&gt; 요청은 웹페이지의 javascript에 의한 명시적인 요청이 아니라, 브라우저가 몰래(?) 보내는 요청이다.
이 요청의 응답으로 적절한 CORS 접근 권한을 얻지 못하면 브라우저는 그 사이트에 대한 xhr 요청을 모두 무시한다. (실제 서버응답을 javascript로 돌려주지 않는다)&lt;/p&gt;

&lt;p&gt;&lt;em&gt;CORS&lt;/em&gt;와 관련된 응답헤더들은 아래와 같다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;Access-Contorl-Allow-Origin&lt;/code&gt;
    &lt;ul&gt;
      &lt;li&gt;CORS 요청을 허용할 사이트 (e.g. &lt;code class=&quot;highlighter-rouge&quot;&gt;https://oddpoet.net&lt;/code&gt;)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;Access-Contorl-Allow-Method&lt;/code&gt;
    &lt;ul&gt;
      &lt;li&gt;CORS 요청을 허용할 Http Method들 (e.g. &lt;code class=&quot;highlighter-rouge&quot;&gt;GET,PUT,POST&lt;/code&gt;)&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;Access-Contorl-Allow-Headers&lt;/code&gt;
    &lt;ul&gt;
      &lt;li&gt;특정 헤더를 가진 경우에만 CORS 요청을 허용할 경우&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;Access-Contorl-Allow-Credencial&lt;/code&gt;
    &lt;ul&gt;
      &lt;li&gt;자격증명과 함께 요청을 할 수 있는지 여부.&lt;/li&gt;
      &lt;li&gt;해당 서버에서 &lt;code class=&quot;highlighter-rouge&quot;&gt;Authorization&lt;/code&gt;로 사용자 인증도 서비스할 것이라면 &lt;code class=&quot;highlighter-rouge&quot;&gt;true&lt;/code&gt;로 응답해야 할 것이다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;Access-Contorl-Max-Age&lt;/code&gt;
    &lt;ul&gt;
      &lt;li&gt;preflight 요청의 캐시 시간.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;References&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/HTTP/Access_control_CORS&quot;&gt;Access_control_CORS&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://stackoverflow.com/questions/36968963/how-to-configure-cors-in-a-spring-boot-spring-security-application/37610988#37610988&quot;&gt;how-to-configure-cors-in-a-spring-boot-spring-security-application&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://docs.spring.io/spring-security/site/docs/current/reference/html/cors.html&quot;&gt;Spring Security : CORS&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://docs.spring.io/spring/docs/current/spring-framework-reference/html/cors.html&quot;&gt;Spring : CORS support&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><author><name>Yunsang Choi</name><email>oddpoet@gmail.com</email></author><category term="CORS" /><category term="spring security" /><summary type="html">API 서버와 Web FrontEnd 서버를 나누어서 구성할 때 CORS(Cross Origin Resource Sharing) 설정을 해야하는데, spring-security를 적용 중이라면 servlet filter로 CORS를 적용하면 원치 않은 결과를 얻을 수 있다. spring-security를 사용 중일 때 CORS를 적용하는 방법을 정리한다.</summary></entry><entry><title type="html">블로그 호스팅 이전 : github pages</title><link href="https://oddpoet.net/blog/2017/04/25/move-to-github-pages/" rel="alternate" type="text/html" title="블로그 호스팅 이전 : github pages" /><published>2017-04-25T00:00:00+09:00</published><updated>2017-04-25T00:00:00+09:00</updated><id>https://oddpoet.net/blog/2017/04/25/move-to-github-pages</id><content type="html" xml:base="https://oddpoet.net/blog/2017/04/25/move-to-github-pages/">&lt;p&gt;제주의 습한 기후 때문인지 블로그를 서비스해주던 홈서버가 사망하셨다.
(아마도 2~3달 동안 블로그가 내려가 있었던 듯)&lt;/p&gt;

&lt;p&gt;오래된 PC로 돌리던 리눅스박스였으니 성능은 별루 였지만, 이런저런 장난감으로 잘 활용했었는데 참 아쉽다.
나이를 먹었는지(?) 최근 홈서버 운영하는게 귀찮아지기도 했고, 클라우스 서비스들이 좋아져서 더이상 홈서버를 돌리지는 않을 생각이다.&lt;/p&gt;

&lt;p&gt;파일 서버는 &lt;em&gt;HDD dock&lt;/em&gt;으로 대체하기로 했고, 이제 귀찮게 gitlab을 운영하는 것보다 필요하면 github 돈내고 쓰는게 더 편하다.&lt;/p&gt;

&lt;p&gt;블로그도 &lt;code class=&quot;highlighter-rouge&quot;&gt;jekyll&lt;/code&gt;기반의 &lt;a href=&quot;http://octopress.org/&quot;&gt;octopress&lt;/a&gt;를 쓰고 있던 터라,
약간의 품을 팔아서 금방 &lt;em&gt;github pages&lt;/em&gt;로 옮겼다.
&lt;code class=&quot;highlighter-rouge&quot;&gt;gh-pages&lt;/code&gt; 특성상 다양한 jekyll 플러그인을 못쓰는게 아쉽지만 호스팅의 장점을 생각하면 충분히 가치가 있다.
&lt;a href=&quot;https://www.cloudflare.com/&quot;&gt;cloudflare&lt;/a&gt;를 이용하면 쉽게 &lt;em&gt;https&lt;/em&gt;로 서비스할 수 있다는 점도 장점.&lt;/p&gt;

&lt;p&gt;그다지 블로깅을 열심히 하는 편은 아니지만,
테마도 바꾸고, &lt;em&gt;gh-pages&lt;/em&gt; 설정도 하고, &lt;em&gt;cloudflare&lt;/em&gt; 세팅도 하다보니
살짝 동기부여가 되는 느낌이다. 그렇다고 열심히 포스팅할 것 같진 않지만…&lt;/p&gt;</content><author><name>Yunsang Choi</name><email>oddpoet@gmail.com</email></author><summary type="html">제주의 습한 기후 때문인지 블로그를 서비스해주던 홈서버가 사망하셨다. (아마도 2~3달 동안 블로그가 내려가 있었던 듯) 오래된 PC로 돌리던 리눅스박스였으니 성능은 별루 였지만, 이런저런 장난감으로 잘 활용했었는데 참 아쉽다. 나이를 먹었는지(?) 최근 홈서버 운영하는게 귀찮아지기도 했고, 클라우스 서비스들이 좋아져서 더이상 홈서버를 돌리지는 않을 생각이다. 파일 서버는 HDD dock으로 대체하기로 했고, 이제 귀찮게 gitlab을 운영하는 것보다 필요하면 github 돈내고 쓰는게 더 편하다. 블로그도 jekyll기반의 octopress를 쓰고 있던 터라, 약간의 품을 팔아서 금방 github pages로 옮겼다. gh-pages 특성상 다양한 jekyll 플러그인을 못쓰는게 아쉽지만 호스팅의 장점을 생각하면 충분히 가치가 있다. cloudflare를 이용하면 쉽게 https로 서비스할 수 있다는 점도 장점. 그다지 블로깅을 열심히 하는 편은 아니지만, 테마도 바꾸고, gh-pages 설정도 하고, cloudflare 세팅도 하다보니 살짝 동기부여가 되는 느낌이다. 그렇다고 열심히 포스팅할 것 같진 않지만…</summary></entry><entry><title type="html">docker device-mapper error</title><link href="https://oddpoet.net/blog/2014/11/19/docker-device-mapper-error/" rel="alternate" type="text/html" title="docker device-mapper error" /><published>2014-11-19T13:12:00+09:00</published><updated>2014-11-19T13:12:00+09:00</updated><id>https://oddpoet.net/blog/2014/11/19/docker-device-mapper-error</id><content type="html" xml:base="https://oddpoet.net/blog/2014/11/19/docker-device-mapper-error/">&lt;p&gt;지난주에 빌드 서버에서 &lt;code class=&quot;highlighter-rouge&quot;&gt;docker pull&lt;/code&gt;하다가 서버에 hang이 걸리는 문제가 발생했다.&lt;/p&gt;

&lt;p&gt;시스템 담당자와 로그 뒤지면서 씨름하던 끝에 devicemapper가 원인이라는 것을 알았다.
시스템 로그에 보면 아래와 같은게 있는데, &lt;code class=&quot;highlighter-rouge&quot;&gt;dm-1&lt;/code&gt;이라는게 docker의 &lt;strong&gt;devicemapper&lt;/strong&gt; 이다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;...
Nov 10 16:55:56 my-server-01 kernel: EXT4-fs (dm-1): VFS: Can't find ext4 filesystem
Nov 10 17:06:40 my-server-01 kernel: EXT4-fs (dm-1): VFS: Can't find ext4 filesystem
...
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;!-- more --&gt;

&lt;h2 id=&quot;해결방법&quot;&gt;해결방법&lt;/h2&gt;

&lt;p&gt;먼저 &lt;a href=&quot;https://github.com/jthornber/thin-provisioning-tools&quot;&gt;thin-provisioning-tools&lt;/a&gt;을 설치한다.&lt;/p&gt;

&lt;p&gt;CentOs 계열은 아래와 같이 &lt;a href=&quot;https://github.com/jthornber/thin-provisioning-tools&quot;&gt;thin-provisioning-tools&lt;/a&gt;을 설치할 수 있다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo yum install device-mapper-persistent-data -y
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/jthornber/thin-provisioning-tools&quot;&gt;thin-provisioning-tools&lt;/a&gt;을 설치했으면 아래와 같이 devicemapper에 문제가 있는 확인해본다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo thin_check /var/lib/docker/devicemapper/devicemapper/metadata
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;문제가 있다면 아래와 같은 메시지들이 출력된다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;...
10122: was 1, expected 2
10123: was 1, expected 2
10124: was 1, expected 2
10125: was 4, expected 5
10126: was 4, expected 5
10127: was 1, expected 2
12384: was 1, expected 2
12385: was 1, expected 2
...
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;아래와 같이 문제있는 devicemapper의 metadata를 복구할 수 있다. (&lt;code class=&quot;highlighter-rouge&quot;&gt;thin_dump&lt;/code&gt;의 &lt;code class=&quot;highlighter-rouge&quot;&gt;-r&lt;/code&gt;이 &lt;em&gt;repair&lt;/em&gt; 옵션이다.)&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo service docker stop
sudo thin_dump -f xml -r /var/lib/docker/devicemapper/devicemapper/metadata &amp;gt; /tmp/metadata
sudo thin_restore -i /tmp/metadata -o /var/lib/docker/devicemapper/devicemapper/metadata
sudo service docker start
sudo thin_check /var/lib/docker/devicemapper/devicemapper/metadata
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;docker가 실행 중일 때 dump &amp;amp; restore를 하면 &lt;code class=&quot;highlighter-rouge&quot;&gt;thin_check&lt;/code&gt;시에 &lt;em&gt;Bad Checksum&lt;/em&gt; 관련 오류 메시지가 뜨니, 반드시 docker 데몬을 중단시키고 작업하자.&lt;/p&gt;

&lt;p&gt;이제 &lt;code class=&quot;highlighter-rouge&quot;&gt;thin_check&lt;/code&gt;를 수행해도 아무런 메시지가 실행되지 않을 것이다.&lt;/p&gt;

&lt;p&gt;만약 이렇게 해도 문제가 되면 아예 devicemapper 관련 파일을 몽땅 삭제하는 방법을 쓰자.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# 모든 image를 삭제하고 devicemapper의 metadata도 삭제한다.
docker rmi $(docker images -a | awk '{print $3}' | grep -v IMAGE)

sudo service docker stop
sudo rm -rf /var/lib/docker/devicemapper
sudo service docker start
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;다시 &lt;code class=&quot;highlighter-rouge&quot;&gt;docker pull&lt;/code&gt;을 하면 &lt;code class=&quot;highlighter-rouge&quot;&gt;/var/lib/docker/devicemapper/devicemapper/metadata&lt;/code&gt;은 다시 생성된다.&lt;/p&gt;

&lt;p&gt;참고 : &lt;a href=&quot;http://stackoverflow.com/questions/24709741/cant-run-docker-container-due-device-mapper-error&quot;&gt;StackOverflow의 스레드&lt;/a&gt;&lt;/p&gt;</content><author><name>Yunsang Choi</name><email>oddpoet@gmail.com</email></author><category term="docker" /><summary type="html">지난주에 빌드 서버에서 docker pull하다가 서버에 hang이 걸리는 문제가 발생했다. 시스템 담당자와 로그 뒤지면서 씨름하던 끝에 devicemapper가 원인이라는 것을 알았다. 시스템 로그에 보면 아래와 같은게 있는데, dm-1이라는게 docker의 devicemapper 이다. ... Nov 10 16:55:56 my-server-01 kernel: EXT4-fs (dm-1): VFS: Can't find ext4 filesystem Nov 10 17:06:40 my-server-01 kernel: EXT4-fs (dm-1): VFS: Can't find ext4 filesystem ...</summary></entry><entry><title type="html">MockHTable : HBase testing</title><link href="https://oddpoet.net/blog/2014/07/13/mockhtable-hbase-testing/" rel="alternate" type="text/html" title="MockHTable : HBase testing" /><published>2014-07-13T15:56:00+09:00</published><updated>2014-07-13T15:56:00+09:00</updated><id>https://oddpoet.net/blog/2014/07/13/mockhtable-hbase-testing</id><content type="html" xml:base="https://oddpoet.net/blog/2014/07/13/mockhtable-hbase-testing/">&lt;p&gt;HBase 기반 시스템을 개발하면서 제일 아쉬운게 HSQL과 같이 테스트 시에 사용할 수 있는 embeded DB였다. HSQL은 실제 DB와 거의 동일한 기능을 제공하면서 로컬 머신에서 빠르게 동작하기 때문에 Unit Test 작성하고 확인할 때 빠르게 피드백을 받을 수 있게 도와주는 좋은 툴이었다.&lt;/p&gt;

&lt;p&gt;하지만 HBase는 unit test 수행후 rollback 한다던가 하는 전략이 불가능하기 때문에 매 테스트마다 테이블을 생성하고 삭제해야 했다. 시간이 지날수록, 로직에 대한 시나리오 테스트가 늘어날 수록 테스트 수행시간은 늘어날 수 밖에 없었다.&lt;/p&gt;

&lt;p&gt;그러다가 이 문제의 해결책으로 찾은 것이 바로 &lt;a href=&quot;https://gist.github.com/agaoglu/613217&quot;&gt;MockHTable&lt;/a&gt;이다.&lt;/p&gt;

&lt;!-- more --&gt;

&lt;p&gt;&lt;a href=&quot;https://gist.github.com/agaoglu/613217&quot;&gt;MockHTable&lt;/a&gt;은 in-memory로 작동하는 HTableInterface 구현이다. HBase 스펙에 맞게 로직을 구현했기 때문에 Get/Put/Delete/Increment 등의 대부분의 operation이 동일하게 작동한다. 또한 서버 구동이나 네트웍 접근을 하지 않기 때문에 간편하게 HBase 연동 테스트를 작성하고 결과를 확인할 수 있다.&lt;/p&gt;

&lt;p&gt;하지만 MockHTable가 완벽하게 실제 HBase와 동일하게 작동하는가의 문제가 있다. MockHTable에 버그가 있을 수도 있고, HBase 버전에 따라 세부적인 작동이 달라질 수도 있다. 따라서 Unit Test 수준에서는 MockHTable로 빠르게 검증하고, Integration Test에서는 실제 HBase에 연동하여 테스트하는 것이 좋겠다. (실제로도 agaoglue 의 구현에는 실제 HBase와 다르게 작동하는 부분이 있다. )&lt;/p&gt;

&lt;p&gt;하나의 클래스라서 코드가 길어 보이지만, 단순하게 생각하면 HBase는 기본 데이터 모델인 KevValue을 분산저장하는 시스템에 불과하다. MockHTable의 소스를 보면 HBase가 KeyValue 객체를 어떻게 다루는지 대략적으로 파악을 할 수 있으므로 HBase를 이해하는데 도움이 될 수 있다.&lt;/p&gt;

&lt;p&gt;아래는 내가 원래 버전인 agaoglu의 MockHTable를 fork해서 몇 가지 문제를 수정한 버전이다.&lt;/p&gt;

&lt;script src=&quot;https://gist.github.com/odd-poet/bbc2aa93e41de6dd5475.js?file=MockHTable.java&quot;&gt; &lt;/script&gt;

&lt;ul&gt;
  &lt;li&gt;filter 로직 수정(by k-mack): https://gist.github.com/k-mack/4600133
    &lt;ul&gt;
      &lt;li&gt;agaoglue 버전은 filter 구현에 버그가 있다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;batch 수정
    &lt;ul&gt;
      &lt;li&gt;result array의 갯수와 actions의 갯수가 일치하지 않는 문제.&lt;/li&gt;
      &lt;li&gt;누락된 Increment / Append 등의 operation 처리 추가&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;exists 메소스 수정
    &lt;ul&gt;
      &lt;li&gt;버전에 따른 차이인지 모르겠지만 HBase 0.94 기준으로 exists는 단순히 get 수행후 result.isEmpty()를 체크하는 방식으로 수행된다. &lt;a href=&quot;https://gist.github.com/agaoglu/613217&quot;&gt;MockHTable&lt;/a&gt;의 구현과 달라서 내가 사용 중인 0.94 버전 기준으로 로직을 변경함.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;아래는 실제 Hbase와 동일하게 작동하는지 확인하기 위해 만든 테스트다. MockHTable 사용 중에 다른 점이 나타날 경우 여기에 테스트를 추가하면서 보완할 생각.&lt;/p&gt;

&lt;script src=&quot;https://gist.github.com/odd-poet/bbc2aa93e41de6dd5475.js?file=MockHTableTest.java&quot;&gt; &lt;/script&gt;</content><author><name>Yunsang Choi</name><email>oddpoet@gmail.com</email></author><category term="HBase" /><category term="testing" /><summary type="html">HBase 기반 시스템을 개발하면서 제일 아쉬운게 HSQL과 같이 테스트 시에 사용할 수 있는 embeded DB였다. HSQL은 실제 DB와 거의 동일한 기능을 제공하면서 로컬 머신에서 빠르게 동작하기 때문에 Unit Test 작성하고 확인할 때 빠르게 피드백을 받을 수 있게 도와주는 좋은 툴이었다. 하지만 HBase는 unit test 수행후 rollback 한다던가 하는 전략이 불가능하기 때문에 매 테스트마다 테이블을 생성하고 삭제해야 했다. 시간이 지날수록, 로직에 대한 시나리오 테스트가 늘어날 수록 테스트 수행시간은 늘어날 수 밖에 없었다. 그러다가 이 문제의 해결책으로 찾은 것이 바로 MockHTable이다.</summary></entry><entry><title type="html">TDD is dead. Long live testing</title><link href="https://oddpoet.net/blog/2014/05/07/tdd-is-dead-long-live-testing/" rel="alternate" type="text/html" title="TDD is dead. Long live testing" /><published>2014-05-07T12:52:00+09:00</published><updated>2014-05-07T12:52:00+09:00</updated><id>https://oddpoet.net/blog/2014/05/07/tdd-is-dead-long-live-testing</id><content type="html" xml:base="https://oddpoet.net/blog/2014/05/07/tdd-is-dead-long-live-testing/">&lt;p&gt;Rails를 만든 &lt;a href=&quot;http://david.heinemeierhansson.com/&quot;&gt;DHH&lt;/a&gt;가 쓴 “&lt;a href=&quot;http://david.heinemeierhansson.com/2014/tdd-is-dead-long-live-testing.html&quot;&gt;TDD is dead. Long live testing.&lt;/a&gt;“라는 글이 최근 TDDer들에게 논란이 되고 있다. 이 논쟁에는 엉클밥, 켄트백, 마틴파울러도 참여하면서(&lt;a href=&quot;https://www.facebook.com/notes/kent-beck/rip-tdd/750840194948847&quot;&gt;RIP-TDD&lt;/a&gt;,&lt;a href=&quot;https://twitter.com/martinfowler/status/460182896817733632&quot;&gt;unclebob-twitter&lt;/a&gt;, &lt;a href=&quot;https://plus.google.com/app/basic/events/ci2g23mk0lh9too9bgbp3rbut0k&quot;&gt;Is TDD DEAD?&lt;/a&gt;) 분위기가 후끈 달아오른 터라 급하게 날림번역을 해본다.&lt;/p&gt;

&lt;!-- more --&gt;

&lt;p&gt;대충 DHH의 논지는 TDD의 “test-first”가 단위 테스트에만 초점을 맞추다 보니 전체적인 관점에서 균형이 맞지 않을 뿐더러, 전체적인 시스템 설계에는 도움이 안되더라는 얘기인 듯 하다. 그리고 중간중간 다소 과격한 듯한 표현이 나오기는 하지만 DHH는 “TDD 너네들 틀렸어!”가 아니라 “Rails에서는 다른 대안을 찾겠다”는 의도의 글로 보인다(Rails가 프로젝트 생성시에 unit/funtional/integration test의 stub, fixture 및 환경 구성도 제공하는 framework이다보니 대부분 rails가 제공하는 방식을 따르게 된다). 어쨌든 DHH의 이 글 때문에 심기가 불편한 사람이 많았는지 &lt;a href=&quot;https://plus.google.com/app/basic/events/ci2g23mk0lh9too9bgbp3rbut0k&quot;&gt;Is TDD DEAD?&lt;/a&gt; 같은 토론회(?)도 진행된다니 그 결과가 사뭇 기대된다.&lt;/p&gt;

&lt;p&gt;번역 관련해서는 수사적 표현이 많다 보니 번역이 매끄럽지 못한 부분이 많다. 원문도 함께 병기했으니 참고바람.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;tdd-is-dead-long-live-testing&quot;&gt;TDD is dead. Long live testing.&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;By &lt;a href=&quot;http://david.heinemeierhansson.com/&quot;&gt;David Heinemeier Hansson&lt;/a&gt; on April 23, 2014&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Test-first fundamentalism is like abstinence-only sex ed: An unrealistic, ineffective morality campaign for self-loathing and shaming.&lt;/p&gt;

&lt;p&gt;Test-first 원칙주의는 금욕적인 성(性)교육과 유사하다. 자기비하와 자기혐오를 위한 비현실적이며 쓸모없는 도덕적 캠페인 같다.&lt;/p&gt;

&lt;p&gt;It didn’t start out like that. When I first discovered TDD, it was like a courteous invitation to a better world of writing software. A mind hack to get you going with the practice of testing where no testing had happened before. It opened my eyes to the tranquility of a well-tested code base, and the bliss of confidence it grants those making changes to software.&lt;/p&gt;

&lt;p&gt;“test-first”는 그렇게 시작되지 않았다. 내가 처음 TDD에 대해 알게되었을 때, TDD는 더 나은 소프트웨어 개발의 세계를 열어줄 친절한 초대장 같았다. 전에는 테스트가 없던 세계였다면, 테스트를 함께 할 수 있는 마음가짐을 가지게 해주었다. TDD는 잘 테스트된 코드 기반이 주는 평온함에 눈 뜨게 해주었고, 소프트웨어를 변경해야 할 때 (코드에 대한) 신뢰의 기쁨을 누리게 해주었다.&lt;/p&gt;

&lt;p&gt;The test-first part was a wonderful set of training wheels that taught me how to think about testing at a deeper level, but also some I quickly left behind.&lt;/p&gt;

&lt;p&gt;TDD의 “test-first” 부분은 깊은 수준에서의 테스트를 어떻게 고민해야 할지 가르쳐주고, 뒤로 미뤄야 할 부분을 빠르게 결정할 수 있게 해주는 놀라운 보조바퀴(역주:아동용 자전거에 연습용으로 붙이는 바퀴) 같았다.&lt;/p&gt;

&lt;p&gt;Over the years, the test-first rhetoric got louder and angrier, though. More mean-spirited. And at times I got sucked into that fundamentalist vortex, feeling bad about not following the true gospel. Then I’d try test-first for a few weeks, only to drop it again when it started hurting my designs.&lt;/p&gt;

&lt;p&gt;몇 년이 지나고, “test-first”에 대한 미사여구는 시끄러워지고 거칠어졌다. 그리고 더욱 천박해졌다. 나는 TDD 원칙주의자들의 소용돌이 속으로 빠져들게 되면서 진짜 복음서를 따르지 않는 것 같다는 나쁜 느낌을 받게 되었다. 그래서 나는 test-first로 몇 주 동안 시도해보고 그것이 내 설계를 망치기 시작할 때 그만 두곤 했다.&lt;/p&gt;

&lt;p&gt;It was yoyo cycle of pride, when I was able to adhere to the literal letter of the teachings, and a crash of despair, when I wasn’t. It felt like falling off the wagon. Something to keep quiet about. Certainly not something to admit in public. In public, I at best just alluded to not doing test-first all the time, and at worst continued to support the practice as “the right way”. I regret that now.&lt;/p&gt;

&lt;p&gt;내가 가르침을 문자 그대로 따를 수 있을 때도 있었지만, 그렇지 못할 때는 절망했다. 이것은 자부심의 요요 현상이 있었다. 마치 금주를 그만 두고 다시 술을 마시는 것 같은 느낌이었다. 뭔가 조용한 듯했지만, 확실히 공식적으로 인정하지 않는 뭔가가 있었다. 공식적으로 나는 기껏해야 항상 “test-first”로 개발하지는 않음을 암시적으로 얘기하는 것이었고, 최악은 “올바른 방식”으로써의 TDD 실천법을 계속 지원하는 것이었다. 지금은 내가 그랬던 것을 후회한다.&lt;/p&gt;

&lt;p&gt;Maybe it was necessary to use test-first as the counterintuitive ram for breaking down the industry’s sorry lack of automated, regression testing. Maybe it was a parable that just wasn’t intended to be a literal description of the day-to-day workings of software writing. But whatever it started out as, it was soon since corrupted. Used as a hammer to beat down the nonbelievers, declare them unprofessional and unfit for writing software. A litmus test.&lt;/p&gt;

&lt;p&gt;자동화된 회귀 테스트에 대한 산업계의 인식의 부족을 깨기 위해 반직관적 램(역주:기계에서 무엇을 세게 치거나 상하, 수평 이동을 하는 데 쓰이는 부분; 확실치 않음. ram이 뭐야!)으로써 “test-first”를 활용하는 것은 필요한 일일지도 모른다. 또한 소프트웨어 개발의 일상업무를 문자 그대로 표현하려는 의도는 아니었던 단순한 비유였을 수도 있다. 그러나 “test-first”가 어떻게 시작되었던지 간에, “test-first”는 훼손되었다. “test-first”를 비신자(TDD를 따르지 않는 사람들)를 깨부실 햄머로 사용하고 있으며, 비신자들을 소프트웨어 개발에 적합치 않은 비전문가로 규정하고 있다. 리트머스 시험처럼…&lt;/p&gt;

&lt;p&gt;Enough. No more. My name is David, and I do not write software test-first. I refuse to apologize for that any more, much less hide it. I’m grateful for what TDD did to open my eyes to automated regression testing, but I’ve long since moved on from the design dogma.&lt;/p&gt;

&lt;p&gt;그걸로 됐다. 내 이름은 David이고, 나는 “test-first”로 소프트웨어를 개발하지 않는다. 나는 더이상 그 사실에 대해서 사과하지 않을 것이며 그 사실을 감추지 않겠다. TDD가 자동화된 회귀 테스트에 눈을 뜨게 해준 것에 감사하지만, 나는 그 설계 신조(dogma)에서는 오래전에 벗어났다.&lt;/p&gt;

&lt;p&gt;I suggest you take a hard look at what that approach is doing to the integrity of your system design as well. If you’re willing to honestly consider the possibility that it’s not an unqualified good, it’ll be like taking the red pill. You may not like what you see after that.&lt;/p&gt;

&lt;p&gt;“test-first” 접근방법이 당신의 시스템 설계의 무결성에 대해 해줄 수 있는 것이 있는지 다시 한번 면밀히 살펴보길 바란다. 당신이 “test-first”가 완전한 선이 아닐 가능성에 대해서도 솔직하게 고민할 의향이 있다면, 그건 빨간약을 선택하는 것이다(역주:영화 매트릭스에서 네오가 빨간약을 선택한 것에 대한 비유). 당신은 그 선택 후에 보게 될 것을 좋아하지 않을 수도 있다.&lt;/p&gt;

&lt;h3 id=&quot;so-where-do-we-go-from-here&quot;&gt;So where do we go from here?&lt;/h3&gt;

&lt;h3 id=&quot;우리는-여기에서-어디로-가는가&quot;&gt;우리는 여기에서 어디로 가는가?&lt;/h3&gt;

&lt;p&gt;Step one is admitting there’s a problem. I think we’ve taken that now. Step two is to rebalance the testing spectrum from unit to system. The current fanatical TDD experience leads to a primary focus on the unit tests, because those are the tests capable of driving the code design (the original justification for test-first).&lt;/p&gt;

&lt;p&gt;첫 번째 단계는 문제가 있다는 것을 인정하는 것이다. 나는 이제 우리가 그 단계에 있다고 생각한다. 두 번째 단계는 단위 테스트에서 시스템 테스트까지의 테스트 스펙트럼에 균형을 찾는 것이다. 현재의 광신적인 TDD 경험은 단위 테스트에 주로 초점을 맞추도록 하는데, 단위 테스트가  코드 설계를 유도해내는데(이것이 “test-first”의 정당성에 대한 근거이다) 적합하기 때문이다.&lt;/p&gt;

&lt;p&gt;I don’t think that’s healthy. Test-first units leads to an overly complex web of intermediary objects and indirection in order to avoid doing anything that’s “slow”. Like hitting the database. Or file IO. Or going through the browser to test the whole system. It’s given birth to some truly horrendous monstrosities of architecture. A dense jungle of service objects, command patterns, and worse.&lt;/p&gt;

&lt;p&gt;나는 단위 테스트에 초점을 맞추는 것이 건전하다고 생각하지 않는다. “test-first”의 단위 테스트들은 매개 객체들과 “느림”을 피하기 위한 간접적인 행동들로 가득찬 완전히 복잡한 웹을 초래한다. 데이터베이스 접근을 테스트하거나, 파일 IO에 대한 테스트 혹은 전체 시스템 테스트를 위해 브라우저를 사용하는 일을 피하려고 한다. 이것은 정말 끔직한 괴물같은 아키텍쳐를 낳았다. 서비스 객체들, 커맨드 패턴들로 가득찬 복잡한 정글이다.&lt;/p&gt;

&lt;p&gt;I rarely unit test in the traditional sense of the word, where all dependencies are mocked out, and thousands of tests can close in seconds. It just hasn’t been a useful way of dealing with the testing of Rails applications. I test active record models directly, letting them hit the database, and through the use of fixtures. Then layered on top is currently a set of controller tests, but I’d much rather replace those with even higher level system tests through Capybara or similar.&lt;/p&gt;

&lt;p&gt;모든 종속성은 mock으로 처리하고 수 천개의 테스트가 수 초만에 수행되는 전통적인 의미에서의 단위 테스트를 나는 거의 사용하지 않는다. 그것은 Rails 애플리케이션의 테스트를 다루는 유용한 방법이 아니었다. 나는 Active Record(rails의 ORM) 모델을 직접 테스트하고, 그것이 픽스쳐 사용을 통해 직접 데이터베이스를 접근하게 한다. 그 다음의 상위 계층은 controller 테스트 집합인데, 나는 Capybara 같은 것을 사용해서 상위 수준의 시스템 테스트로 그것을 대체하려 한다.&lt;/p&gt;

&lt;p&gt;I think that’s the direction we’re heading. Less emphasis on unit tests, because we’re no longer doing test-first as a design practice, and more emphasis on, yes, slow, system tests. (Which btw do not need to be so slow any more, thanks to advances in parallelization and cloud runner infrastructure).&lt;/p&gt;

&lt;p&gt;나는 우리가 가고 있는 방향이 그것이라고 생각한다. 설계 실천법으로써의 “test-first”를 우리가 더이상 사용하지 않기 때문에 단위 테스트를 덜 강조할 것이고, 느린 시스템 테스트를 보다 더 강조할 것이다. (병렬수행과 클라우드 실행을 위한 인프라스트럭처의 발전 덕에 더이상 느리게 수행될 필요는 없어지고 있다)&lt;/p&gt;

&lt;p&gt;Rails can help with this transition. Today we do nothing to encourage full system tests. There’s no default answer in the stack. That’s a mistake we’re going to fix. But you don’t have to wait until that’s happening. Give Capybara a spin today, and you’ll have a good idea of where we’re heading tomorrow.&lt;/p&gt;

&lt;p&gt;Rails는 이러한 변화를 도울 수 있다. 요즘 우리는 전체 시스템 테스트를 권장하는 어떤 일도 하지 않는다. 그 스택(시스템 테스트)에서 기본 설정은 없다. 그것은 우리가 고쳐가야할 우리의 실수다. 그러나 그것이 수정되길 기다릴 필요는 없다. 당장 &lt;a href=&quot;https://github.com/jnicklas/capybara&quot;&gt;Capybara&lt;/a&gt;를 돌려보면 우리가 향하고 있는 곳에 대한 좋은 아이디어를 얻을 수 있을 것이다.&lt;/p&gt;

&lt;p&gt;But first of all take a deep breath. We’re herding some sacred cows to the slaughter right now. That’s painful and bloody. TDD has been so successful that it’s interwoven in a lot of programmer identities. TDD is not just what they do, it’s who they are. We have some serious deprogramming ahead of us as a community to get out from under that, and it’s going to take some time.&lt;/p&gt;

&lt;p&gt;그러나 먼저 심호흡을 해보자. 우리는 신성한 소(sacred cows; 역주: 지나치게 신성시되어 비판・의심이 허용되지 않는 관습・제도 등)를 지금 도축장으로 데려가고 있다. 이건 고통스러운 일이다. TDD는 성공적이었고 많은 프로그래머의 정체성과 섞여 있다. 그들이 하는 것이 TDD가 아니고, 바로 그들이 TDD가 되었다. 우리는 그곳에서 빠져나와 우리보다 앞서 커뮤니티를 심각하게 재교육을 해야 하며, 이에 시간이 걸릴 것이다.&lt;/p&gt;

&lt;p&gt;The worst thing we can do is just rush into another testing religion. I can just imagine the golden calf of “system tests only!” right now. Please don’t go there.&lt;/p&gt;

&lt;p&gt;우리가 할 수 있는 가장 좋지 않은 선택은 다른 테스트 분파로 들어가는 것이다. “system tests only”의 금송아지(역주: 유대인이 숭배했던 우상) 같은 것이 그런 것이다. 제발 그쪽으로는 가지 말길 바란다.&lt;/p&gt;

&lt;p&gt;Yes, test-first is dead to me. But rather than dance on its grave, I’d rather honor its contributions than linger on the travesties. It marked an important phase in our history, yet it’s time to move on.&lt;/p&gt;

&lt;p&gt;그렇다. 나에게 있어서 “test-first”는 죽었다. 그러나 그 무덤 위에서 춤추기보다는, 그것이 기여했던 바를 존중하여 모사꾼으로 남지는 않겠다. 이것은 우리의 역사에서 중요한 단계이며, 이제 옮겨가야할 시간이다.&lt;/p&gt;

&lt;p&gt;Long live testing.&lt;/p&gt;

&lt;p&gt;테스트여 영원하라.&lt;/p&gt;</content><author><name>Yunsang Choi</name><email>oddpoet@gmail.com</email></author><category term="TDD" /><category term="test-first" /><category term="TDD is dead" /><category term="DHH" /><summary type="html">Rails를 만든 DHH가 쓴 “TDD is dead. Long live testing.“라는 글이 최근 TDDer들에게 논란이 되고 있다. 이 논쟁에는 엉클밥, 켄트백, 마틴파울러도 참여하면서(RIP-TDD,unclebob-twitter, Is TDD DEAD?) 분위기가 후끈 달아오른 터라 급하게 날림번역을 해본다.</summary></entry><entry><title type="html">vagrant box 패키징과 공유</title><link href="https://oddpoet.net/blog/2014/04/11/share-vagrant-box/" rel="alternate" type="text/html" title="vagrant box 패키징과 공유" /><published>2014-04-11T21:14:00+09:00</published><updated>2014-04-11T21:14:00+09:00</updated><id>https://oddpoet.net/blog/2014/04/11/share-vagrant-box</id><content type="html" xml:base="https://oddpoet.net/blog/2014/04/11/share-vagrant-box/">&lt;p&gt;개발환경에서 필요한 인프라적인 요소들(db, hbase 등)을 쉽게 구축하고 공유하기 위해서
&lt;a href=&quot;http://www.vagrantup.com/&quot;&gt;vagrant&lt;/a&gt;를 종종 사용하게 되는데, 자기가 만든 &lt;strong&gt;vagrant box&lt;/strong&gt;를 패키징하고
다른 사람들에게 쉽게 공유하는 방법을 정리한다.&lt;/p&gt;

&lt;!-- more --&gt;

&lt;h2 id=&quot;packaging&quot;&gt;Packaging&lt;/h2&gt;

&lt;p&gt;현재 자신의 PC에서 세팅을 끝낸 vagrant box를 공유하기 위해서는 vagrant box 파일로
패키징해야 한다. 일반적으로 패키징할 vagrant 프로젝트 디렉토리에서 &lt;code class=&quot;highlighter-rouge&quot;&gt;vagrant package&lt;/code&gt;만 실행하면 된다.
패키징된 vm은 &lt;code class=&quot;highlighter-rouge&quot;&gt;package.box&lt;/code&gt;라는 파일로 생성된다.&lt;/p&gt;

&lt;p&gt;패키징 옵션은 &lt;a href=&quot;https://docs.vagrantup.com/v2/cli/package.html&quot;&gt;Vagrant Packaging&lt;/a&gt; 문서를
참고한다.&lt;/p&gt;

&lt;p&gt;패키징할 때는 다음과 같은 사항을 주의한다.&lt;/p&gt;

&lt;h4 id=&quot;가급적이면-vagrantfile을-함께-패키징하지-않는다&quot;&gt;가급적이면 &lt;code class=&quot;highlighter-rouge&quot;&gt;Vagrantfile&lt;/code&gt;을 함께 패키징하지 않는다.&lt;/h4&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;Vagrantfile&lt;/code&gt;을 함께 box로 패키징하면 box와 함께 설치된다.
그리고 실제 생성된 vagrant box가 부팅될때
&lt;a href=&quot;https://docs.vagrantup.com/v2/vagrantfile/index.html&quot;&gt;Vagrantfile&lt;/a&gt; 문서에 나와있는 순서대로 패키징됐던 &lt;code class=&quot;highlighter-rouge&quot;&gt;Vagrantfile&lt;/code&gt;이 먼저
로드되고, 그 다음에 &lt;code class=&quot;highlighter-rouge&quot;&gt;~/.vagrant.d&lt;/code&gt;에 있는 &lt;code class=&quot;highlighter-rouge&quot;&gt;Vagrantfile&lt;/code&gt;, 프로젝트의 &lt;code class=&quot;highlighter-rouge&quot;&gt;Vagrantfile&lt;/code&gt; 순으로
로딩된다.&lt;/p&gt;

&lt;p&gt;이때 &lt;code class=&quot;highlighter-rouge&quot;&gt;warning: already initialized constant VAGRANTFILE_API_VERSION&lt;/code&gt; 과 같은
오류메시지를 만나게 된다. ruby에서 동일한 상수에 또 다시 값을 assign할 때 나오는
메시지인데, 여러 개의 &lt;code class=&quot;highlighter-rouge&quot;&gt;Vagrantfile&lt;/code&gt;이 읽히면서 파일 앞부분에 있는
&lt;code class=&quot;highlighter-rouge&quot;&gt;VAGRANTFILE_API_VERSION = &quot;2&quot;&lt;/code&gt; 라인이 계속 다시 evaluate 되기 때문에 발생하는 에러다.
기능에는 지장이 없지만 보기에 흐뭇하지 않다.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;Vagrantfile&lt;/code&gt; 파일에는 네트웍설정 등의 vm 외부 인터페이스 관련 설정만 있으므로
굳이 공유할 vagrant box에 &lt;code class=&quot;highlighter-rouge&quot;&gt;Vagrantfile&lt;/code&gt;을 embeded하지 않도록 하자.&lt;/p&gt;

&lt;h4 id=&quot;firewall-disable&quot;&gt;Firewall disable&lt;/h4&gt;

&lt;p&gt;일반적으로 vagrant box 내에 특정 포트의 service를 띄워놓고 host os에서 그 서비스에
접근하게 되므로 &lt;code class=&quot;highlighter-rouge&quot;&gt;iptable&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;ip6table&lt;/code&gt; 서비스는 disable 시키는게 좋다.&lt;/p&gt;

&lt;p&gt;Centos의 경우 아래와 같은 명령으로 부팅시 &lt;code class=&quot;highlighter-rouge&quot;&gt;iptable&lt;/code&gt; 서비스가 실행되는 걸 막을 수 있다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ chkconfig iptables off
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;리눅스 배포본 별 방화벽을 끄는 방법은
&lt;a href=&quot;http://www.cyberciti.biz/faq/turn-on-turn-off-firewall-in-linux/&quot;&gt;HowTo Disable The Iptables Firewall in Linux&lt;/a&gt;을 참고한다.&lt;/p&gt;

&lt;h4 id=&quot;패키징-전에-etcudevrulesd70-persistent-netrules을-지운다&quot;&gt;패키징 전에 &lt;code class=&quot;highlighter-rouge&quot;&gt;/etc/udev/rules.d/70-persistent-net.rules&lt;/code&gt;을 지운다&lt;/h4&gt;

&lt;p&gt;자기가 packaging했던 vagrant box을 설치하고 부팅해보면
아래와 같은 메시지를 만나게 되는 경우가 있다. 이 오류가 발생하면
guest os의 네트웍에 접근하지 못할 수 있다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[default] Configuring and enabling network interfaces...
The following SSH command responded with a non-zero exit status.
Vagrant assumes that this means the command failed!

/sbin/ifup eth1 2&amp;gt; /dev/null
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;그 이유는 &lt;a href=&quot;https://wiki.debian.org/udev&quot;&gt;udev&lt;/a&gt;가 디바이스를 자동으로 감지하고
그 내용을 &lt;code class=&quot;highlighter-rouge&quot;&gt;/etc/udev/rules.d/70-persistent-net.rules&lt;/code&gt; 파일에 쓰는데,
거기에는 &lt;code class=&quot;highlighter-rouge&quot;&gt;eth0&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;eth1&lt;/code&gt; 과 같은 network card 디바이스가 포함된다.&lt;/p&gt;

&lt;p&gt;그런데 vagrant가 vm을 초기화할 때 network card의 &lt;code class=&quot;highlighter-rouge&quot;&gt;MAC address&lt;/code&gt;를 생성하기 때문에
패키징했던 vm과 그것을 기반으로 생성된 새로운 vm의 MAC address가 달라져서 발생하는 오류다.&lt;/p&gt;

&lt;p&gt;따라서 아래와 같이 packaging 전에 이 파일을 지운다. (지워도 자동생성되는 파일이다)&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;vagrant ssh -c &quot;sudo rm /etc/udev/rules.d/70-persistent-net.rules&quot;
vagrant halt
vagrant package
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;h2 id=&quot;box-file-upload&quot;&gt;Box file upload&lt;/h2&gt;

&lt;p&gt;패키징된 vagrant box 파일은 사이즈가 수 백 MByte를 넘기 때문에
일반적인 파일 호스팅 서비스를 이용하기가 쉽지 않다.&lt;/p&gt;

&lt;p&gt;아래와 같은 파일 호스팅 서비스를 고려해보자.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Dropbox
    &lt;ul&gt;
      &lt;li&gt;용량이 몇GB 밖에 안되서 넉넉하진 않지만 가장 손쉬운 방법이다.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;github
    &lt;ul&gt;
      &lt;li&gt;git repository에는 100MB 제한 때문에 올릴 수는 없다.&lt;/li&gt;
      &lt;li&gt;하지만 github의 release 기능을 이용하면 그 보다 큰 파일을 올릴 수 있다.&lt;/li&gt;
      &lt;li&gt;release에 포함시킬 수 있는 용량제한이 얼마인지는 모르겠지만, 900Mb 정도 되는 파일까지는 잘 올라가더라.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;vagrant-box-공유하기&quot;&gt;Vagrant box 공유하기&lt;/h2&gt;

&lt;p&gt;호스팅 서비스에 올린 box 파일의 url을 직접 사용해서 다른 사람들에게 box를 공유할 수도 있다.&lt;/p&gt;

&lt;p&gt;이런 경우 사용자는 아래와 같이 box를 받아서 사용하게 된다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;vagrant init yourbox http://very.long.url/very/long/path/yourbox.box
vagrant up
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;긴 이름 때문에 공유하기도 쉽지 않을 뿐더러 URL을 직접 알려주지 않으면 힘들게 만들고
패키징한 vm box를 다른 사람들이 알 수도 없다. &lt;a href=&quot;http://www.vagrantbox.es/&quot;&gt;vagrantbox.es&lt;/a&gt;에 등록하는 방법도 있지만
더 좋은 방법이 있다. &lt;a href=&quot;https://vagrantcloud.com/&quot;&gt;vagrantcloud.com&lt;/a&gt;에 등록하는 것이다.&lt;/p&gt;

&lt;h4 id=&quot;vagrantcloud&quot;&gt;vagrantcloud&lt;/h4&gt;

&lt;p&gt;&lt;img src=&quot;/assets/include/2014-04/vagrantcloud.com.jpg&quot; alt=&quot;Vagrantcloud.com&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://vagrantcloud.com/&quot;&gt;vagrantcloud.com&lt;/a&gt;에 자신의 박스를 등록하면 &lt;code class=&quot;highlighter-rouge&quot;&gt;username/boxname&lt;/code&gt; 형태로 box의 식별자가
만들어지고, &lt;a href=&quot;https://vagrantcloud.com/&quot;&gt;vagrantcloud.com&lt;/a&gt; 내에서 &lt;strong&gt;검색이 가능&lt;/strong&gt;하며 &lt;strong&gt;version 관리&lt;/strong&gt;,
&lt;strong&gt;다운로드 통계&lt;/strong&gt; 등의 기능도 제공된다.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://vagrantcloud.com/&quot;&gt;vagrantcloud.com&lt;/a&gt;에 등록되면 다른 사람들은 &lt;a href=&quot;https://vagrantcloud.com/oddpoet/hbase-0.94&quot;&gt;oddpoet/hbase-0.94&lt;/a&gt;와 같은 box 설명 페이지를
볼 수 있고 내가 만든 box를 아래처럼 사용하게 될 것이다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;vagrant init hbase oddpoet/hbase-0.94
vagrant up
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;h4 id=&quot;vagrantcloud에-box-등록하기&quot;&gt;vagrantcloud에 box 등록하기&lt;/h4&gt;

&lt;p&gt;&lt;a href=&quot;https://vagrantcloud.com/&quot;&gt;vagrantcloud.com&lt;/a&gt;에 계정을 만들고 &lt;strong&gt;Create Box&lt;/strong&gt; 메뉴를 선택하면 아래와 같은 화면을
보인다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/include/2014-04/vagrantcloud-create-box.png&quot; alt=&quot;Create Box&quot; /&gt;&lt;/p&gt;

&lt;p&gt;box에 대한 이름과 설명을 등록하면 box의 식별자가 생성된다. (e.g. &lt;code class=&quot;highlighter-rouge&quot;&gt;oddpoet/hbase-0.94&lt;/code&gt;)
그 후에는 버전을 생성한다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/include/2014-04/vagrantcloud-create-version.png&quot; alt=&quot;Create new version&quot; /&gt;&lt;/p&gt;

&lt;p&gt;버전이 생성되면 버전에 provider를 추가 할 수 있게 된다. provider는 &lt;em&gt;vitualbox&lt;/em&gt;, &lt;em&gt;vmware&lt;/em&gt; 등의
가상머신 종류를 의미하고, 동일한 버전의 box를 provider 종류별로 등록할 수 있다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/include/2014-04/vagrantcloud-created-version.png&quot; alt=&quot;Created version&quot; /&gt;&lt;/p&gt;

&lt;p&gt;아래는 provider를 등록하는 화면인데, 여기에 실제 box URL을 등록하면 된다.
아직 자체적인 파일 호스팅은 지원하지 않는다. 그건 베타버전인 탓도 있겠지만, 파일 호스팅을 유료로 지원할 생각인 것 같다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/include/2014-04/vagrantcloud-create-provider.png&quot; alt=&quot;Create provider&quot; /&gt;&lt;/p&gt;

&lt;p&gt;provider까지 등록하고 나면 release할 수 있게 된다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/include/2014-04/vagrantcloud-release.png&quot; alt=&quot;release box&quot; /&gt;&lt;/p&gt;

&lt;p&gt;release 후에는 다른 사람들이 &lt;a href=&quot;https://vagrantcloud.com/&quot;&gt;vagrantcloud.com&lt;/a&gt;에서 등록된 box를 검색할 수 있고,
&lt;code class=&quot;highlighter-rouge&quot;&gt;vagrant box add&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;vagrant init boxname username/boxname&lt;/code&gt; 형태로 등록된 박스를 사용할 수 있게 된다.&lt;/p&gt;</content><author><name>Yunsang Choi</name><email>oddpoet@gmail.com</email></author><category term="vagrant" /><summary type="html">개발환경에서 필요한 인프라적인 요소들(db, hbase 등)을 쉽게 구축하고 공유하기 위해서 vagrant를 종종 사용하게 되는데, 자기가 만든 vagrant box를 패키징하고 다른 사람들에게 쉽게 공유하는 방법을 정리한다.</summary></entry><entry><title type="html">pow와 apache를 함께 실행하기</title><link href="https://oddpoet.net/blog/2013/12/10/pow-with-apache/" rel="alternate" type="text/html" title="pow와 apache를 함께 실행하기" /><published>2013-12-10T13:23:00+09:00</published><updated>2013-12-10T13:23:00+09:00</updated><id>https://oddpoet.net/blog/2013/12/10/pow-with-apache</id><content type="html" xml:base="https://oddpoet.net/blog/2013/12/10/pow-with-apache/">&lt;p&gt;OSX에서 개발할 때 호스트 설정 없이 로컬에 구동 중인 서버에 접근하기 위해
&lt;a href=&quot;http://pow.cx/&quot;&gt;pow&lt;/a&gt;를 애용하는데, pow를 설치하고 나면 pow가 &lt;code class=&quot;highlighter-rouge&quot;&gt;localhost&lt;/code&gt; 접근을
하이재킹하기 때문에 로컬 호스트의 apache에 접근할 수 없게 된다.
이에 대한 해결책은 다음과 같다.&lt;/p&gt;

&lt;!--more--&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;curl get.pow.cx/uninstall.sh | sh &lt;span class=&quot;c&quot;&gt;#if you have pow installed&lt;/span&gt;
&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'export POW_DST_PORT=88'&lt;/span&gt; &amp;gt;&amp;gt; ~/.powconfig
&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;sudo curl https://gist.github.com/soupmatt/1058580/raw/zzz_pow.conf -o /private/etc/apache2/other/zzz_pow.conf
&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;sudo apachectl restart
&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;curl get.pow.cx | sh
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;그리고 &lt;code class=&quot;highlighter-rouge&quot;&gt;/etc/apache2/httpd.conf&lt;/code&gt;에 아래 설정이 주석해제되었는지 확인한다.&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Include /private/etc/apache2/extra/httpd-vhosts.conf
Include /private/etc/apache2/other/*.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;위 설정에 대한 자세한 설명은 아래 링크를 참조한다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/37signals/pow/wiki/Running-Pow-with-Apache&quot;&gt;Running Pow with Apache&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><author><name>Yunsang Choi</name><email>oddpoet@gmail.com</email></author><category term="pow" /><category term="apache" /><summary type="html">OSX에서 개발할 때 호스트 설정 없이 로컬에 구동 중인 서버에 접근하기 위해 pow를 애용하는데, pow를 설치하고 나면 pow가 localhost 접근을 하이재킹하기 때문에 로컬 호스트의 apache에 접근할 수 없게 된다. 이에 대한 해결책은 다음과 같다. $ curl get.pow.cx/uninstall.sh | sh #if you have pow installed $ echo 'export POW_DST_PORT=88' &amp;gt;&amp;gt; ~/.powconfig $ sudo curl https://gist.github.com/soupmatt/1058580/raw/zzz_pow.conf -o /private/etc/apache2/other/zzz_pow.conf $ sudo apachectl restart $ curl get.pow.cx | sh 그리고 /etc/apache2/httpd.conf에 아래 설정이 주석해제되었는지 확인한다. Include /private/etc/apache2/extra/httpd-vhosts.conf Include /private/etc/apache2/other/*.conf 위 설정에 대한 자세한 설명은 아래 링크를 참조한다. Running Pow with Apache</summary></entry><entry><title type="html">UnitTest에 embeded HSQL 사용하기</title><link href="https://oddpoet.net/blog/2013/11/29/hsql-for-testing/" rel="alternate" type="text/html" title="UnitTest에 embeded HSQL 사용하기" /><published>2013-11-29T04:03:00+09:00</published><updated>2013-11-29T04:03:00+09:00</updated><id>https://oddpoet.net/blog/2013/11/29/hsql-for-testing</id><content type="html" xml:base="https://oddpoet.net/blog/2013/11/29/hsql-for-testing/">&lt;p&gt;현재 회사(SKP)의 웃기는 네트웍 정책 덕에 예전보다 unit test 뿐아니라
개발환경에서도 embeded DB를 더 자주 사용하고 있는 편인데,
UnitTest 용으로 embeded DB로 HSQL을 사용할 때 SQL 호환성 이슈를 피하는 방법을 정리한다.
(물론 Spring+MyBatis+MySQL 환경)&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;일단 아래와 같이 embeded DB로 HSQL을 설정한다.&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;beans&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;xmlns=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://www.springframework.org/schema/beans&quot;&lt;/span&gt;
       &lt;span class=&quot;na&quot;&gt;xmlns:xsi=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&lt;/span&gt;
       &lt;span class=&quot;na&quot;&gt;xmlns:jdbc=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://www.springframework.org/schema/jdbc&quot;&lt;/span&gt;
       &lt;span class=&quot;na&quot;&gt;xsi:schemaLocation=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
        &lt;span class=&quot;err&quot;&gt;http://www.springframework.org/schema/beans&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;http://www.springframework.org/schema/beans/spring-beans.xsd&lt;/span&gt;
        &lt;span class=&quot;err&quot;&gt;http://www.springframework.org/schema/jdbc&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;http://www.springframework.org/schema/jdbc/spring-jdbc.xsd&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;nt&quot;&gt;&amp;lt;jdbc:embedded-database&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;dataSource&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;HSQL&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;jdbc:script&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;location=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;classpath:database/schema.sql&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;jdbc:script&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;location=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;classpath:database/init-data.sql&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/jdbc:embedded-database&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/beans&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;보통 위와 같이 Table 생성하는 스크립트(&lt;code class=&quot;highlighter-rouge&quot;&gt;schema.sql&lt;/code&gt;)와 초기 데이터를 생성하는 스크립트(&lt;code class=&quot;highlighter-rouge&quot;&gt;init-data.sql&lt;/code&gt;)을 나누어서 작성한다.&lt;/p&gt;

&lt;p&gt;HSQL의 기본 SQL syntax는 다른 Database들과 완전히 호환되지 않는다.
따라서 실제 운영 및 개발환경에서 MySQL을 사용하는 경우 테이블 생성 스크립트 부터 HSQL에서는 정상적으로 실행되지 않는다.&lt;/p&gt;

&lt;p&gt;예를 들면, HSQL은 &lt;code class=&quot;highlighter-rouge&quot;&gt;AUTO_INCREMENT&lt;/code&gt; 키워드 대신 &lt;code class=&quot;highlighter-rouge&quot;&gt;IDENTITY&lt;/code&gt;라는 키워드를 사용한다.
(&lt;code class=&quot;highlighter-rouge&quot;&gt;IDENTITY&lt;/code&gt; 키워드는 &lt;code class=&quot;highlighter-rouge&quot;&gt;PRIMARY KEY&lt;/code&gt;의 의미도 가지기 때문에 &lt;code class=&quot;highlighter-rouge&quot;&gt;AUTO_INCREMENT&lt;/code&gt;와 완전히 동일하지도 않다)&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;-- MySQL&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sample&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;INTEGER&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AUTO_INCREMENT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;VARCHAR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;KEY&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;-- HSQL&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sample&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;INTEGER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;IDENTITY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;VARCHAR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;다행히도 HSQL은 Oracle, MSSQL, MySQL등의 다른 Database의 syntax를 지원하는 기능이 있다.
그래서 아래처럼 &lt;code class=&quot;highlighter-rouge&quot;&gt;schema.sql&lt;/code&gt;을 작성하면 HSQL에서 MySQL의 syntax를 그대로 쓸 수 있다.
(HSQL 2.0 이상에서 지원)&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;-- for mysql&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;SET&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DATABASE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;SQL&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SYNTAX&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MYS&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TRUE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;-- for oracle&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;-- SET DATABASE SQL SYNTAX ORA TRUE;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sample&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;INTEGER&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AUTO_INCREMENT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;VARCHAR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;KEY&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;하지만 이런 기능에는 빈틈이 있기 마련인데, 이걸로는 &lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;selectKey&amp;gt;&lt;/code&gt;를 사용할 때 문제가 생긴다.
즉, Insert된 후 auto increment인 column의 값을 가져오는 쿼리에 대한 호환성 문제를
해결해주지는 않는다. 참고로 MySQL에서는 &lt;code class=&quot;highlighter-rouge&quot;&gt;SELECT LAST_INSERT_ID()&lt;/code&gt;라는 sql을 사용하고,
HSQL에서는 &lt;code class=&quot;highlighter-rouge&quot;&gt;CALL IDENTITY()&lt;/code&gt;라는 sql을 사용한다.&lt;/p&gt;

&lt;p&gt;HSQL을 UnitTest용으로 사용할 때 &lt;code class=&quot;highlighter-rouge&quot;&gt;selectKey&lt;/code&gt;의 문제는 JDBC 3.0의 &lt;code class=&quot;highlighter-rouge&quot;&gt;getGeneratedKey()&lt;/code&gt;
기능을 사용하여 피할 수 있다. (ibatis에서는 안되고 mybatis에서만 가능하다는 얘기)&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@lombok&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Data&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Sample&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Integer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;위와 같은 Model 클래스를 Insert하는 SQL map을 아래와 같이 쓸 수 있다.
&lt;code class=&quot;highlighter-rouge&quot;&gt;&amp;lt;selectKey&amp;gt;&lt;/code&gt; 엘리먼트를 사용하지 않고 &lt;code class=&quot;highlighter-rouge&quot;&gt;useGeneratedKeys&lt;/code&gt;라는 속성을 사용하면
argument로 넘어왔던 &lt;code class=&quot;highlighter-rouge&quot;&gt;Sample&lt;/code&gt; 객체의 &lt;code class=&quot;highlighter-rouge&quot;&gt;id&lt;/code&gt;값이 채워진다.&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;&amp;lt;!-- selectKey를 쓰지 않고, JDBC의 getGeneratedKeys를 사용한다. --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;insert&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;insertSample&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;parameterType=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Sample&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;useGeneratedKeys=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;true&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;keyProperty=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    INSERT INTO sample
    (name) values #{name}
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/insert&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;그 외에 HSQL의 SQL 호환기능에 대한 유의사항은 아래 링크를 참고한다.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://hsqldb.org/doc/2.0/guide/management-chapt.html#mtc_compatibility_mysql&quot;&gt;HSQL: Compatibility with Other RDBMS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><author><name>Yunsang Choi</name><email>oddpoet@gmail.com</email></author><category term="test" /><category term="testing" /><category term="hsql" /><category term="spring" /><category term="mybatis" /><category term="embeded DB" /><summary type="html">현재 회사(SKP)의 웃기는 네트웍 정책 덕에 예전보다 unit test 뿐아니라 개발환경에서도 embeded DB를 더 자주 사용하고 있는 편인데, UnitTest 용으로 embeded DB로 HSQL을 사용할 때 SQL 호환성 이슈를 피하는 방법을 정리한다. (물론 Spring+MyBatis+MySQL 환경) 일단 아래와 같이 embeded DB로 HSQL을 설정한다. &amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt; &amp;lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; xmlns:jdbc=&quot;http://www.springframework.org/schema/jdbc&quot; xsi:schemaLocation=&quot; http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd&quot;&amp;gt; &amp;lt;jdbc:embedded-database id=&quot;dataSource&quot; type=&quot;HSQL&quot;&amp;gt; &amp;lt;jdbc:script location=&quot;classpath:database/schema.sql&quot;/&amp;gt; &amp;lt;jdbc:script location=&quot;classpath:database/init-data.sql&quot;/&amp;gt; &amp;lt;/jdbc:embedded-database&amp;gt; &amp;lt;/beans&amp;gt; 보통 위와 같이 Table 생성하는 스크립트(schema.sql)와 초기 데이터를 생성하는 스크립트(init-data.sql)을 나누어서 작성한다. HSQL의 기본 SQL syntax는 다른 Database들과 완전히 호환되지 않는다. 따라서 실제 운영 및 개발환경에서 MySQL을 사용하는 경우 테이블 생성 스크립트 부터 HSQL에서는 정상적으로 실행되지 않는다. 예를 들면, HSQL은 AUTO_INCREMENT 키워드 대신 IDENTITY라는 키워드를 사용한다. (IDENTITY 키워드는 PRIMARY KEY의 의미도 가지기 때문에 AUTO_INCREMENT와 완전히 동일하지도 않다) -- MySQL CREATE TABLE sample ( id INTEGER AUTO_INCREMENT, name VARCHAR(100), PRIMARY KEY (id) ); -- HSQL CREATE TABLE sample ( id INTEGER IDENTITY, name VARCHAR(100) ); 다행히도 HSQL은 Oracle, MSSQL, MySQL등의 다른 Database의 syntax를 지원하는 기능이 있다. 그래서 아래처럼 schema.sql을 작성하면 HSQL에서 MySQL의 syntax를 그대로 쓸 수 있다. (HSQL 2.0 이상에서 지원) -- for mysql SET DATABASE SQL SYNTAX MYS TRUE; -- for oracle -- SET DATABASE SQL SYNTAX ORA TRUE; CREATE TABLE sample ( id INTEGER AUTO_INCREMENT, name VARCHAR(100), PRIMARY KEY (id) ); 하지만 이런 기능에는 빈틈이 있기 마련인데, 이걸로는 &amp;lt;selectKey&amp;gt;를 사용할 때 문제가 생긴다. 즉, Insert된 후 auto increment인 column의 값을 가져오는 쿼리에 대한 호환성 문제를 해결해주지는 않는다. 참고로 MySQL에서는 SELECT LAST_INSERT_ID()라는 sql을 사용하고, HSQL에서는 CALL IDENTITY()라는 sql을 사용한다. HSQL을 UnitTest용으로 사용할 때 selectKey의 문제는 JDBC 3.0의 getGeneratedKey() 기능을 사용하여 피할 수 있다. (ibatis에서는 안되고 mybatis에서만 가능하다는 얘기) @lombok.Data public class Sample { private Integer id; private String name; } 위와 같은 Model 클래스를 Insert하는 SQL map을 아래와 같이 쓸 수 있다. &amp;lt;selectKey&amp;gt; 엘리먼트를 사용하지 않고 useGeneratedKeys라는 속성을 사용하면 argument로 넘어왔던 Sample 객체의 id값이 채워진다. &amp;lt;!-- selectKey를 쓰지 않고, JDBC의 getGeneratedKeys를 사용한다. --&amp;gt; &amp;lt;insert id=&quot;insertSample&quot; parameterType=&quot;Sample&quot; useGeneratedKeys=&quot;true&quot; keyProperty=&quot;id&quot;&amp;gt; INSERT INTO sample (name) values #{name} &amp;lt;/insert&amp;gt; 그 외에 HSQL의 SQL 호환기능에 대한 유의사항은 아래 링크를 참고한다. HSQL: Compatibility with Other RDBMS</summary></entry><entry><title type="html">Jolokia: JMX-HTTP bridge</title><link href="https://oddpoet.net/blog/2013/09/26/jolokia-jmx-http-bridge/" rel="alternate" type="text/html" title="Jolokia: JMX-HTTP bridge" /><published>2013-09-26T23:17:00+09:00</published><updated>2013-09-26T23:17:00+09:00</updated><id>https://oddpoet.net/blog/2013/09/26/jolokia-jmx-http-bridge</id><content type="html" xml:base="https://oddpoet.net/blog/2013/09/26/jolokia-jmx-http-bridge/">&lt;p&gt;요즘 python으로 host monitoring 시스템을 만드는 중인데,
java가 아니다보니 JVM의 JMX 연결을 할 수 없는 문제가 발생했다.
그래서 구글링 중에 &lt;a href=&quot;http://www.jolokia.org/&quot;&gt;Jolokia&lt;/a&gt;라는 녀석을 발견했는데, JMX-HTTP bridge를 제공해준다.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Jolokia&lt;/em&gt;는 HTTP를 통해서 MBean의 속성에 접근할 수 있게 해주며, &lt;code class=&quot;highlighter-rouge&quot;&gt;JSON&lt;/code&gt; 형식으로 결과를 돌려준다.
따라서 &lt;em&gt;Jolokia agent&lt;/em&gt;를 띄워놓으면 Java가 아닌 언어를 쓰더라도 &lt;code class=&quot;highlighter-rouge&quot;&gt;HTTP/JSON&lt;/code&gt;으로 JVM MBean에
접근할 수 있다.&lt;/p&gt;

&lt;!-- more --&gt;

&lt;h2 id=&quot;agent-mode&quot;&gt;Agent Mode&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Jolokia&lt;/em&gt;는 다양한 상황에서 agent로 작동할 수 있게 binary를 제공하는데, 제공하는 agent들은
다음과 같다.&lt;/p&gt;

&lt;dl&gt;
  &lt;dt&gt;WAR Agent&lt;/dt&gt;
  &lt;dd&gt;JEE서버에 web application으로 작동하는 Agent&lt;/dd&gt;
  &lt;dt&gt;OSGi Agent&lt;/dt&gt;
  &lt;dd&gt;OSGi 컨테이너 내에서 작동하는 Agent&lt;/dd&gt;
  &lt;dt&gt;JVM Agent&lt;/dt&gt;
  &lt;dd&gt;Oracle/Sun 기반의 JVM(version 6 이상)에 작동하는 Agent&lt;/dd&gt;
  &lt;dt&gt;Mule Agent&lt;/dt&gt;
  &lt;dd&gt;Mule ESB 내에서 작동하는 Agent&lt;/dd&gt;
&lt;/dl&gt;

&lt;p&gt;&lt;img src=&quot;/assets/include/2013-09/jolokia-agent-mode.png&quot; alt=&quot;Jolokia as Agent&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;proxy-mode&quot;&gt;Proxy Mode&lt;/h2&gt;

&lt;p&gt;Proxy 모드는 &lt;em&gt;jolokia agent&lt;/em&gt;를 target platform에 배포할 수 없는 경우를 위한 방법이다.
&lt;em&gt;jolokia&lt;/em&gt;가 대상 JVM에 JMX로 연결하고 MBean에 접근하고, &lt;em&gt;Jolokia client&lt;/em&gt;에 &lt;code class=&quot;highlighter-rouge&quot;&gt;HTTP/JSON&lt;/code&gt;
인터페이스를 제공해주게 된다.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/include/2013-09/jolokia-proxy-mode.png&quot; alt=&quot;Jolokia as Proxy&quot; /&gt;&lt;/p&gt;

&lt;p&gt;나의 경우 모니터링 대상 호스트의 Tomcat에 jolokia를 직접 배포할 수 없는 상황이므로,
모니터링을 위해 jolokia를 proxy 모드로 실행하는 방식을 선택했다.&lt;/p&gt;

&lt;h3 id=&quot;다운로드&quot;&gt;다운로드&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;http://www.jolokia.org/download.html&quot;&gt;Jolokia Download&lt;/a&gt; 페이지에서 JVM-Agent를 다운로드한다.
파일은 &lt;code class=&quot;highlighter-rouge&quot;&gt;jolokia-jvm-VERSION-agent.jar&lt;/code&gt; 형식의 이름이다.&lt;/p&gt;

&lt;h3 id=&quot;실행방법&quot;&gt;실행방법&lt;/h3&gt;

&lt;p&gt;아래처럼 아무 arguments없이 실행하면 실행중인 JVM process 목록을 pid와 함께 보여준다.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;java -jar jolokia-jvm-1.1.3-agent.jar
79182   org.apache.catalina.startup.Bootstrap start
83727   jolokia-jvm-1.1.3-agent.jar
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;start&lt;/code&gt; command와 &lt;em&gt;pid&lt;/em&gt; 혹은 이름에 대한 &lt;em&gt;정규식&lt;/em&gt;을 argument로 주면
&lt;em&gt;Jolokia&lt;/em&gt;는 타겟 JVM에 attatch되어 시작된다.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;java -jar jolokia-jvm-1.1.3-agent.jar start catalina
Started Jolokia &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;process matching &lt;span class=&quot;s2&quot;&gt;&quot;catalina&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;PID: 79182&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
http://localhost:8778/jolokia/
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;세부 실행 옵션들은 &lt;code class=&quot;highlighter-rouge&quot;&gt;--help&lt;/code&gt; 로 확인할 것&lt;/p&gt;

&lt;h3 id=&quot;mbean에-접근하기&quot;&gt;MBean에 접근하기&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Jolokia&lt;/em&gt;가 실행되면 아래처럼 &lt;code class=&quot;highlighter-rouge&quot;&gt;/jolokia/read/MBean_OBJECT_NAME&lt;/code&gt; 형식의 URL로
MBean 속성을 읽을 수 있다. 이 부분은 &lt;em&gt;Jolokia&lt;/em&gt;가 Agent 모드로 구동될 때도 마찬가지이다.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gp&quot;&gt;$ &lt;/span&gt;curl http://localhost:8778/jolokia/read/java.lang:type&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;Memory
&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;timestamp&quot;&lt;/span&gt;:1380207665,&lt;span class=&quot;s2&quot;&gt;&quot;status&quot;&lt;/span&gt;:200,&lt;span class=&quot;s2&quot;&gt;&quot;request&quot;&lt;/span&gt;:&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;mbean&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;java.lang:type=Memory&quot;&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;type&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;read&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;:&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Verbose&quot;&lt;/span&gt;:false,&lt;span class=&quot;s2&quot;&gt;&quot;ObjectPendingFinalizationCount&quot;&lt;/span&gt;:0,&lt;span class=&quot;s2&quot;&gt;&quot;NonHeapMemoryUsage&quot;&lt;/span&gt;:&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;max&quot;&lt;/span&gt;:136314880,&lt;span class=&quot;s2&quot;&gt;&quot;committed&quot;&lt;/span&gt;:24313856,&lt;span class=&quot;s2&quot;&gt;&quot;init&quot;&lt;/span&gt;:24313856,&lt;span class=&quot;s2&quot;&gt;&quot;used&quot;&lt;/span&gt;:19809328&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;HeapMemoryUsage&quot;&lt;/span&gt;:&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;max&quot;&lt;/span&gt;:1043922944,&lt;span class=&quot;s2&quot;&gt;&quot;committed&quot;&lt;/span&gt;:88735744,&lt;span class=&quot;s2&quot;&gt;&quot;init&quot;&lt;/span&gt;:73310080,&lt;span class=&quot;s2&quot;&gt;&quot;used&quot;&lt;/span&gt;:33035216&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;,&lt;span class=&quot;s2&quot;&gt;&quot;ObjectName&quot;&lt;/span&gt;:&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;objectName&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;java.lang:type=Memory&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</content><author><name>Yunsang Choi</name><email>oddpoet@gmail.com</email></author><category term="java" /><category term="monitoring" /><category term="jmx" /><category term="jolokia" /><summary type="html">요즘 python으로 host monitoring 시스템을 만드는 중인데, java가 아니다보니 JVM의 JMX 연결을 할 수 없는 문제가 발생했다. 그래서 구글링 중에 Jolokia라는 녀석을 발견했는데, JMX-HTTP bridge를 제공해준다. Jolokia는 HTTP를 통해서 MBean의 속성에 접근할 수 있게 해주며, JSON 형식으로 결과를 돌려준다. 따라서 Jolokia agent를 띄워놓으면 Java가 아닌 언어를 쓰더라도 HTTP/JSON으로 JVM MBean에 접근할 수 있다.</summary></entry></feed>