http://www.google.com/////// 이란 문자열에서 뒤의 /를 모두 빼려면 어떻게 할까?


파이썬에서는 아주 간단히 해결할 수 있다.


"http://www.google.com/get/order/".strip("/")

->http://www.google.com/get/order


"http://www.google.com/get/order////".strip("/")

->http://www.google.com/get/order


Posted by '김용환'
,

spark / sbt 1.3.0-RC1를 사용 중이다. 


sbt test를 실행해

테스트 코드를 모두 완료하고 종료할 때 Java의 ShutdownHookManager에서 Can not access 또는 Can not load 예외가 발생할 수 있다. 

이는 jvm shutdown하면서 shutdownhook에서 정리할 내용보다 먼저 자원이 정리되면서 발생하는 문제이다. 



$ sbt test 

...

INFO: Illegal access: this web application instance has been stopped already.  Could not load org.apache.hadoop.util.ShutdownHookManager$2.  The eventual following stack trace is caused by an error thrown for debugging purposes as well as to attempt to terminate the thread which caused the illegal access, and has no functional impact.

java.lang.IllegalStateException

  at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1600)

  at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1559)

  at org.apache.hadoop.util.ShutdownHookManager.getShutdownHooksInOrder(ShutdownHookManager.java:124)

  at org.apache.hadoop.util.ShutdownHookManager$1.run(ShutdownHookManager.java:52




sbt 실행시 아래와 같은 설정을 추가한다. 더 이상 해당 에러는 발생하지 않는다. 

-Dsbt.classloader.close=false


'scala' 카테고리의 다른 글

[scala] jackson, ujson  (0) 2019.10.04
[펌] [spark] spark graceful하게 종료하는 방법  (0) 2019.10.02
[scala] 문자열의 값에 해당하는 enum 타입 얻어오기  (0) 2019.09.20
[sbt] 1.3.0  (0) 2019.09.06
scala cats 공부 자료.  (1) 2019.06.18
Posted by '김용환'
,



How to get applicationId of Spark application deployed to YARN in ...


https://spark.apache.org/docs/2.3.0/api/java/org/apache/spark/SparkContext.html


applicationId

public String applicationId()
A unique identifier for the Spark application. Its format depends on the scheduler implementation. (i.e. in case of local spark app something like 'local-1433865536131' in case of YARN something like 'application_1433865536131_34483' in case of MESOS something like 'driver-20170926223339-0001' )




val appId = spark.sparkContext.applicationId


Posted by '김용환'
,


Spark/Scala 작업을 하다 아래와 같은 에러가 발생했다.

Unsupported literal type class scala.collection.immutable.$colon$colon List(0, 1)





원인은 아래 문 isin 메소드 때문이다.


(아래 코드는 df1, df2라는 데이터 프레임에 대해 df1-df2를 하기 전에 
df1의 null 부분을 0으로 변경해, 쉽게 df1-df2(차집합)을 얻기 위한 코드이다)

import spark.implicits._

val validCode = List(0, 1)

var df1 =
Seq(
("1", "r1c2", null),
("2", "r2c2", new Integer(1)),
("3", "r2c2", new Integer(3))
).toDF("id", "c1", "c2")

val df2 =
Seq(
("1", "r1c2", new Integer(0)),
("2", "r2c2", new Integer(2))
).toDF("id", "c1", "c2")

df1 = df1.withColumn("c2", when($"c2".isin(validCode).isNull, 0) .otherwise($"c2"))
val df3 = df1.except(df2)
df3.show(5)



org.apache.spark.sql.Column.isin 메소드는 다음과 같다. Any 타입의 variable argument 타입을 추가해야 한다. 

def isin(list : scala.Any*)



그래서 에러 코드를 아래와 같이 변경하면.. 컴파일러가 인식하지 못한다.

isin(validCode) ->

isin(validCode:Any*)


이럴 때는 Any* 타입이 아니라 _* 으로 변경한다.


df1 = df1.withColumn("c2", 
         when($"c2".isin(validCode:_*).isNull, 0)
          .otherwise($"c2"))


코드는 잘 실행된다.


_*를 스칼라에서 Type Ascription(annotation으로 쉽게 표기하는 문법)에 필요한 문법으로서

Sugar Syntax로 말하기도 한다. 




isin(validCode:_*)

은 아래와 같이 사용해도 된다.

isInCollection(validCode)


Posted by '김용환'
,


인덱스를 복사하려면 reindex 커맨드를 사용한다.



curl -X POST http://gift-es-dev.daumkakao.io:9200/_reindex  -H 'Content-Type: application/json' -d'

{

  "source": {

    "index": "google_items"

  },

  "dest": {

    "index": google_item_test"

  }

}'







Posted by '김용환'
,



Mac OS에서 docker-compose를 실행하다 다음 에러가 발생했다. 



Credentials store docker-credential-osxkeychain exited with "User interaction is not allowed." 


해결 방법


- 첫 번째 작업

최신 docker로 업그레이드 한다.



- 두 번째 작업


menu bar -> preferences -> "Securely store docker logins in macOS keychain"의 체크박스가 on되어 있는데. off로 변경한다.


- 세 번째 작업

예전 docker 설정 때문에 이슈가 있으니 아래와 같이 변경한다.


cat ~/.docker/config.json

{

  "experimental" : "disabled",

  "stackOrchestrator" : "swarm",

  "auths" : {


  },

  "credsStore" : ""

}



그래도 안되면 재부팅해본다. 

Posted by '김용환'
,


파이프라인 프로세서(pipeline processor)를 사용하면 특정 인덱스로 저장 또는 검색할 수 있다. 


먼저 템플릿을 저장한다. 


$ curl -X PUT "localhost:9200/_template/reglog?pretty" -H 'Content-Type: application/json' -d'

{

   "template": "reqlog_*",

   "mappings": {

       "properties": {

         "id": {

           "type": "long"

        },

        "date1": {

           "type": "date"

        }

    }

  }

}

'


파이프라인을 등록한다. 파이프라인을 월별로 저장하기 위해 설정한 부분은 아래와 같다.

 
$ curl -X PUT "http://localhost:9200/_ingest/pipeline/reqlog-monthly-index" -H 'Content-Type: application/json' -d'

{

  "description": "monthly index naming for reqlog",

  "processors" : [

    {

      "date_index_name" : {

        "field" : "date1",

        "index_name_prefix" : "reqlog_",

        "index_name_format" : "yyyy-MM",

        "date_rounding" : "M"

      }

    }

  ]

}'



파이프에 템플릿을 저장하는 방식을 사용해 데이터를 인덱스에 저장한다. 


$ curl -X PUT "localhost:9200/reqlog/_doc/2?pipeline=reqlog-monthly-index&pretty" -H 'Content-Type: application/json' -d'

{

  "date1" : "2019-01-25T20:16:55.000Z"

}

'


데이터를 확인하면 제대로 있다.  이렇게 월별 인덱스에 저장한다. 


$ curl http://localhost:9200/reqlog_2019-01/_doc/2



이렇게 다르게 저장해도 제대로 데이터를 검색할 수 있다.



$ curl -X PUT "localhost:9200/reqlog/_doc/4?pipeline=reqlog-monthly-index&pretty" -H 'Content-Type: application/json' -d'

{

  "date1" : "2019-02-25T20:16:55.000Z"

}

'

$ curl http://localhost:9200/reqlog_2019-02/_doc/4





연도별 인덱스를 생성하려면 다음과 같이 파이프라인을 정의할 수 있다. 


curl -X PUT "http://localhost:9200/_ingest/pipeline/reqlog_yearly_index" -H 'Content-Type: application/json' -d'

{

  "description": "yearly index naming for reqlog_yearly",

  "processors" : [

    {

      "date_index_name" : {

        "field" : "date1",

        "index_name_prefix" : "reqlog_yearly_",

        "index_name_format" : "yyyy",

        "date_rounding" : "y"

      }

    }

  ]

}'






그런데 문제는 UTC 기준으로 데이터를 인덱싱하지 못한다는 점이다.


timezone: +09:00을 사용하면 잘 동작한다. 


$ curl -X PUT "http://localhost:9200/_ingest/pipeline/reqlog-monthly-index" -H 'Content-Type: application/json' -d'

{

  "description": "monthly index naming for reqlog",

  "processors" : [

    {

      "date_index_name" : {

        "field" : "date1",

        "index_name_prefix" : "reqlog_",

        "index_name_format" : "yyyy-MM",

        "date_rounding" : "M",

        "timezone": "+09:00"

      }

    }

  ]

}'




만약 java timemillis 타입으로 데이터를 저장했다면 

date_formats의 값을 [UNIX_MS]로 변경한다.


$ curl -X PUT "http://localhost:9200/_ingest/pipeline/reqlog-monthly-index" -H 'Content-Type: application/json' -d'

{

  "description": "monthly index naming for reqlog",

  "processors" : [

    {

      "date_index_name" : {

        "field" : "date1",

        "index_name_prefix" : "reqlog_",

        "index_name_format" : "yyyy-MM",

        "date_formats"      : [ "UNIX_MS" ],

        "date_rounding" : "M",

        "timezone": "+09:00"

      }

    }

  ]

}'






참고 자료

https://github.com/elastic/elasticsearch/blob/7.4/modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/DateIndexNameFactoryTests.java

Posted by '김용환'
,


스칼라에서 enum 타입에 맞는 문자열 값을 찾아 enum을 리턴하는 메소드가 필요하다.

이럴 때는 사용할 만한 메소드로 filter와 find가 적당하다.

filter는 List 배열을 리턴하기에 마땅치 않고 find가 적당한 것 같다.


public enum TestEnum {
A("a"),
B("b");

private String value;

private TestEnum(String value) {
this.value = value;
}

public String getValue() {
return value;
}
}





object Test {

def main(args: Array[String]): Unit = {

val wrongEnumType : String = "xxx"

val wrong = TestEnum.values().find(e => wrongEnumType.equals(e.getValue))

if (wrong.isDefined) {
println(wrong)
} else {
println("wrong enum type")
}

}
}


Posted by '김용환'
,



커맨드

 docker-compose -f docker-compose.yml up ${container_name}


예)

 docker-compose -f docker-compose.yml up  rabbitmq



