量子プログラミング入門→量子コンピューティングサービス構築入門

量子コンピューター初心者の筆者が日々学びながら、量子コンピュータ上で量子プログラミングできるようなるまでの学習日記を記録していきます。 内容は量子コンピューター入門者向けで、専門家の方からするとおかしな内容があるかもしれません。その際はコメント等でお知らせください!

pyDriveを使ってフォルダ丸ごとアップロードするプログラム


はじめに

pyDriveを使うための事前準備はこちらに書いてあるのでご参考にしてください。
今回作成したのはローカルのディレクトリにあるファイルを一括転送するプログラムで、2階層のディレクトリ構造で下記のように2階層目にデータファイルがある構造を想定しています。
この構成でtestディレクトリを丸々アップロードするプログラムです。
/home/static/Data/test
|
|
+- dir1
| |
| +- Data1
| +- Data2
|
+- dir2
|
+- Data3
+- Data4



とりあえずプログラム

実際に使う場合は下記を自分の環境に合わせて設定してください。その他はコピペすれば動くはずです。
    parentid:  Google Drive上のアップロード先フォルダのIDを指定、この下にデータがアップロードされます
    path: ローカルのデータ格納フォルダ

import os
import sys
import datetime

from pydrive.auth import GoogleAuth
from pydrive.drive import GoogleDrive

def auth_gd():
    gauth = GoogleAuth()
    gauth.CommandLineAuth()
    drive = GoogleDrive(gauth)
    return drive

def upload2googledrive():
    drive = auth_gd()
    
    parentid = 'xxxxxxxx' #project-root directory
    path = "/home/static/Data/test" #path to local data

    # "test"フォルダをGoogle Drive上に作成
    t_folder = create_dir(parentid, os.path.basename(path), drive)

    # ローカルのディレクトリ一覧取得
    flag = ""
    dir_list=get_dirlist(path)
    for d in dir_list:
        d_folder = create_dir(t_folder['id'], os.path.basename(d), drive)
#ローカルのファイル一覧取得 file_list=get_filelist(d) for f in file_list: upload_file(d_folder['id'], f, drive)
# ディレクトリがGoogle Drive上に存在するかどうかをチェックし、
# 存在しなければ作成、すでに存在すれば既存のフォルダを返す def create_dir(pid, fname, drive=None): if drive == None: drive = auth_gd() ret = check_files(pid, fname, drive) if ret == False: folder = drive.CreateFile({'title': fname, 'mimeType': 'application/vnd.google-apps.folder'}) folder['parents']= [{'id': pid}] folder.Upload() else: folder = ret print(folder['title']+" exists") return folder
#同じ名前のファイルがGoogle Drive上に存在するかチェックし、
#存在しなければアップロード、存在すれば既存のファイルを返す def upload_file(pid, fname, drive=None): if drive == None: drive = auth_gd() ret = check_files(pid, fname, drive) if ret == False: gf = drive.CreateFile() gf['parents']= [{'id': pid}] gf.SetContentFile(fname) gf['title'] = os.path.basename(fname) gf.Upload() else: gf = ret print(gf['title']+" exists") return gf
#Google Drive上にその名前のファイル/フォルダがあるかチェック、なければFalseを、あれば既存のファイル/フォルダを返す def check_files(pid, fname, drive=None): if drive == None: drive = auth_gd() query = '"{}" in parents'.format(pid) query += ' and title = "' + os.path.basename(fname) + '"' list = drive.ListFile({'q': query}).GetList() if len(list)> 0: return list[0] return False def get_dirlist(basedir): ret = [] for path, dirnames, filenames in os.walk(basedir): for dir in sorted(dirnames): d = os.path.join(path, dir) ret.append(d) return ret def get_filelist(basedir): ret = [] for path, dirnames, filenames in os.walk(basedir): for filename in sorted(filenames): fullpath = os.path.join(path, filename) ret.append(fullpath) return ret if __name__ == '__main__': upload2googledrive()


プログラム解説

Google Driveのファイル管理について

