怒Mは思いつきでモノを言う

やったことメモなどなど

Fn Projectについて調べて見た。④

Introduction to Fn with Javaの最後です。

前回はfn invokeが成功したところまで。

do-m-gatoru.hatenablog.com

まずは、Fn Projectの更新。

  1. brew upgrade fn でFn CLIを更新
  2. fn update server でFn Serverを更新

2018/11/26時点で、fn version 0.5.29Fn Server v0.3.624です。

Tutorialのコードを確認。

これまで動かしていたコードは以下です。

package com.example.fn;

public class HelloFunction {

    public String handleRequest(String input) {
        String name = (input == null || input.isEmpty()) ? "world"  : input;

        return "Hello, " + name + "!";
    }

}

テストコードは以下。

package com.example.fn;

import com.fnproject.fn.testing.*;
import org.junit.*;

import static org.junit.Assert.*;

public class HelloFunctionTest {

    @Rule
    public final FnTestingRule testing = FnTestingRule.createDefault();

    @Test
    public void shouldReturnGreeting() {
        testing.givenEvent().enqueue();
        testing.thenRun(HelloFunction.class, "handleRequest");

        FnResult result = testing.getOnlyResult();
        assertEquals("Hello, world!", result.getBodyAsString());
    }

    @Test
    public void shouldReturnWithInput() {
        testing.givenEvent().withBody("Bob").enqueue();
        testing.thenRun(HelloFunction.class, "handleRequest");

        FnResult result = testing.getOnlyResult();
        assertEquals("Hello, Bob!", result.getBodyAsString());
    }
}

JSONで入出力する。

コードを書き換えます。

package com.example.fn;

public class HelloFunction {

    public static class Input {
        public String name;
    }

    public static class Result {
        public String salutation;
    }

    public Result handleRequest(Input input) {
        Result result = new Result();
        result.salutation = "Hello " + input.name;

        return result;
    }

}

この状態で、fn deployします。

$ fn --verbose deploy --app myapp --local
Deploying fntest to app: myapp
Bumped to version 0.0.18
Building image fntest:0.0.18 

(中略)

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.example.fn.HelloFunctionTest
Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.829 sec <<< FAILURE!
shouldReturnGreeting(com.example.fn.HelloFunctionTest)  Time elapsed: 0.472 sec  <<< FAILURE!
org.junit.ComparisonFailure: expected:<{"salutation":"[Hello ]Bob"}> but was:<{"salutation":"[]Bob"}>
    at org.junit.Assert.assertEquals(Assert.java:115)
    at org.junit.Assert.assertEquals(Assert.java:144)
    at com.example.fn.HelloFunctionTest.shouldReturnGreeting(HelloFunctionTest.java:19)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:564)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.rules.RunRules.evaluate(RunRules.java:20)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:252)
    at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:141)
    at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:112)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:564)
    at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray(ReflectionUtils.java:189)
    at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:165)
    at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:85)
    at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:115)
    at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:75)


Results :

Failed tests:   shouldReturnGreeting(com.example.fn.HelloFunctionTest): expected:<{"salutation":"[Hello ]Bob"}> but was:<{"salutation":"[]Bob"}>

Tests run: 1, Failures: 1, Errors: 0, Skipped: 0

[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.544 s
[INFO] Finished at: 2018-11-25T16:51:12Z
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.12.4:test (default-test) on project hello: There are test failures.
[ERROR] 
[ERROR] Please refer to /function/target/surefire-reports for the individual test results.
[ERROR] -> [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException
The command 'mvn package' returned a non-zero code: 1


Fn: error running docker build: exit status 1

See 'fn <command> --help' for more information. Client version: 0.5.29

失敗しました。テストを書き換えてないから当然です、と言うことで書き換えます。

package com.example.fn;

import com.fnproject.fn.testing.*;
import org.junit.*;

import static org.junit.Assert.*;

public class HelloFunctionTest {

    @Rule
    public final FnTestingRule testing = FnTestingRule.createDefault();

    @Test
    public void shouldReturnGreeting() {
        testing.givenEvent().withBody("{\"name\":\"Bob\"}").enqueue();
        testing.thenRun(HelloFunction.class, "handleRequest");

        FnResult result = testing.getOnlyResult();
        assertEquals("{\"salutation\":\"Hello Bob\"}", result.getBodyAsString());
    }
}

再度、fn deployをしてみます。

$ fn --verbose deploy --app myapp --local
Deploying fntest to app: myapp
Bumped to version 0.0.19
Building image fntest:0.0.19 

(中略)

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.example.fn.HelloFunctionTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.859 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO] 
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ hello ---
[INFO] Building jar: /function/target/hello-1.0.0.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.896 s
[INFO] Finished at: 2018-11-25T16:54:27Z
[INFO] ------------------------------------------------------------------------
Removing intermediate container 3e81cb813926
 ---> 2ffe6ce2f4c3
Step 8/11 : FROM fnproject/fn-java-fdk:jdk9-1.0.72
 ---> d23b2ab9ca0a
Step 9/11 : WORKDIR /function
 ---> Using cache
 ---> 2484b72ee70d
Step 10/11 : COPY --from=build-stage /function/target/*.jar /function/app/
 ---> cf6f2b4d62e2
Step 11/11 : CMD ["com.example.fn.HelloFunction::handleRequest"]
 ---> Running in 3b7325fbd756
Removing intermediate container 3b7325fbd756
 ---> 435980547049
Successfully built 435980547049
Successfully tagged fntest:0.0.19

Updating function fntest using image fntest:0.0.19...

無事に成功しました。
動作確認します。期待通りの結果が返ってきます。

$ curl -H "Content-Type: application/json" -d '{"name":"Bob"}' http://localhost:8080/t/myapp/fntest-trigger
{"salutation":"Hello Bob"}

Tutorialでは想定通りにならないコマンドがあった。

$ curl -H "Content-Type: application/json" http://localhost:8080/t/myapp/fntest-trigger
{"message":"invalid function response"}

Tutorialでは{"salutation":"Hello World"}となるっぽいですが、なぜか上記のような結果に。 コードを書き換えてみました。

package com.example.fn;

public class HelloFunction {

    public static class Input {
        public String name = "World";
    }

    public static class Result {
        public String salutation;
    }

    public Result handleRequest(Input input) {
        Result result = new Result();
        result.salutation = "Hello " + input.name;

        return result;
    }
}

動作を確認します。

$ curl -H "Content-Type: application/json" http://localhost:8080/t/myapp/fntest-trigger
{"message":"invalid function response"}

$ curl -H "Content-Type: application/json" -d '{}' http://localhost:8080/t/myapp/fntest-trigger
{"salutation":"Hello World"}

$ curl -H "Content-Type: application/json" -d '{"name":"Bob"}' http://localhost:8080/t/myapp/fntest-trigger
{"salutation":"Hello Bob"}

Tutorialに書いている通りのコマンドだと当然失敗します。書き換えて空っぽのJSONを渡すとHello Worldが返りますが、そうじゃない感。
Tutorialの何かを見落としているか、Tutorialが間違っているか。

今後

Introductionが終わったので、引き続きTutorialを続けていきます。
Fn CLIのコマンドをまとめたいなー。