Posted by '김용환'
,


spring cloud config 사용시 사용자 이름, 패스워드를 입력한다


"java -jar config-server.jar --spring.cloud.config.server.git.username=knight76 --spring.cloud.config.server.git.password=1234"


아니면 프로퍼티(property) 설정을 추가한다.


spring.cloud.config.server.git.username=knight76

spring.cloud.config.server.git.password=1234




패스워드가 나타나는 게 꺼림직하다.

jasypt(com.github.ulisesbocchio:jasypt-spring-boot-starter) 라이브러리를 사용하면 암호화를 할수 있다.


gradle 설정에  jasypt(com.github.ulisesbocchio:jasypt-spring-boot-starter) 라이브러리를  추가한다.

dependencies {
implementation 'org.springframework.cloud:spring-cloud-config-server'
implementation 'org.springframework.cloud:spring-cloud-starter-config'
compile 'com.github.ulisesbocchio:jasypt-spring-boot-starter:2.1.2'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}


application.properties에 


server.port=8080
jasypt.encryptor.bean=jasyptStringEncryptor

management.endpoint.env.enabled=true
management.endpoints.web.exposure.include=*

spring.cloud.config.server.bootstrap=true
spring.cloud.config.server.git.uri=https://github.com/knight76/spring-cloud-config-example
spring.cloud.config.server.git.timeout=5
spring.cloud.config.server.git.username=knight76
spring.cloud.config.server.git.password=123214