初めに、pyDriveでGoogle Driveを取り扱ううえで厄介なのがファイルがファイル名でなくIDで管理されていることです。IDで管理されているということは、同じ名前のファイルを同じフォルダにアップロードできてしまい、別ファイルとして存在できてしまいます。そのためこのプログラムではファイル名(title)でlistを確認し、同じファイル名のファイルが存在すればそれを返すようにすることで二重生成を防いでいます。毎回フォルダ作成やファイルアップロードのたびにこのチェックが入るので、けっこうなやり取りが発生します。

ファイルのアップロード

        gf = drive.CreateFile()
        gf['parents']= [{'id': pid}]
        gf.SetContentFile(fname)
        gf['title'] = os.path.basename(fname)
        gf.Upload()
CreateFileを使用し、google driveのGoogleDriveFileオブジェクトを生成します。
生成したファイルオブジェクトに属性を設定して、最後にアップロードします。

['parents']:格納するディレクトリのIDを指定します。
Google Driveはパス名やファイル名でデータを管理しておらず、IDを使ってデータ構造を管理します。
自分のIDはアップロード後に、gf['id']で参照することができます。
フォルダを作成し、その下にファイルをアップロードする場合は、作成したフォルダの['id']を取得し、アップロードするファイルの['parents']にフォルダの['id']を設定します。

フォルダの生成

        folder = drive.CreateFile({'title': fname,
                                 'mimeType': 'application/vnd.google-apps.folder'})
        folder['parents']= [{'id': pid}]
        folder.Upload()

フォルダの生成もファイルの生成とほぼ同様です。1つだけポイントは
'mimeType': 'application/vnd.google-apps.folder'
とすることです。

Google Drive上の情報取得

    query = '"{}" in parents'.format(pid)
    query += ' and title = "' + os.path.basename(fname) + '"'

    list =  drive.ListFile({'q': query}).GetList()

ListFile()を使用してGoogle Drive上のファイルの一覧を取得することができます。
'q'を使ってQueryで条件を指定することができます。
ここでは、
  • in parentsで親となるフォルダを指定している
  • title=を使ってファイル・フォルダ名を指定している
で、条件を絞り込んでいます。queryの内容はstrで指定でき、条件を"and"でつなぐことで、複数の条件を指定することができます。andでつなぐ場合はきちんと間にスペースが入っていることを確認しましょう。

ローカルファイルの一覧取得

    ret = []
    for path, dirnames, filenames in os.walk(basedir):
        for dir in sorted(dirnames):
            d = os.path.join(path, dir)
            ret.append(d)

os.walkを使用してローカルのディレクトリ・ファイル一覧を取得します。上記ではディレクトリの一覧のみを取得していますが、ファイルに関しても同じように処理できます。


google-drive-ocamlfuse vs pyDrive性能比較


ubuntu上のシステムからGoogle Driveをストレージとして使用したいと考え、google-drive-ocamlfuseを使ってみたが、性能が非常に遅いのと、長時間通信をしているとファイルシステムがおかしくなり途中で通信できなくなるので、pyDriveでのデータ転送を試してみる。

google-drive-ocamlfuseの使い方はこちらを参照
使用したpyDriveコードはこちらを参照


性能測定結果

1MBのファイル 10個のアップロード

モジュール5回測定の平均時間
pyDrive28.60
ocamlfuse32.38

1MBのファイルを10個アップロードする処理を5回実施し、それぞれの平均時間をとってみた。
ばらつきがあるものの、pyDriveのほうが基本的に早く、平均すると13%ほど早い。
ただ、どちらの処理も10MBのデータ転送に30秒程度かかるのでけっこうな遅さだ。

10MBのファイル 1個のアップロード

モジュール5回測定の平均時間
pyDrive5.89
ocamlfuse4.81

こちらのテストではocamlfuseのほうが早かった。おそらくファイル1個の転送に対してpyDriveでは1回の認証処理が入っているので祖分部分のオーバーヘッドが大きいと思われる。

100MBのファイル 1個のアップロード

モジュール5回測定の平均時間
pyDrive8.29
ocamlfuse8.90

