memo of CADDE

メモ

CADDE-sip/connector のREADMEを読んでメモする。

前提

CADDE-sip/connector/README 前提条件 に色々書いてある。

1
カタログ検索、データ交換、認証、認可、来歴、契約の機能を具備します。

これらの機能を利用するためのAPIを具備している、くらいの読み方の方が良いかも。

導入ガイドライン

CADDE-sip/connector/README 導入ガイドライン にドキュメントの置き場所が書いてある。 このあたりは、先に読んでおいたほうが良いだろう。

利用者コネクタ

CADDE-sip/connector/README 利用者コネクタの構築手順 の通り、クローンしたレポジトリの src/consumer/ 内に 利用者コネクタのソースコードがある。

アーキテクチャの概要は以下に載っている。

CADDE-sip/connector/README 利用者アーキテクチャ図

利用者目線でのざっくりとした理解

上記図の、コネクタメイン(利用者側)の左側に、カタログ検索I/Fとデータ取得I/Fがあるが、 これが利用者絡みたときの接点となるI/Fである。

コネクタイメインのSwagger定義で言えば以下。src/consumer/connector-main/swagger_server/swagger/swagger.yaml

カタログ検索 paths./cadde/api/v4/catalog

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  /cadde/api/v4/catalog:
get:
tags:
- Search
summary: API. カタログ検索
description: |-
カタログ情報を取得する。APIユーザが指定した条件に従いカタログ情報を取得する。

Response:
* 処理が成功した場合は200を返す。
* 処理に失敗した場合は、2xx以外のコードを返す。 Responsesセクション参照。
operationId: search

(snip)

データ取得 paths./cadde/api/v4/file

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  /cadde/api/v4/file:
get:
tags:
- Files
summary: API. データ取得(CADDE)
description: |-
リソースURLに指定されたデータを取得する。

Response:
* 処理が成功した場合は200を返す。
* 処理に失敗した場合は、2xx以外のコードを返す。 Responsesセクションを参照。
operationId: files

(snip)

Swaggerの定義に基づき、PythonのFlask使って作られている。

ロケーション情報 

手順では src/consumer/connector-main/swagger_server/configs/location.json を編集することで、 提供者コネクタのアドレス設定をするように書かれている。

このファイルは以下のような内容。

1
2
3
4
5
6
7
8
9
10
{
"connector_location": {
"test-provider:catalog": {
"provider_connector_url": "http://10.0.4.71:28080"
},
"test-provider:data": {
"provider_connector_url": "http://10.0.4.71:38080"
}
}
}

あるデータ提供者コネクタのカタログAPI、データAPI(?)のURLが記載されている。

このファイルがどこで利用されるかというと、サービス定義内の以下。

src/consumer/connector-main/swagger_server/services/service.py:384

1
def __get_location_info(provider, location_service_url, external_interface) -> (str):

ロケーションサービスから取得ができなかった場合、 location.jsonからコンフィグ情報を取得する。

とある通り、まずロケーションサービスから情報を取得するようにしレスポンスがあったらリターン。

src/consumer/connector-main/swagger_server/services/service.py:418

1
2
3
4
5
6
if response:
if response.endswith('/'):
response = response[:-1]
provider_connector_url = response

return provider_connector_url

だめな場合は、コンフィグから読み込む。

src/consumer/connector-main/swagger_server/services/service.py:425

1
2
3
4
5
6
# コンフィグから再取得を試みる
logger.info(f'Not Found {provider} from {location_service_url}')
try:
config = internal_interface.config_read(__CONFIG_LOCATION_FILE_PATH)
except Exception: # pathミス
raise CaddeException(message_id='020000003E')

この __get_location_info 関数は2箇所で利用されている。

1
2
3
__get_location_info(provider, location_service_url, external_interface)
catalog_search(query_string, search, provider, authorization, external_interface=ExternalInterface())
fetch_data(authorization, resource_url, resource_api_type, provider, options, external_interface=ExternalInterface())

ひとつがカタログ検索内。

src/consumer/connector-main/swagger_server/services/service.py:42

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
def catalog_search(
query_string: str,
search: str,
provider: str,
authorization: str,
external_interface: ExternalInterface = ExternalInterface()) -> Response:
"""
カタログ検索I/Fに、カタログ検索を行い、検索結果を返す

Args:
query_string str : クエリストリング
search str : 検索種別
provider str: CADDEユーザID(提供者)
authorization str: 利用者トークン
external_interface ExternalInterface : GETリクエストを行うインタフェース

Returns:
Response : 取得した情報

Raises:
Cadde_excption: 検索種別確認時に不正な値が設定されている場合 エラーコード: 020001002E
Cadde_excption: 認証I/F 認証トークン検証処理でエラーが発生した場合 エラーコード: 020001003E
Cadde_excption: 提供者コネクタURLの取得に失敗した場合 エラーコード: 020001004E
Cadde_excption: カタログ検索I/Fのカタログ検索処理でエラーが発生した場合 エラーコード: 020001005E

"""
(snip)

カタログ検索機能を利用する際、もし詳細検索であれば、ロケーション情報を取得する。

src/consumer/connector-main/swagger_server/services/service.py:99

1
2
3
4
5
if search == 'detail':
provider_connector_url = __get_location_info(
provider, location_service_url, external_interface)
if not provider_connector_url:
raise CaddeException('020001004E')

もうひとつがデータの取得時。

src/consumer/connector-main/swagger_server/services/service.py:142

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def fetch_data(authorization: str,
resource_url: str,
resource_api_type: str,
provider: str,
options: dict,
external_interface: ExternalInterface = ExternalInterface()) -> (BytesIO,
dict):
"""
データ交換I/Fからデータを取得する、もしくはデータ管理から直接データを取得する。

Args:
resource_url str : リソースURL
resource_api_type str : リソース提供手段識別子
provider str : CADDEユーザID(提供者)
authorization str : 利用者トークン
options : dict リクエストヘッダ情報 key:ヘッダ名 value:パラメータ
external_interface ExternalInterface : GETリクエストを行うインタフェース

Returns:
BytesIO :取得データ
dict: レスポンスヘッダ情報 key:ヘッダ名 value:パラメータ レスポンスヘッダがない場合は空のdictを返す

(snip)

提供社のCADDEユーザIDが与えられたときにロケーション情報を取得する。

src/consumer/connector-main/swagger_server/services/service.py:230

1
2
3
4
5
6
7
8
9
    # CADDEユーザID(提供者)あり
else:

provider_connector_url = __get_location_info(
provider, location_service_url, external_interface)
if not provider_connector_url:
raise CaddeException('020004003E')

(snip)

コネクタ設定

READMEの通り、コネクタのIDやシークレットキーの設定は src/consumer/connector-main/swagger_server/configs/connector.json にて行う。

以下のような内容。

1
2
3
4
5
6
{
"consumer_connector_id" : "test_consumer_connector_id",
"consumer_connector_secret" : "test_consumer_connector_secret",
"location_service_url" : "https://testexample.com",
"trace_log_enable" : true
}

また、これが用いられるのは、以下。

src/consumer/connector-main/swagger_server/services/service.py:486

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def __get_connector_config() -> (str, str, str, str):
"""
connector.jsonからコンフィグ情報を取得し、
利用者側コネクタID、利用者側コネクタのシークレット、来歴管理者用トークンを返す。

