/
jiaapi.go
163 lines (145 loc) · 4.72 KB
/
jiaapi.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
package scenario
import (
"context"
"errors"
"fmt"
"net/http"
"net/url"
"strings"
"sync"
"time"
"github.com/isucon/isucon11-qualify/bench/logger"
"github.com/isucon/isucon11-qualify/bench/model"
"github.com/isucon/isucon11-qualify/bench/service"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
var (
streamsForPosterMutex sync.Mutex
isuIsActivated = map[string]struct{}{}
streamsForPoster = map[string]*model.StreamsForPoster{}
//isuDetailInfomation = map[string]*IsuDetailInfomation{}
isuFromUUID = map[string]*model.Isu{}
posterRootContext context.Context
)
type IsuDetailInfomation struct {
Character string `json:"character"`
}
//シナリオ Goroutineからの呼び出し
func RegisterToJiaAPI(isu *model.Isu, streams *model.StreamsForPoster) {
streamsForPosterMutex.Lock()
defer streamsForPosterMutex.Unlock()
isuFromUUID[isu.JIAIsuUUID] = isu
streamsForPoster[isu.JIAIsuUUID] = streams
}
func (s *Scenario) JiaAPIService(ctx context.Context) {
defer logger.AdminLogger.Println("--- JiaAPIService END")
posterRootContext, s.JiaPosterCancel = context.WithCancel(ctx)
// Echo instance
e := echo.New()
e.HideBanner = true
e.HidePort = true
//e.Debug = true
//e.Logger.SetLevel(log.DEBUG)
// Middleware
//e.Use(middleware.Logger())
e.Use(middleware.Recover())
// Initialize
e.POST("/api/activate", func(c echo.Context) error { return s.postActivate(c) })
// Start
var bindPort string
if s.jiaServiceURL.Port() != "" {
bindPort = fmt.Sprintf("0.0.0.0:%s", s.jiaServiceURL.Port())
} else {
bindPort = "0.0.0.0:80"
}
go func() {
defer logger.AdminLogger.Println("--- ISU協会サービス END")
err := e.Start(bindPort)
if err != nil && !errors.Is(err, http.ErrServerClosed) {
panic(fmt.Errorf("ISU協会サービスが異常終了しました: %v", err))
}
}()
//コンテキストにより終了された場合は、echoサーバーも終了
<-ctx.Done()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
err := e.Shutdown(ctx)
if err != nil {
//有効なエラー処理は出来ないのでエラーは握り潰し
logger.AdminLogger.Printf("Failed to shutdown jia service: %s", err)
}
}
func (s *Scenario) postActivate(c echo.Context) error {
state := &service.JIAServiceRequest{}
err := c.Bind(state)
if err != nil {
return c.String(http.StatusBadRequest, "Bad Request")
}
targetBaseURL, err := url.Parse(state.TargetBaseURL)
if err != nil {
return c.String(http.StatusBadRequest, "Bad URL")
}
//poster Goroutineの起動
var isu *model.Isu
var scenarioChan *model.StreamsForPoster
var fqdn string
posterContext := posterRootContext
errCode, errMsg := func() (int, string) {
var ok bool
streamsForPosterMutex.Lock()
defer streamsForPosterMutex.Unlock()
// scenario goroutine とやり取りするためのチャネルを受け取る
scenarioChan, ok = streamsForPoster[state.IsuUUID]
if !ok {
return http.StatusNotFound, "Bad isu_uuid"
}
// リクエストされた JIA_ISU_UUID が事前に scenario.NewIsu にて作成された isu と紐付かない場合 404 を返す
isu, ok = isuFromUUID[state.IsuUUID]
if !ok {
//scenarioChanでチェックしているのでここには来ないはず
return http.StatusNotFound, "Bad isu_uuid"
}
_, ok = isuIsActivated[state.IsuUUID]
if ok {
//activate済み
return 0, ""
}
// useTLS が有効 && POST isucondition する URL に https 以外が指定されていたら 400 を返す
if s.UseTLS && targetBaseURL.Scheme != "https" {
return http.StatusBadRequest, "Bad URL Scheme: scheme must be https"
}
// FQDN が競技者 VM のものでない場合 400 を返す
fqdn = targetBaseURL.Hostname()
ipAddr, ok := s.GetIPAddrFromFqdn(fqdn)
if !ok {
return http.StatusBadRequest, "Bad URL: hostname must be isucondition-[1-3].t.isucon.dev"
}
//httpsモードの際はportは指定なしのみ
port := targetBaseURL.Port()
if s.UseTLS && port != "" {
return http.StatusBadRequest, "Bad Port: ポート番号は指定できません"
}
// URL の文字列を IP アドレスに変換
if port != "" {
targetBaseURL.Host = strings.Join([]string{ipAddr, port}, ":")
} else {
targetBaseURL.Host = ipAddr
}
// activate 済みフラグを立てる
isuIsActivated[state.IsuUUID] = struct{}{}
//activate
s.loadWaitGroup.Add(1)
go func() {
defer s.loadWaitGroup.Done()
defer logger.AdminLogger.Println("defer s.loadWaitGroup.Done() keepPosting")
s.keepPosting(posterContext, targetBaseURL, fqdn, isu, scenarioChan)
}()
return 0, ""
}()
if errCode != 0 {
return c.String(errCode, errMsg)
}
time.Sleep(50 * time.Millisecond)
return c.JSON(http.StatusAccepted, IsuDetailInfomation{isu.Character})
}