ファイルのサイズを10倍にしてみた。こちらのテストではpyDriveのほうが若干早かった。
転送時間自体は、それぞれ3秒程度伸びだだけで、処理時間の大部分が転送以外の部分に使われていることがわかる。
よくわからない動きをしたのが、ocamlfuseの場合、最初の2回は10秒程度かかっていたが、後半の3回は7秒台にまで短縮され、キャッシュの処理か何かあるのか突然加速した。

その他気になったこと

ocamlfuseはファイルシステムで、おそらくキャッシュを持っている。ubuntuからファイルをGoogle Driveにファイルを作成し、作成したファイルをブラウザで開いたGoogle Driveで削除といった処理をすると、削除が反映されるまでに時間がかかる。syncコマンドも効かず、キャッシュ更新のトリガーはよくわからなかった。

まとめ

基本的にネットワーク越しのストレージなのでベースとなる性能はよくない。いずれのモジュールを使っても性能的には大きな差は出ない。
ただ、小さなファイルを大量にいくつもアップロードする必要がある場合は、pyDriveで独自の処理を書いたほうが良いと思われる。ocamlfuseの場合はそのような処理をしていると途中でエラーがでたりおかしくなる。業務で利用するならocamlfuseは避けたいと思う。

少ない数のファイルを置いたり、軽めの参照をしたりするぐらいだとocamlfuseが便利だ。
pyDriveの難点は簡単なファイル操作でもそれなりのコードが必要で、その点でocamlfuseは通常のファイルシステムとして見えるので非常に便利である。


pyDriveでUbuntuのDockerからGoogle Driveのファイル操作を行う


google-drive-ocamlfuseの問題

google-drive-ocamlfuseを使ってUbuntuのDockerからGoogle Driveをマウントする方法を試してみたのだが、google-drive-ocamlfuseは特別な手続きなしでファイルを操作できる反面、使ってみるとめちゃめちゃ遅くて、小さなファイルのやり取りならなんとかなるが、数十MBのデータの移動などをするとなると実用レベルにないくらい遅い。別の方法も試してみようと思い、pyDriveに行き着く。

pyDriveとは

pyDriveはGoogleのGoogle Drive APIのpython wrapperで、直接APIを呼び出すよりはpythonから呼び出したほうが便利かと思ってこれを試してみる。


Dockerを立ち上げる前に下記のファイルを用意しておく
  • Dockerfile
  • Google APIのClient ID / Client Secret (デスクトップアプリ用のID/Secretを用意)
  • settings.yaml (Google Driveの認証情報などの設定の定義ファイル)
  • gd_pydrive.py (pyDriveのお試しプログラム)

デスクトップアプリ用のClient ID/Client Secretについては作り方をこちらで紹介しています。今回は認証にCommandLineAuth()を使うので"デスクトップアプリ"用のID/Secretを用意してください。

それぞれのファイルは下記。

Dockerfile
FROM ubuntu:latest

RUN apt-get update
RUN apt-get install python3 python3-pip -y
RUN pip3 install pydrive

COPY . /work
WORKDIR /work

とりあえず最低限の構成。ローカルディレクトリに設定ファイル(settings.yaml)とテストプログラム(gd_pydrive.py)を配備しておき、Docker上の/workにコピーする


settings.yaml
client_config_backend: settings
client_config:
  client_id: xxxxx.apps.googleusercontent.com
  client_secret: XXXXxXXXxXXXX

save_credentials: True
save_credentials_backend: file
save_credentials_file: credentials.json

get_refresh_token: True

詳細なオプションは公式マニュアルを参照してもらうとして、
ポイントを説明すると、
  • client_id, client_secretをこのファイルで指定しておく。
  • save_credentialsをTrueにして、ファイルに保存するようにしておくことで次回以降の認証手続きを簡素化させる。
  • get_refresh_tokenをTrueにすることでアクセスするごとにtokenをリフレッシュさせる


gd_pydrive.py
from pydrive.auth import GoogleAuth

gauth = GoogleAuth()
gauth.CommandLineAuth()

調べていると、gauth.LocalWebserverAuth()を使うやり方が多く紹介されているが、ホストのUbuntuからだとうまく行くが、Docker上からだと認証後のリダイレクトがうまく行かず断念。
公式のマニュアルもLocalWebserverAuth()が第一候補だが、 CommandLineAuth() も紹介されており、こちらを使ってみる