Returns:
str: 利用者側コネクタID
str: 利用者側コネクタのシークレット
str: ロケーションサービスのURL
str: トレースログ設定

Raises:
Cadde_excption: コンフィグファイルの読み込みに失敗した場合 エラーコード: 020000005E
Cadde_excption: 必須パラメータが設定されていなかった場合(利用者コネクタID) エラーコード: 020000006E
Cadde_excption: 必須パラメータが設定されていなかった場合(利用者コネクタのシークレット) エラーコード: 020000007E
Cadde_excption: 必須パラメータが設定されていなかった場合(ロケーションサービスのURL) エラーコード: 020000008E
Cadde_excption: 必須パラメータが設定されていなかった場合(トレースログ設定) エラーコード: 020000009E

"""

(snip)

この関数が利用されるのは以下。

1
2
3
__get_connector_config()
catalog_search(query_string, search, provider, authorization, external_interface=ExternalInterface())
fetch_data(authorization, resource_url, resource_api_type, provider, options, external_interface=ExternalInterface())

両方とも同様の使われ方。 基本的には、呼び出して必要な値を読み出す。以下は、カタログ検索での例。

src/consumer/connector-main/swagger_server/services/service.py:72

1
consumer_connector_id, consumer_connector_secret, location_service_url, trace_log_enable = __get_connector_config()

得られた値は、例えば認証トークン検証などに用いられる。

src/consumer/connector-main/swagger_server/services/service.py:76

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    if authorization:
# 認証トークン検証
token_introspect_headers = {
'Authorization': authorization,
'x-cadde-consumer-connector-id': consumer_connector_id,
'x-cadde-consumer-connector-secret': consumer_connector_secret
}
token_introspect_response = external_interface.http_get(
__ACCESS_POINT_URL_AUTHENTICATION_AUTHORIZATION_INTROSPECT, token_introspect_headers)

if token_introspect_response.status_code < 200 or 300 <= token_introspect_response.status_code:
raise CaddeException(
message_id='020001003E',
status_code=token_introspect_response.status_code,
replace_str_list=[
token_introspect_response.text])

consumer_id = token_introspect_response.headers['x-cadde-consumer-id']

(snip)

その他

NGSIの設定や、横断検索用CKAN URLの設定の説明がある。

横断検索用CKAN URL設定の使われどころ。 src/consumer/catalog-search/swagger_server/services/service.py:13

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def search_catalog_meta(
q: str,
internal_interface: InternalInterface,
external_interface: ExternalInterface) -> Response:
"""
横断検索を行い、横断検索サイトからカタログ情報を取得する

Args:
q str : 検索条件のクエリストリング
internal_interface InternalInterface : コンフィグ情報取得処理を行うインタフェース
external_interface ExternalInterface : GETリクエストを行うインタフェース

Returns:
Response : 取得した情報

Raises:
Cadde_excption : コンフィグファイルからCKANURLを取得できない場合、エラーコード : 020101004E
Cadde_excption : ステータスコード2xxでない場合 エラーコード : 020101005E

"""

(snip)

src/consumer/catalog-search/swagger_server/services/service.py:34

1
2
3
4
    try:
config = internal_interface.config_read(__CONFIG_CKAN_URL_FILE_PATH)
ckan_url = config[__CONFIG_CKAN_URL]
(snip)

また、TLS相互認証のためフォワードプロキシを利用する手順も載っているし、利用者コネクタへのアクセス制限のためのリバースプロキシの設定手順も載っている。

検索について

検索は、src/consumer/catalog-search が担う。以下、このコンポーネントについて。

以下の通り、search APIがエントリポイント。

swagger_server/swagger/swagger.yaml:19

1
operationId: search

ということでコントローラを見る。

swagger_server.controllers.search_controller.search

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def search(q=None, x_cadde_search=None, x_cadde_provider_connector_url=None, Authorization=None):  # noqa: E501
"""API. カタログ検索

横断検索、詳細検索を判定し、 横断検索サイトまたは提供者カタログサイトからカタログ情報を取得する Response: * 処理が成功した場合は200を返す * 処理に失敗した場合は、2xx以外を返す。Responsesセクション参照。 # noqa: E501

:param q: CKAN検索条件クエリ CKAN APIに準拠
:type q: str
:param x-cadde-search: 横断検索、詳細検索を指定する(横断検索:meta、詳細検索:detail)
:type x-cadde-search: str
:param x-cadde-provider-connector-url: 提供者コネクタURL
:type x-cadde-provider-connector-url: str
:param Authorization: 認証トークン
:type Authorization: str

:rtype: None
"""

(snip)

検索は、横断検索(meta)か、詳細検索(detail)か。

swagger_server/controllers/search_controller.py:39

1
search = connexion.request.headers['x-cadde-search']

横断検索は以下。

swagger_server/controllers/search_controller.py:41

1
2
3
4
5
if search == 'meta':
logger.debug(get_message('020101001N', [query_string, search]))

data = search_catalog_meta(
query_string, internal_interface, external_interface)

基本的には、コンフィグ(public_ckan.json)にある、公開CKAN(横断検索用カタログ)の 情報を取得し、問い合わせを投げるのみ。 認証はされない。(認証用トークなどを渡す仕様ではない) これは「公開」なので。

詳細検索は以下。

swagger_server/controllers/search_controller.py:47

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
else:
if 'x-cadde-provider-connector-url' in connexion.request.headers:
provider_connector_url = connexion.request.headers['x-cadde-provider-connector-url']
else:
raise CaddeException(message_id='020101002E')

authorization = None
if 'Authorization' in connexion.request.headers:
authorization = connexion.request.headers['Authorization']

logger.debug(get_message('020101003N', [query_string, log_message_none_parameter_replace(
provider_connector_url), log_message_none_parameter_replace(authorization), search]))

data = search_catalog_detail(
query_string,
provider_connector_url,
authorization,
external_interface)

こちらは、認証情報をヘッダーから取得し、関数に渡している。 swagger_server.services.service.search_catalog_detail 関数が本体。 認証情報を渡しているところが違う、基本的には同様。

データ取得について

Swagger YAML定義に基づくと、利用者側コネクタのコネクタメインのエントリポイントは以下。

consumer/connector-main/swagger_server/controllers/files_controller.py:14

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def files(authorization=None, resource_url=None, resource_api_type=None, provider=None):  # noqa: E501
"""API. データ取得(NGSI以外)