암호화에 사용되는 JasyptConfig 클래스를 추가한다. 


package com.github.knight76.config.configserver;

import org.jasypt.encryption.StringEncryptor;
import org.jasypt.encryption.pbe.PooledPBEStringEncryptor;
import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class JasyptConfig {

final static String KEY = "knight76";

final static String ALGORITHM = "PBEWithMD5AndDES";

@Bean("jasyptStringEncryptor")
public StringEncryptor stringEncryptor() {
PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
SimpleStringPBEConfig config = new SimpleStringPBEConfig();
config.setPassword(KEY);
config.setAlgorithm(ALGORITHM);
config.setKeyObtentionIterations("1000");
config.setPoolSize("1");
config.setProviderName("SunJCE");
config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
config.setStringOutputType("base64");
encryptor.setConfig(config);
return encryptor;
}

}


 테스트 코드는 다음과 같다. 테스트 코드의 비밀번호를 사용한다. 

package com.github.knight76.config.configserver;

import org.jasypt.encryption.pbe.StandardPBEStringEncryptor;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class ConfigServerApplicationTests {

@Test
public void test() {
StandardPBEStringEncryptor standardPBEStringEncryptor = new StandardPBEStringEncryptor();
standardPBEStringEncryptor.setAlgorithm(JasyptConfig.ALGORITHM);
standardPBEStringEncryptor.setPassword(JasyptConfig.KEY);

String enc = standardPBEStringEncryptor.encrypt("비밀번호");
System.out.println("enc = " + enc);

String des = standardPBEStringEncryptor.decrypt(enc);
System.out.println("des = " + des);
}
}



테스트 코드를 통해 얻은 비밀번호를 application.properties의 비밀번호로 설정한다. ENC(..비밀번호..)로 적용한다.


server.port=8080
jasypt.encryptor.bean=jasyptStringEncryptor

management.endpoint.env.enabled=true
management.endpoints.web.exposure.include=*