You can also use CommandLineAuth() which manually takes code from user at command line.

Docker Image作成+実行


下記コマンドを実行し、Dockerイメージを作成+Dockerを実行
sudo docker build -t gdrive .
sudo docker run  -it gdrive /bin/bash

Docker上でpyDriveをお試し(初回認証)
root@2385921abe7d:/work# python3 gd_pydrive.py
/usr/local/lib/python3.8/dist-packages/oauth2client/_helpers.py:255: UserWarning: Cannot access credentials.json: No such file or directory
  warnings.warn(_MISSING_FILE_MESSAGE.format(filename))
Go to the following link in your browser:

    https://accounts.google.com/o/oauth2/auth?client_id=xxxxxxxxxxxx.apps.googleusercontent.com&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive&access_type=offline&response_type=code&approval_prompt=force

Enter verification code: xxxxxxxxxxxxxxxxxxxxxxxx
Authentication successful.



同じディレクトリにcredentials.jsonが生成され、次回からブラウザによる認証なしで行ける
root@536122a820a4:/work# ls -l
-rw------- 1 root root 1286 Sep 20 13:38  credentials.json
-rw-rw-r-- 1 root root  306 Sep 20 12:57  gd_pydrive.py
-rw-rw-r-- 1 root root  296 Sep 20 12:57  settings.yaml

認証は通るようになったので次回はファイル操作をしてみて性能を見てみることとする。

Ubuntu DockerからGoogle Driveをマウント

0) はじめに

LightsailでWebサイトを運用していてストレージが不足してきたので、ストレージを追加しようと計画していていろいろ調べていましたが、Lightsailのstorageは割高感があり、S3も調べましたが、料金体系が複雑で、試算しているとけっこういい値段になります。Google Driveは100GBで200円程度で、トラフィック量など複雑なことを考えずにすむので、とりあえず試してみようと思いました。

ここで問題はグラフィックコンソールがあるUbuntu DesktopからGoogle Driveをマウントする方法はたくさん紹介されていますが、Lightsailはheadless(グラフィックコンソル / X端末がない状態)で運用しておりそれらの方法が使えません。ここではheadless(グラフィックコンソル / X端末がない状態)でubuntuからGoogle Driveをマウントする方法を紹介します。
さらに、WebサイトはDockerを使って立ち上げているので、UbuntuのDocker ImageからGoogle Driveをマウントする方法を紹介します。

1) Google API設定  Client ID / Client Secretの取得

UbuntuからGoogle Driveをマウントするには"google-drive-ocamlfuse"を使用しますが、"google-drive-ocamlfuse"をheadless/グラフィック端末無しで使用するには通常のGoogle Accountによる認証ではなく、Google APIのclient ID/client secretを用意する必要があり、これらを初めて使う人には少し複雑な作業です。

Google APIのclient ID / client secretはGoogle Developer Consoleから取得可能で、このページにGoogleアカウントを使用してログインします。

a) プロジェクトの作成

no title

まず最初にプロジェクトを作成します。(既存のプロジェクトを使うのでも問題ないです)
Google APIコンソールの左上にあるプロジェクトをクリックすると新しいプロジェクトを作成するボタンがでますので、プロジェクト名を決めて新しいプロジェクトを作成します。

b) 認証情報の作成 → oauth同意画面を設定

1
次に左のメニューの"認証情報"を選択し、"+認証情報を作成"をクリック、"OAuthクライアントID"を選択
  1. 左のメニューの"認証情報"を選択
  2. +認証情報を作成"をクリック
  3. OAuthクライアントID
2
初回は"OAuth同意画面"の設定に行くことになります。
3
この画面が出たら、"外部"を選択します。

c) OAuth同意画面の設定

4
アプリケーション名を入力し、スコープでGoogle Driveを追加する
5

Google Drive APIをスコープに追加
6



  • Google Drive内のすべてのファイルの表示
  • Google Drive内のファイルのメタデータの表示
  • Google Driveのファイルのメタデータの表示