CADDEインタフェースを用いて、HTTPサーバ、FTPサーバからファイルを取得する

Response:
* 処理が成功した場合は200を返す
* 処理に失敗した場合は、2xx以外のコードを返す。 Responsesセクションを参照。 # noqa: E501

:param Authorization: 利用者トークン
:type Authorization: str
:param resource_url: リソースURL
:type resource_url: str
:param resource_api_type: リソース提供手段識別子
:type resource_api_type: str
:param provider: CADDEユーザID(提供者)
:type provider: str

:rtype: None
"""

(snip)

基本的には、ユーザ(ないし、ユーザのシステム)から与えられた認証情報(トークン)、リソースURLなどを利用し、 リソースURLにアクセスしてファイルを受領する、というもの。

実際に、データを取得するのは以下の部分。

consumer/connector-main/swagger_server/controllers/files_controller.py:54

1
2
3
4
5
6
7
8
9
10
11
12
13
    data, headers = fetch_data(
authorization,
resource_url,
resource_api_type,
provider,
None,
external_interface)

response = make_response(data.read(), 200)
response.headers = headers
response.headers['Content-Disposition'] = 'attachment; filename=' + \
get_url_file_name(resource_url)
(snip)

fetch_data関数は、servicesにある。

consumer/connector-main/swagger_server/services/service.py:142

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def fetch_data(authorization: str,
resource_url: str,
resource_api_type: str,
provider: str,
options: dict,
external_interface: ExternalInterface = ExternalInterface()) -> (BytesIO,
dict):
"""
データ交換I/Fからデータを取得する、もしくはデータ管理から直接データを取得する。

Args:
resource_url str : リソースURL
resource_api_type str : リソース提供手段識別子
provider str : CADDEユーザID(提供者)
authorization str : 利用者トークン
options : dict リクエストヘッダ情報 key:ヘッダ名 value:パラメータ
external_interface ExternalInterface : GETリクエストを行うインタフェース

Returns:
BytesIO :取得データ
dict: レスポンスヘッダ情報 key:ヘッダ名 value:パラメータ レスポンスヘッダがない場合は空のdictを返す

(snip)

リソースのURLとともに、リソースのAPI種類を受け取る。 下記の通り、いまのところ、NGSI、file/ftp、file/httpに対応。

consumer/connector-main/swagger_server/services/service.py:177

1
2
3
    if resource_api_type != 'api/ngsi' and resource_api_type != 'file/ftp' and resource_api_type != 'file/http':
raise CaddeException('020004001E')
(snip)

認証は以下。

consumer/connector-main/swagger_server/services/service.py:184

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    if authorization:
# 認証トークン検証
token_introspect_headers = {
'Authorization': authorization,
'x-cadde-consumer-connector-id': consumer_connector_id,
'x-cadde-consumer-connector-secret': consumer_connector_secret
}
token_introspect_response = external_interface.http_get(
__ACCESS_POINT_URL_AUTHENTICATION_AUTHORIZATION_INTROSPECT, token_introspect_headers)

if token_introspect_response.status_code < 200 or 300 <= token_introspect_response.status_code:
raise CaddeException(
message_id='020004002E',
status_code=token_introspect_response.status_code,
replace_str_list=[
token_introspect_response.text])

consumer_id = token_introspect_response.headers['x-cadde-consumer-id']

(snip)

利用者コネクタのIDとシークレットキーをヘッダーに入れ、Introspect宛先に問う。 上記の通り、基本的にはCADDE固有の認証サーバ?

これを一般的な外部サービス利用可能にするには、実装の変更の必要がありそう。

データ取得部分。 CADDEユーザIDのありなしで挙動が異なる。

まずCADDEユーザIDなしの場合。

consumer/connector-main/swagger_server/services/service.py:212

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    # CADDEユーザID(提供者)なし
if not provider:
if resource_api_type == 'api/ngsi':
response_bytes, ngsi_response_headers = provide_data_ngsi(
resource_url, options)
header_arry = ngsi_response_headers.keys()
for key_data in header_arry:
response_headers[key_data] = ngsi_response_headers[key_data]
elif resource_api_type == 'file/ftp':
response_bytes = provide_data_ftp(
resource_url, external_interface, internal_interface)

elif resource_api_type == 'file/http':
response_bytes = provide_data_http(
resource_url, options, external_interface, internal_interface)

return response_bytes, response_headers
(snip)

この中で、provide_data_http関数がどこに定義されているかというと、common.swagger_server.services.provide_data_http.provide_data_httpなど。これは common の中にあるが、実はセットアップスクリプトでシンボリックリンクが貼られている。

consumer/setup.sh:18

1
ln -f ../common/swagger_server/services/provide_data_http.py  connector-main/swagger_server/services/provide_data_http.py

関数を見てみる。

common/swagger_server/services/provide_data_http.py:24

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def provide_data_http(
resource_url: str,
headers_dict: dict = None,
file_get_interface: ExternalInterface = ExternalInterface(),
config_get_interface: InternalInterface = InternalInterface()) -> BytesIO:
"""
HTTPサーバからファイルを取得して返却する。
※2020年9月版ではダイジェスト認証、TLS証明書認証、OAuth等の認証処理は実施せず、
※ベーシック認証のみ実施する。

Args:
resource_url str : ファイル取得を行うリソースURL
headers_dict : 設定するheader {ヘッダー名:パラメータ}
file_get_interface object : ファイル取得処理を行うインタフェース
config_get_interface object : コンフィグファイルからの情報取得を行うインタフェース

Returns:
BytesIO :取得データ

(snip)

関数の説明にあるとおりだが、データソースに対する認証については、いまの実装ではBASIC認証のみに対応。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    if 0 < len(http_config_domain):
if __CONFIG_KEY_BASIC_ID not in http_config_domain[0]:
raise CaddeException(
'000201004E',
status_code=None,
replace_str_list=[__CONFIG_KEY_BASIC_ID])

if __CONFIG_KEY_BASIC_PASS not in http_config_domain[0]:
raise CaddeException(
'000201005E',
status_code=None,
replace_str_list=[__CONFIG_KEY_BASIC_PASS])

auth = (
http_config_domain[0][__CONFIG_KEY_BASIC_ID],
http_config_domain[0][__CONFIG_KEY_BASIC_PASS])

(snip)

レスポンスのコンテンツをBytesIOにして戻り値として返す。

common/swagger_server/services/provide_data_http.py:94

1
2
3
4
5
6
7
8
if response.status_code == requests.codes.ok:
return BytesIO(response.content)

if response.status_code == requests.codes.not_found:
raise CaddeException('000201006E')

if response.status_code == requests.codes.unauthorized:
raise CaddeException('000201007E')

なお、FTPの場合はSFTPではなく、FTPのみ。 NGSIの場合は、アクセストークンを取得して利用するようになっている。

提供者コネクタ

提供者コネクタの主なエントリポイントは paths./cadde/api/v4/catalog である。 つまりカタログ詳細検索APIである。

provider/catalog-search/swagger_server/swagger/swagger.yaml:8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  /cadde/api/v4/catalog:
get:
tags:
- Search
summary: API. カタログ検索(詳細検索)
description: |-
詳細検索リクエストを受け付け、メイン制御に処理を依頼する。

Response:
* 処理が成功した場合は200を返す。
* 処理に失敗した場合は、2xx以外を返す。場合によりエラーを示すペイロードがつく場合もある。Responsesセクションを参照すること。
operationId: search

(snip)

上記の通り、呼び出されるのは search 関数。

provider/catalog-search/swagger_server/controllers/search_controller.py:15

1
2
3
4
5
6
7
8
9
10
11
12
13
def search(q=None, Authorization=None):  # noqa: E501
"""API. カタログ検索(詳細検索)