spring.cloud.config.server.bootstrap=true
spring.cloud.config.server.git.uri=https://github.com/knight76/spring-cloud-config-example
spring.cloud.config.server.git.timeout=5
spring.cloud.config.server.git.username=knight76
spring.cloud.config.server.git.password=ENC(암호문)


풀 예제 코드는 다음과 같다.

https://github.com/knight76/spring-cloud-config-example



이 방식 외에 RSA 비밀 키를 사용하는 방식이 있다. 


https://cloud.spring.io/spring-cloud-config/reference/html/


spring: cloud: config: server: git: uri: git@gitserver.com:team/repo1.git ignoreLocalSshSettings: true hostKey: someHostKey hostKeyAlgorithm: ssh-rsa privateKey: | -----BEGIN RSA PRIVATE KEY----- MIIEpgIBAAKCAQEAx4UbaDzY5xjW6hc9jwN0mX33XpTDVW9WqHp5AKaRbtAC3DqX IXFMPgw3K45jxRb93f8tv9vL3rD9CUG1Gv4FM+o7ds7FRES5RTjv2RT/JVNJCoqF ol8+ngLqRZCyBtQN7zYByWMRirPGoDUqdPYrj2yq+ObBBNhg5N+hOwKjjpzdj2Ud 1l7R+wxIqmJo1IYyy16xS8WsjyQuyC0lL456qkd5BDZ0Ag8j2X9H9D5220Ln7s9i oezTipXipS7p7Jekf3Ywx6abJwOmB0rX79dV4qiNcGgzATnG1PkXxqt76VhcGa0W DDVHEEYGbSQ6hIGSh0I7BQun0aLRZojfE3gqHQIDAQABAoIBAQCZmGrk8BK6tXCd fY6yTiKxFzwb38IQP0ojIUWNrq0+9Xt+NsypviLHkXfXXCKKU4zUHeIGVRq5MN9b BO56/RrcQHHOoJdUWuOV2qMqJvPUtC0CpGkD+valhfD75MxoXU7s3FK7yjxy3rsG EmfA6tHV8/4a5umo5TqSd2YTm5B19AhRqiuUVI1wTB41DjULUGiMYrnYrhzQlVvj 5MjnKTlYu3V8PoYDfv1GmxPPh6vlpafXEeEYN8VB97e5x3DGHjZ5UrurAmTLTdO8 +AahyoKsIY612TkkQthJlt7FJAwnCGMgY6podzzvzICLFmmTXYiZ/28I4BX/mOSe pZVnfRixAoGBAO6Uiwt40/PKs53mCEWngslSCsh9oGAaLTf/XdvMns5VmuyyAyKG ti8Ol5wqBMi4GIUzjbgUvSUt+IowIrG3f5tN85wpjQ1UGVcpTnl5Qo9xaS1PFScQ xrtWZ9eNj2TsIAMp/svJsyGG3OibxfnuAIpSXNQiJPwRlW3irzpGgVx/AoGBANYW dnhshUcEHMJi3aXwR12OTDnaLoanVGLwLnkqLSYUZA7ZegpKq90UAuBdcEfgdpyi PhKpeaeIiAaNnFo8m9aoTKr+7I6/uMTlwrVnfrsVTZv3orxjwQV20YIBCVRKD1uX VhE0ozPZxwwKSPAFocpyWpGHGreGF1AIYBE9UBtjAoGBAI8bfPgJpyFyMiGBjO6z FwlJc/xlFqDusrcHL7abW5qq0L4v3R+FrJw3ZYufzLTVcKfdj6GelwJJO+8wBm+R gTKYJItEhT48duLIfTDyIpHGVm9+I1MGhh5zKuCqIhxIYr9jHloBB7kRm0rPvYY4 VAykcNgyDvtAVODP+4m6JvhjAoGBALbtTqErKN47V0+JJpapLnF0KxGrqeGIjIRV cYA6V4WYGr7NeIfesecfOC356PyhgPfpcVyEztwlvwTKb3RzIT1TZN8fH4YBr6Ee KTbTjefRFhVUjQqnucAvfGi29f+9oE3Ei9f7wA+H35ocF6JvTYUsHNMIO/3gZ38N CPjyCMa9AoGBAMhsITNe3QcbsXAbdUR00dDsIFVROzyFJ2m40i4KCRM35bC/BIBs q0TY3we+ERB40U8Z2BvU61QuwaunJ2+uGadHo58VSVdggqAo0BSkH58innKKt96J 69pcVH/4rmLbXdcmNYGm6iu+MlPQk4BUZknHSmVHIFdJ0EPupVaQ8RHT 

-----END RSA PRIVATE KEY-----


Posted by '김용환'
,