をチェックし、Scopeに追加します。

d) 認証情報の作成 

7
次に認証情報を作成します。もう一度"+認証情報を作成"をクリックします。

8
"デスクトップアプリ"を指定します。

e) client ID/ client secret取得

これでClient ID/ Client Secretが取得できました。

9

2) "google-drive-ocamlfuse"でマウント

a) google-drive-ocamlfuseのインストール

まず最初に下記コマンドを実施し、必要なモジュールをインストールします。
sudo apt-get -y install software-properties-common
sudo add-apt-repository ppa:alessandro-strada/ppa
sudo apt update
sudo apt -y install google-drive-ocamlfuse

b) google-drive-ocamlfuseの実行

インストールができたら下記のようにコマンドを実行しGoogle Driveをマウントします。
google-drive-ocamlfuse /MOUNTPOINT -headless -id YOURID.apps.googleusercontent.com -secret YOURSECRET
MOUNTPOINTはマウントしたいディレクトリを指定、-idと-secretは先ほど取得したclient IDとclient secretを指定します。

初回は下記のようにURLが表示されて、verification codeを入力するように求められます。
10
このURLをコピーし、PCのブラウザからURLを開き認証を実施します。コードが表示されるのでコピーし、入力します。認証がうまく通らない場合はAPIコンソールで登録したアプリケーションの種類などを見直してください。"デスクトップアプリケーション"になっていることが前提です。
11

3) DockerからGoogle Driveをマウント

通常のUbuntuでGoogle Driveをマウントできるようになったので、次にDockerからGoogle Driveをマウントさせてみます。下記のDockerfileでDockerを生成します。
Dockerfile
FROM ubuntu:latest

RUN apt-get update
RUN apt-get install python3 python3-pip -y


ENV TZ=Asia/Tokyo
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

RUN apt-get -y install software-properties-common
RUN add-apt-repository ppa:alessandro-strada/ppa
RUN apt update
RUN apt -y install google-drive-ocamlfuse
RUN mkdir /googledrive
ここでのポイントはDockerfileないでTime Zone設定を実施しておくこと、これをやってないとこの問題にはまります。マウントポイントのディレクトリは/googledriveとしていますが、ここは自由に変更できます。

次にDockerを起動させます。
sudo docker run  -it --rm --privileged -v $(pwd)/vol:/root/.gdfuse google-drive /bin/bash
google-drive-ocamlfuseはホームディレクトリの.gdfuseにクレデンシャル情報を書き出します。このディレクトリがDocker停止のたびに消えてしまうと毎回ブラウザの認証が必要になってしまい不便ですので、ホスト側のディレクトリをマウントしておき消えないようにしておきます。

またdocker runに"--privileged"を入れておく必要があります。これがないと"fuse: device not found, try 'modprobe fuse' first"のエラーが出てしまいます。

これでUbuntu上からGoogle Driveがマウントできアクセスできるようになっているはずです。
12

13



Docker Build中に Configuring tzdataでハング


Docker build中に下記の状態でハングした場合の対策メモ
この状態におちいると、数字を入力しても先に進まなくなってしまう。


Configuring tzdata ------------------ Please select the geographic area in which you live. Subsequent configuration questions will narrow this down by presenting a list of cities, representing the time zones in which they are located. 1. Africa 4. Australia 7. Atlantic 10. Pacific 13. Etc 2. America 5. Arctic 8. Europe 11. SystemV 3. Antarctica 6. Asia 9. Indian 12. US Geographic area:
対策は、このTime Zone設定状態に入らないようにDockerfileで先にtime zone設定をしてしまう。
問題となる箇所の手前に下記のTime Zone設定を入れておくことでTime Zone設定を聞かれなくなる。
ENV TZ=Asia/Tokyo
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

例えばこんな感じです。

Dockerfile
FROM ubuntu:latest

ENV TZ=Asia/Tokyo
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

RUN apt-get -y install software-properties-common
RUN add-apt-repository ppa:alessandro-strada/ppa
RUN apt update
RUN apt -y install google-drive-ocamlfuse
RUN mkdir /mnt/googledrive

↑このページのトップヘ