提供者カタログサイトからCKANカタログ情報を取得する. Response: * 処理が成功した場合は200を返す * 処理に失敗した場合は、2xx以外を返す。Responsesセクション参照。 # noqa: E501

:param q: CKAN検索条件クエリ CKAN APIに準拠
:type q: str
:param Authorization: 認証トークン
:type Authorization: str

:rtype: None
"""
(snip)

カタログ詳細検索用のクエリ、認証トークンなどを受領し、それを用いて詳細検索用のCKANに問い合わせる。

provider/catalog-search/swagger_server/controllers/search_controller.py:41

1
data = search_catalog_ckan(query_string, authorization, external_interface)

search_catalog_ckan関数は、services内に定義されている。

provider/catalog-search/swagger_server/services/service.py:8

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
def search_catalog_ckan(
query_string: str,
auth_token: str,
external_interface: ExternalInterface) -> str:
"""
コネクタメインに詳細検索を送信する

Args:
query_string str : 検索条件のクエリストリング
auth_token str : HTTPリクエストヘッダとしての認証トークン
external_interface ExternalInterface : コネクタ外部とのインタフェース

Returns:
str : 取得したデータ文字列

Raises:
Cadde_excption : ステータスコード200 OKでない場合 エラーコード : 010101002E

"""

response = external_interface.http_get(
__ACCESS_POINT_URL + query_string, {'Authorization': auth_token})

if response.status_code != 200:
raise CaddeException(
message_id='010101002E',
status_code=response.status_code,
replace_str_list=[
response.text])
else:
return response.text

ちなみに、common側にもあるのだが、そちらはデータ提供者コネクタからは使われていない様子。 common/swagger_server/services/ckan_access.py これは、セットアップスクリプトにより、利用者側コネクタ内にシンボリックリンクが作られるようになっているが、呼び出されている形跡がない?

参考

共有

EDCステータス変化の様子の謎メモ

メモ

EDC/Samples のステータス変化の謎メモ。 調べたのは、コミットb7e2220のバージョン。

このバージョンで扱うEDC Connectorのバージョンは以下。

gradle/libs.versions.toml:7

1
edc = "0.8.1"
1
git clone git@github.com:eclipse-edc/Connector.git -b refs/tags/v0.8.1
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
diff --git a/gradle.properties b/gradle.properties
index 4511d8eb5..1c57d1403 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,7 +1,8 @@
group=org.eclipse.edc
-version=0.8.1-SNAPSHOT
+version=0.8.1
# for now, we're using the same version for the autodoc plugin and the processor, but that could change in the future
-annotationProcessorVersion=0.8.1-SNAPSHOT
-edcGradlePluginsVersion=0.8.1-SNAPSHOT
+annotationProcessorVersion=0.8.1
+edcGradlePluginsVersion=0.8.1
edcScmUrl=https://github.com/eclipse-edc/Connector.git
edcScmConnection=scm:git:git@github.com:eclipse-edc/Connector.git
+org.gradle.jvmargs=-Xmx4096M
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index bdd4f8d83..ac3efafe5 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -9,7 +9,7 @@ atomikos = "6.0.0"
awaitility = "4.2.1"
bouncyCastle-jdk18on = "1.78.1"
cloudEvents = "4.0.1"
-edc = "0.8.1-SNAPSHOT"
+edc = "0.8.1"
failsafe = "3.3.2"
h2 = "2.3.230"
httpMockServer = "5.15.0"

デバッガをアタッチして動作を確認

ひとまずContract Negotiation。

どうやらProvider側では、Consumerからリクエストが届いたときに、 org.eclipse.edc.protocol.dsp.negotiation.http.api.controller.DspNegotiationApiController#initialContractRequestが呼ばれる様子。

Provider側でContractNegotiationのログが出るのは2箇所。

org.eclipse.edc.connector.controlplane.services.contractnegotiation.ContractNegotiationProtocolServiceImpl#update ここには、PROVIDER型のContractNegotiationが渡されてくる。

もうひとつは org.eclipse.edc.statemachine.AbstractStateEntityManager#update

1
2
3
4
5
6
protected void update(E entity) {
store.save(entity);
monitor.debug(() -> "[%s] %s %s is now in state %s"
.formatted(this.getClass().getSimpleName(), entity.getClass().getSimpleName(),
entity.getId(), entity.stateAsString()));
}

org.eclipse.edc.connector.controlplane.contract.negotiation.ProviderContractNegotiationManagerImpl が用いいられる。

型階層は以下の通り。

1
2
3
core/common/lib/state-machine-lib/src/main/java/org/eclipse/edc/statemachine/AbstractStateEntityManager.java
core/control-plane/control-plane-contract/src/main/java/org/eclipse/edc/connector/controlplane/contract/negotiation/AbstractContractNegotiationManager.java
core/control-plane/control-plane-contract/src/main/java/org/eclipse/edc/connector/controlplane/contract/negotiation/ProviderContractNegotiationManagerImpl.java

org.eclipse.edc.connector.controlplane.contract.negotiation.ProviderContractNegotiationManagerImpl内のプロセッサの定義で、リクエストなどが届いたときの動作を定義。

org.eclipse.edc.connector.controlplane.contract.negotiation.ProviderContractNegotiationManagerImpl#configureStateMachineManager

1
2
3
4
5
6
7
8
9
10
protected StateMachineManager.Builder configureStateMachineManager(StateMachineManager.Builder builder) {
return builder
.processor(processNegotiationsInState(OFFERING, this::processOffering))
.processor(processNegotiationsInState(REQUESTED, this::processRequested))
.processor(processNegotiationsInState(ACCEPTED, this::processAccepted))
.processor(processNegotiationsInState(AGREEING, this::processAgreeing))
.processor(processNegotiationsInState(VERIFIED, this::processVerified))
.processor(processNegotiationsInState(FINALIZING, this::processFinalizing))
.processor(processNegotiationsInState(TERMINATING, this::processTerminating));
}

ここで管理されるステータスはいかに定義されている。

org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public enum ContractNegotiationStates {

INITIAL(50),
REQUESTING(100),
REQUESTED(200),
OFFERING(300),
OFFERED(400),
ACCEPTING(700),
ACCEPTED(800),
AGREEING(825),
AGREED(850),
VERIFYING(1050),
VERIFIED(1100),
FINALIZING(1150),
FINALIZED(1200),
TERMINATING(1300),
TERMINATED(1400);

(snip)

例えば、とあるケースではREQUESTED(200)の次はAGREEING(825)に変わった。

org.eclipse.edc.connector.controlplane.contract.negotiation.ProviderContractNegotiationManagerImpl#processAgreeing がProvider側が合意をするためのメソッド。

以下の処理を行うとAgreement情報が生成される。

org/eclipse/edc/connector/controlplane/contract/negotiation/ProviderContractNegotiationManagerImpl.java:121

1
2
3
4
5
6
7
8
9
10
11
12
var agreement = Optional.ofNullable(negotiation.getContractAgreement())
.orElseGet(() -> {
var lastOffer = negotiation.getLastContractOffer();

return ContractAgreement.Builder.newInstance()
.contractSigningDate(clock.instant().getEpochSecond())
.providerId(participantId)
.consumerId(negotiation.getCounterPartyId())
.policy(lastOffer.getPolicy().toBuilder().type(PolicyType.CONTRACT).build())
.assetId(lastOffer.getAssetId())
.build();
});

以下のような感じ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
agreement = {ContractAgreement@7424} 
id = "b092de56-c860-4972-8e09-e00e77ce97c4"
providerId = "provider"
consumerId = "consumer"
contractSigningDate = 1728916940
assetId = "assetId"
policy = {Policy@7429}
permissions = {ArrayList@7431} size = 0
prohibitions = {ArrayList@7432} size = 0
obligations = {ArrayList@7433} size = 0
profiles = {ArrayList@7434} size = 0
extensibleProperties = {HashMap@7435} size = 0
inheritsFrom = null
assigner = null
assignee = null
target = "assetId"
type = {PolicyType@7436} "CONTRACT"

dispatchメソッド。

org/eclipse/edc/connector/controlplane/contract/negotiation/ProviderContractNegotiationManagerImpl.java:136

1
2
3
4
5
6
return dispatch(messageBuilder, negotiation, Object.class)
.onSuccess((n, result) -> transitionToAgreed(n, agreement))
.onFailure((n, throwable) -> transitionToAgreeing(n))
.onFatalError((n, failure) -> transitionToTerminated(n, failure.getFailureDetail()))
.onRetryExhausted((n, throwable) -> transitionToTerminating(n, format("Failed to send agreement to consumer: %s", throwable.getMessage())))
.execute("[Provider] send agreement");

上記の通り、成功か失敗か(どのような失敗か)によってステート変更が変わる。

ここに渡ってくるnegotiationは以下のような感じ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
negotiation = {ContractNegotiation@7419} 
callbackAddresses = {ArrayList@7440} size = 0
correlationId = "028d3abb-5086-4324-be77-087095f382c0"
counterPartyId = "consumer"
counterPartyAddress = "http://localhost:29194/protocol"
protocol = "dataspace-protocol-http"
type = {ContractNegotiation$Type@7444} "PROVIDER"
contractAgreement = null
contractOffers = {ArrayList@7445} size = 1
protocolMessages = {ProtocolMessages@7446}
state = 825
stateCount = 1
stateTimestamp = 1728916898826
traceContext = {HashMap@7447} size = 0
errorDetail = null
pending = false
updatedAt = 1728916898826
id = "af2c8a47-022f-450c-87e2-c425de871ab1"
clock = {Clock$SystemClock@7422} "SystemClock[Z]"
createdAt = 1728916898526

org.eclipse.edc.connector.controlplane.contract.negotiation.AbstractContractNegotiationManager#dispatch

メッセージビルドの様子。

org/eclipse/edc/connector/controlplane/contract/negotiation/AbstractContractNegotiationManager.java:93

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
messageBuilder.counterPartyAddress(negotiation.getCounterPartyAddress())
.counterPartyId(negotiation.getCounterPartyId())
.protocol(negotiation.getProtocol())
.processId(Optional.ofNullable(negotiation.getCorrelationId()).orElse(negotiation.getId()));

if (type() == ContractNegotiation.Type.CONSUMER) {
messageBuilder.consumerPid(negotiation.getId()).providerPid(negotiation.getCorrelationId());
} else {
messageBuilder.providerPid(negotiation.getId()).consumerPid(negotiation.getCorrelationId());
}

if (negotiation.lastSentProtocolMessage() != null) {
messageBuilder.id(negotiation.lastSentProtocolMessage());
}

var message = messageBuilder.build();

これによりビルドされるメッセージは以下のようなもの。

1
2
3
4
5
6
7
8
9
message = {ContractAgreementMessage@7463} 
contractAgreement = {ContractAgreement@7424}
id = "c8a7b6dc-7e44-4bcd-9645-a12d5f8690b6"
processId = "028d3abb-5086-4324-be77-087095f382c0"
consumerPid = "028d3abb-5086-4324-be77-087095f382c0"
providerPid = "af2c8a47-022f-450c-87e2-c425de871ab1"
protocol = "dataspace-protocol-http"
counterPartyAddress = "http://localhost:29194/protocol"
counterPartyId = "consumer"

org/eclipse/edc/connector/controlplane/contract/negotiation/AbstractContractNegotiationManager.java:112

1
return entityRetryProcessFactory.doAsyncStatusResultProcess(negotiation, () -> dispatcherRegistry.dispatch(responseType, message));

上記の通り、org.eclipse.edc.spi.message.RemoteMessageDispatcherRegistry を使ってメッセージを送信。 実装は org.eclipse.edc.connector.core.message.RemoteMessageDispatcherRegistryImpl である。

org/eclipse/edc/connector/core/message/RemoteMessageDispatcherRegistryImpl.java:41

1
2
3
4
5
6
7
8
9
public <T> CompletableFuture<StatusResult<T>> dispatch(Class<T> responseType, RemoteMessage message) {
Objects.requireNonNull(message, "Message was null");
var protocol = message.getProtocol();
var dispatcher = dispatchers.get(protocol);
if (dispatcher == null) {
return failedFuture(new EdcException("No provider dispatcher registered for protocol: " + protocol));
}
return dispatcher.dispatch(responseType, message);
}

protocolにはdataspace-protocol-httpが入る。

dispatcherには以下のようなものが入る。

1
2
3
4
5
6
7
8
dispatcher = {DspHttpRemoteMessageDispatcherImpl@7489} 
handlers = {HashMap@7491} size = 13
policyScopes = {HashMap@7492} size = 12
httpClient = {EdcHttpClientImpl@7493}
identityService = {MockIdentityService@7494}
policyEngine = {PolicyEngineImpl@7495}
tokenDecorator = {DspHttpCoreExtension$lambda@7496}
audienceResolver = {IamMockExtension$lambda@7497}

Policyの判定

Policy定義の際に呼び出されるやつ。

org/eclipse/edc/connector/controlplane/api/management/policy/v3/PolicyDefinitionApiV3Controller.java:57

1
2
3
4
5
@POST
@Override
public JsonObject createPolicyDefinitionV3(JsonObject request) {
return createPolicyDefinition(request);
}

コネクタに登録した際に、ログに残るやつ

org/eclipse/edc/connector/controlplane/api/management/policy/BasePolicyDefinitionApiController.java:87

1
2
3
var createdDefinition = service.create(definition)
.onSuccess(d -> monitor.debug(format("Policy Definition created %s", d.getId())))
.orElseThrow(exceptionMapper(PolicyDefinition.class, definition.getId()));

コントラクトのバリデーションサービスは以下。

org.eclipse.edc.connector.controlplane.contract.validation.ContractValidationServiceImpl

この中でorg.eclipse.edc.connector.controlplane.contract.validation.ContractValidationServiceImpl#evaluatePolicyが呼ばれる。

org/eclipse/edc/connector/controlplane/contract/validation/ContractValidationServiceImpl.java:154

1
2
3
4
5
6
7
8
private Result<Policy> evaluatePolicy(Policy policy, String scope, ParticipantAgent agent, ContractOfferId offerId) {
var policyContext = PolicyContextImpl.Builder.newInstance().additional(ParticipantAgent.class, agent).build();
var policyResult = policyEngine.evaluate(scope, policy, policyContext);
if (policyResult.failed()) {
return failure(format("Policy in scope %s not fulfilled for offer %s, policy evaluation %s", scope, offerId.toString(), policyResult.getFailureDetail()));
}
return Result.success(policy);
}

上記のpolicyEngineには以下が入っている。

1
2
3
4
5
6
7
policyEngine = {PolicyEngineImpl@7419} 
constraintFunctions = {TreeMap@7462} size = 1
dynamicConstraintFunctions = {ArrayList@7503} size = 0
ruleFunctions = {TreeMap@7454} size = 0
preValidators = {HashMap@7423} size = 0
postValidators = {HashMap@7517} size = 0
scopeFilter = {ScopeFilter@7509}

ポリシー評価 org.eclipse.edc.policy.evaluator.PolicyEvaluator

デフォルトのポリシーエンジン org.eclipse.edc.policy.engine.PolicyEngineImpl

元になったインターフェース org.eclipse.edc.policy.engine.spi.PolicyEngine

コントラクトネゴシエーションが走ると、Providerで以下が走る。

org.eclipse.edc.policy.engine.PolicyEngineImpl#evaluate org/eclipse/edc/policy/engine/PolicyEngineImpl.java:69

1
2
3
4
5
6
    public Result<Void> evaluate(String scope, Policy policy, PolicyContext context) {
var delimitedScope = scope + ".";

var scopedPreValidators = preValidators.entrySet().stream().filter(entry -> scopeFilter(entry.getKey(), delimitedScope)).flatMap(l -> l.getValue().stream()).toList();

(snip)

評価はPolicyEvaluatorが行う。

org/eclipse/edc/policy/engine/PolicyEngineImpl.java:79

1
var evalBuilder = PolicyEvaluator.Builder.newInstance();

org/eclipse/edc/policy/engine/PolicyEngineImpl.java:115

1
var result = evaluator.evaluate(filteredPolicy);

filteredPolicyには以下のようなものが入る。

1
2
3
4
5
6
7
8
9
10
11
filteredPolicy = {Policy@7361} 
permissions = {ArrayList@7364} size = 0
prohibitions = {ArrayList@7365} size = 0
obligations = {ArrayList@7366} size = 0
profiles = {ArrayList@7367} size = 0
extensibleProperties = {HashMap@7368} size = 0
inheritsFrom = null
assigner = null
assignee = null
target = null
type = {PolicyType@7369} "SET"

org/eclipse/edc/policy/evaluator/PolicyEvaluator.java:66

1
2
3
4

public PolicyEvaluationResult evaluate(Policy policy) {
return policy.accept(this) ? new PolicyEvaluationResult() : new PolicyEvaluationResult(ruleProblems);
}

org.eclipse.edc.policy.evaluator.PolicyEvaluator#visitPolicyが呼ばれる。

org/eclipse/edc/policy/evaluator/PolicyEvaluator.java:71

1
2
3
4
5
6
public Boolean visitPolicy(Policy policy) {
policy.getPermissions().forEach(permission -> permission.accept(this));
policy.getProhibitions().forEach(prohibition -> prohibition.accept(this));
policy.getObligations().forEach(duty -> duty.accept(this));
return ruleProblems.isEmpty();
}

上記の通り、もしpolicyが空だと何も生じず、ruleProblemsが空なのでTrueが返る。

アセットセレクタ

org.eclipse.edc.connector.controlplane.contract.validation.ContractValidationServiceImpl#validateInitialOffer(org.eclipse.edc.connector.controlplane.contract.spi.validation.ValidatableConsumerOffer, org.eclipse.edc.spi.agent.ParticipantAgent) が用いられる。

org/eclipse/edc/connector/controlplane/contract/validation/ContractValidationServiceImpl.java:144

1
2
3
4
5
var testCriteria = new ArrayList<>(consumerOffer.getContractDefinition().getAssetsSelector());
testCriteria.add(new Criterion(Asset.PROPERTY_ID, "=", consumerOffer.getOfferId().assetIdPart()));
if (assetIndex.countAssets(testCriteria) <= 0) {
return failure("Asset ID from the ContractOffer is not included in the ContractDefinition");
}

AssetIndexの型階層は以下の通り。

1
2
3
AssetIndex (org.eclipse.edc.connector.controlplane.asset.spi.index)
InMemoryAssetIndex (org.eclipse.edc.connector.controlplane.defaults.storage.assetindex)
SqlAssetIndex (org.eclipse.edc.connector.controlplane.store.sql.assetindex)

このうち、手元の環境で呼び出されたのはInMemoryAssetIndex。

org/eclipse/edc/connector/controlplane/defaults/storage/assetindex/InMemoryAssetIndex.java:112

1
2
3
public long countAssets(List<Criterion> criteria) {
return filterBy(criteria).count();
}

org.eclipse.edc.connector.controlplane.defaults.storage.assetindex.InMemoryAssetIndex#filterBy

1
2
3
4
5
6
7
8
private Stream<Asset> filterBy(List<Criterion> criteria) {
var predicate = criteria.stream()
.map(criterionOperatorRegistry::toPredicate)
.reduce(x -> true, Predicate::and);

return cache.values().stream()
.filter(predicate);
}

クライテリアの評価用のPredicate生成はorg.eclipse.edc.spi.query.CriterionOperatorRegistry。実装はorg.eclipse.edc.query.CriterionOperatorRegistryImpl

org.eclipse.edc.query.CriterionOperatorRegistryImpl#toPredicate

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
@Override
public <T> Predicate<T> toPredicate(Criterion criterion) {
var predicate = operatorPredicates.get(criterion.getOperator().toLowerCase());
if (predicate == null) {
throw new IllegalArgumentException(format("Operator [%s] is not supported.", criterion.getOperator()));
}

return t -> {

var operandLeft = (String) criterion.getOperandLeft();

var property = propertyLookups.stream()
.map(it -> it.getProperty(operandLeft, t))
.filter(Objects::nonNull)
.findFirst()
.orElse(null);

if (property == null) {
return false;
}

return predicate.test(property, criterion.getOperandRight());
};

}

参考

共有

llama cpp

メモ

クローンとコンパイル

build.md に記載の通り、クローンしてmakeする。

1
2
git clone git@github.com:ggerganov/llama.cpp.git
make

Llamaのコンバート

念の為仮想環境構築してから、必要なPythonパッケージをインストール。

1
2
3
python3 -m venv venv
. venv/bin/activate
pip install -r requirements.txt

参考

共有

Llama3.2

メモ

Llama 3.2 Revolutionizing edge AI and vision with open, customizable models にある通り、 2024/9/25にLlama3.2がリリースされた。

Llama公式Llamaダウンロード からモデルをダウンロードして用いる。

ダウンロードのための情報登録

さらに利用規約に合意すると、具体的なダウンロード手順が表示される。

手順

この手順の中には、リクエストIDやキーなどの情報が含まれているので注意。

もし、Llama CLIをインストールしていなければ以下の通り実行。

1
pip install llama-stack

モデルのリストを確認する。

1
llama model list

結果例

1
2
3
4
5
6
7
8
| Llama3.2-1B                      | meta-llama/Llama-3.2-1B                  | 128K           |
| Llama3.2-3B | meta-llama/Llama-3.2-3B | 128K |
| Llama3.2-11B-Vision | meta-llama/Llama-3.2-11B-Vision | 128K |
| Llama3.2-90B-Vision | meta-llama/Llama-3.2-90B-Vision | 128K |
| Llama3.2-1B-Instruct | meta-llama/Llama-3.2-1B-Instruct | 128K |
| Llama3.2-3B-Instruct | meta-llama/Llama-3.2-3B-Instruct | 128K |
| Llama3.2-11B-Vision-Instruct | meta-llama/Llama-3.2-11B-Vision-Instruct | 128K |
| Llama3.2-90B-Vision-Instruct | meta-llama/Llama-3.2-90B-Vision-Instruct | 128K |

モデルをダウンロードして用いる。ここではテストなので小さなモデルを用いる。

1
2
MODEL_ID=Llama3.2-3B
llama model download --source meta --model-id $MODEL_ID

上記コマンドを実行すると、キー付きのURL入力を求められる。 先程表示した手順の「4」に記載されたURLを入力する。

このメモを書いている時点では混んでいるのか、ダウンロードにそれなりに時間がかかる。

ダウンロードが完了すると、以下のディレクトリにチェックポイントがダウンロードされているはず。

1
~/.llama/checkpoints/Llama3.2-3B

参考

共有

ComfyUI

メモ

導入方法

ComfyUIとは?ノードベースGUIでStable Diffusionを最大活用する機能・特徴・導入方法徹底解説! に導入方法などの基礎が書いてある。 ComfyUIの導入方法が2種類紹介されているが、他のツールなどの管理も兼ねるため、ここではStability Matrixを用いた。

StabilityMatrix のリリースから各環境用のインストーラをダウンロード。 執筆時点では、 v2.12.0が最新だった。

Windwows用は以下。

https://github.com/LykosAI/StabilityMatrix/releases/download/v2.12.0/StabilityMatrix-win-x64.zip

使い方

画像生成AI「Stable Diffusion」を使い倒す! モジュラーシンセみたいな「ComfyUI」をインストール が参考になる。 先に StabilityMatrix のModel Browserで適当なモデルをダウンロードしておくこと。 StabilityMatrix で ComfyUI をインストールした場合、 ComfyUIのコンフィグが自動で編集され、ダウンロードしたモデルのパスが追加されるようになっている。ただし、新しいモデルをダウンロードした場合は ComfyUIの再起動が必要。

参考

共有

Jan

メモ

LLMプラットフォームの「JAN」、様々なモデルでチャットAIを作れる を参考に、 Janを使ってみる。

Janの公式サイト に掲載の通り、パッケージをダウンロードする。 ここではUbuntu環境を前提にAppImageを用いる。 このとき最新版だった、 jan-linux-x86_64-0.5.4.AppImage をダウンロードした。自分の環境の場合は、 ~/Applications/ 以下にダウンロードした。

Ansibleプレイブックとしてはいかが参考になる。

  • https://github.com/dobachi/ansible-miscs/blob/master/playbooks/conf/linux/jan.yml
  • https://github.com/dobachi/ansible-miscs/blob/master/roles/jan/tasks/main.yml

環境が整ったら実行。

1
$ ~/Applications/jan/default/jan-linux-x86_64-0.5.4.AppImage

初回起動時はインストールするかどうかを聞かれるのでYesとする。

GUIからひとまず lama3.1-8b-instruct を試すことにした。

続いて、 LLMプラットフォームの「JAN」、様々なモデルでチャットAIを作れる の記事でも用いられている、huggingfaceからモデルを探して試す。 Huggingfaceについては、 Hugging Faceとは?Hugging Face Hubの機能や使い方・ライブラリをわかりやすく解説! を参照。

参考

共有

US keyboard layout on Ubuntu22

メモ

VMWare WorkstationでUbuntu 22系をインストールしたところ、物理キーボードがUS配列なのに、日本語配列で認識されてしまったので直した。

Ubuntu 22.04 でキーボードレイアウトおかしくなる問題。 にも記載されているのだが、/usr/share/ibus/component/mozc.xmlには以下のような記載がある。

1
2
3
4
5
<!-- Settings of <engines> and <layout> are stored in ibus_config.textproto -->
<!-- under the user configuration directory, which is either of: -->
<!-- * $XDG_CONFIG_HOME/mozc/ibus_config.textproto -->
<!-- * $HOME/.config/mozc/ibus_config.textproto -->
<!-- * $HOME/.mozc/ibus_config.textproto -->

自分の環境だと、以下のような場所にあった。

1
2
3
4
5
$ sudo find / -name *ibus_config.textproto*

(snip)

/home/dobachi/.config/mozc/ibus_config.textproto

以下のようにlayoutを変更する。もともとdefaultである。

1
$ cat /home/dobachi/.config/mozc/ibus_config.textproto
1
2
3
4
5
engines {
name : "mozc-jp"
longname : "Mozc"
layout : "us"
}

参考

共有

Building Block of Data Spaces

メモ

各イニシアティブのビルディングブロックのまとめかたを軽くメモしたもの。

メモのボード

参考

共有

Data Sovereignty

メモ

世における基本的な考え方

データ主権(Data Sovereignty)については色々な解釈をされているが、 一つの考え方は特定地域で収集・保存されたデータがその地域の法律にしたがって運用されること、という考え方が挙げられる。

この考え方を示している記事が多い。

あまり量が多くなっても混乱するので、主要な話だけさらってみるようにする。

簡単なまとめメモ

セーフハーバー法

関連することとして、セーフハーバー法が挙げられる。つまり、特定の条件を満たす限り、違法行為とみなされない範囲を定めた規定である。 総務省の平成29年版白書では、 第1部 特集 データ主導経済と社会変革 にEUと米国のセーフハーバーにちうて記載あり。 米国とEUの間では2000年に個人データ移転についての原則を記したセールハーバー協定を締結したのだが、いわゆるSnowden事件により欧州司法裁判所による無効の判断。そして、その後、2016/2にEU-USプライバシーシールが新たに制定。

Snowden事件とデータローカライゼーション

データ保護については、Snowden事件や米国政府によるマイクロソフトのメール検閲を挙げるケースがある。 2013年ころのことである。参考: 世界を揺るがしたスノーデン事件から5年--変わったこと、変わらなかったこと

ここから、欧州のGDPR(2016)やカナダのデータ主権措置に関する戦略(2016-2020)などのデータ越境移転に関する規定、 つまりデータローカライゼーション規定の流れが考えられる。 参考: データローカライゼーション規制とは?必要となる背景や現状を徹底解説 2022年当時の欧州、米国などのデータローカライゼーション規定については、経産省の「データの越境移転に関する研究会」資料がわかりやすい。 参考: 各国のデータガバナンスにおけるデータ越境流通に関連する制度調査

著名なところだと、中国サイバーセキュリティ法(2017/6施行)の第37条が挙げられる。参考: China’s data localization 他にも、ベトナムのインターネットサービスとオンライン情報コンテンツの管理、提供、仕様に関する法令(2013/9制定)、インドネシアの法令では国内データセンタへのデータ配置が求められている(2014/1)、ロシアのデータローカライゼーション法(2015/9発行)など。

一方で、このようなデータローカライゼーションに関する制約はビジネスや技術発展において足かせとなるという主旨の論調もあった。 参考: The Cloud’s Biggest Threat Are Data Sovereignty Laws

日本からは、2019/1のスイス・ジュネーブにおける、いわゆるダボス会議にて、当時の首相だった安倍晋三からDFFTの提言があった。

GDPRとData Act

データローカライゼーションの議論をする際には、EUにおけるGDPR(一般データ保護規則)は外せない。 個人データの移転と処理について規定したものである。2018年適用開始。参考: GDPR(General Data Protection Regulation:一般データ保護規則)

一方で、個人データだけではなく、非個人データを含むデータの利用促進、データへの公平なアクセスおよびその利用を目的として2020年の欧州データ戦略の一環としてData Actが成立。2024/1発効、2025/9施行。 IoT機器が生成するデータであり、非個人データも対象となる。自動車、スマート家電、などなど影響大。 参考: EUデータ法の解説 - 適用場面ごとのルールと日本企業が講ずべき実務対応を整理Data Act

両者は補完関係と考えることもできる。

また、Data ActはData Governance Actとも補完関係と考えられる。

日本とEU・英国の間のデータ越境移転に関するセーフハーバー

欧州から日本に対しては、2019/1/23に十分性認定がされており、EU域内と同等の個人データ保護水準を持つ国と認定されている。 参考: 日EU間・日英間のデータ越境移転について 日本は個人情報保護法第28条でEUを指定、EUはGDPR第45条に基づき日本を十分性認定。

ただし、個人情報保護法に基づき補完的ルール適用が必要。参考: 補完的ルール 補完的ルールでは以下のような規定。

  • 要配慮個人情報の範囲の拡張
  • 保有個人データの範囲の拡張
  • データ主体の権利保護
  • データの安全管理措置

まとめ

データ主権を議論する際には、基本的事項としてデータを収集・保存した地域における法律遵守が主旨になり、 さらにいわゆるデータローカライゼーションの原則が挙げられる。個人情報保護の観点がひとつは挙げられるが、それに限らない。 一方で経済や技術発展のためには、バランスをとったデータ越境移転が必要であり、DFFTのような提唱も行われている。

参考

共有

Use HOME in case of using sudo [ansible]

メモ

ansible で sudo 時に実行ユーザの HOME 環境変数を取得する を参考にした。

以下のようなロールを作っておき、プレイブックの最初の方に含めておくと良い。 以降のロール実行時に、変数 ansible_home に格納された値を利用できる。

roles/regsiter_home/tasks/main.yml

1
2
3
4
5
6
7
8
9
10
11
- block:
- name: Get ansible_user home directory
shell: 'getent passwd "{{ansible_env.SUDO_USER}}" | cut -d: -f6'
register: ansible_home_result

- name: Set the fact for the other scripts to use
set_fact:
ansible_home: '{{ansible_home_result.stdout}}'
cacheable: true
tags:
- register_home

元記事と変えているのは、SUDO実行ユーザ名を取るところ。環境変数から取るようにしている。

プレイブックでは以下のようにする。

playbooks/something.yml

1
2
3
4
- hosts: "{{ server | default('mypc') }}"
roles:
- register_home
- something

参考

共有