FUSE PYTHONでG-Storageをマウント出来るようにしてみた

前回、PHP FUSEでG-Storageをマウント出来るようにしてみた - yoshida_eth0の日記で作った奴だと、書き込みが出来なかった。
だから今回はPythonでやってみた。
実行するには、easy_installでoauthとtlsliteを入れる必要あり。
 
インスタンス変数をチェックする所のこの部分、もうちょっとどうにか出来ないかなぁ。

if eval('self.%s' % key) is None:

 
メソッド内にクラスを定義出来るのに感動して、意味もなくメソッド内に定義してみた。
 

FUSE PYTHONインストール

FUSEインストール

yum -y install fuse fuse-devel dkms-fuse
modprobe fuse

yum -y install pkgconfig
echo "export PKG_CONFIG_PATH=/usr/lib/pkgconfig" > /etc/profile.d/pkgconfig.sh
source /etc/profile.d/pkgconfig.sh

 
FUSE PYTHONインストール

easy_install fuse-python

 
その他ライブラリ

easy_install oauth
easy_install tlslite

 

GStorageFS.py

ソース
#!/usr/bin/env python

import sys, os, time, re, base64, hashlib, random
import urllib, urllib2
import errno, stat, fuse
from oauth import oauth
from tlslite.utils import keyfactory

fuse.fuse_python_api = (0, 2)



""" debug """

def debug(txt, eol=True):
	f = open('/tmp/pyfuse.log', 'a')
	f.write(txt)
	if eol:
		f.write("\n")
	f.close()



""" Exception """

class Usage(Exception):
	def __str__(self):
		return """Usage: python GStorageFS.py mount_dir

 parameters :
  method     : Authentication method. (WSSE_a, WSSE_b, OAuth_HMAC_SHA1 or OAuth_RSA_SHA1)
  username   : Username. You should specify this parameter.
  password   : Password. If authentication method is wsse_a, wsse_b or oauth_hmac_sha1, you should specify this parameter.
  privatekey : Privatekey. If authentication method is oauth_rsa_sha1, you should specify this parameter."""



""" OAuthSignatureMethod """

class OAuthSignatureMethod_RSA_SHA1(oauth.OAuthSignatureMethod):
	def get_name(self):
		return 'RSA-SHA1'

	def fetch_private_cert(self, oauth_request):
		abstruct

	def build_signature_base_string(self, oauth_request, consumer, token):
		sig = (
			oauth.escape(oauth_request.get_normalized_http_method()),
			oauth.escape(oauth_request.get_normalized_http_url()),
			oauth.escape(oauth_request.get_normalized_parameters()),
		)
		raw = '&'.join(sig)
		return raw

	def build_signature(self, oauth_request, consumer, token):
		raw = self.build_signature_base_string(oauth_request, consumer, token)
		privatekey = keyfactory.parsePEMKey(self.fetch_private_cert(oauth_request), private=True)
		signature = privatekey.hashAndSign(raw)
		return base64.b64encode(signature)



""" GStorage IO """

class GStorageIO(object):
	def __init__(self):
		self.ssl = False
		self.host = None
		self.basepath = ''

		self.username = None
		self.password = None
		self.privatekey = None

	def request(self, method, path, data=None):
		scheme = 'http'
		if self.ssl:
			scheme = 'https'
		url = '%s://%s%s%s' % (scheme, self.host, self.basepath, path)
		params = {
			'format' : 'bin',
			'method' : method,
		}
		if isinstance(data, dict):
			params.update(data)
			data = None

		try:
			req = self.getRequest(url, params, data)
			response = urllib2.urlopen(req)
		except urllib2.HTTPError, e:
			raise Exception('%s %s' % (e.code, e.read()))
		except Exception, e:
			raise e

		return response

	def init(self):
		abstract

	def getRequest(self, url, params, data):
		abstract


class GStorageIO_WSSE_a(GStorageIO):
	def init(self):
		for key in ['host', 'username', 'password']:
			if eval('self.%s' % key) is None:
				raise Usage()

	def getRequest(self, url, params, data):
		nonce = base64.b64encode(hashlib.sha1(str(time.time() + random.random())).digest())
		created = time.strftime('%Y-%m-%dT%H:%M:%SZ')
		digest = base64.b64encode(hashlib.sha1(nonce + created + self.password).digest())
		wsse = 'UsernameToken Username="%(u)s", PasswordDigest="%(p)s", Nonce="%(n)s", Created="%(c)s"'
		value = dict(u=self.username, p=digest, n=nonce, c=created)

		wsse = wsse % value
		url = '%s?%s' % (url, urllib.urlencode(params))
		return urllib2.Request(url, data, {'X-WSSE' : wsse, 'Content-Type' : 'application/octet-stream'})


class GStorageIO_WSSE_b(GStorageIO):
	def init(self):
		for key in ['host', 'username', 'password']:
			if eval('self.%s' % key) is None:
				raise Usage()

	def getRequest(self, url, params, data):
		nonce = hashlib.sha1((str(time.time() + random.random())).digest())
		created = time.strftime('%Y-%m-%dT%H:%M:%SZ')
		digest = base64.b64encode(hashlib.sha1(nonce + created + self.password).digest())
		wsse = 'UsernameToken Username="%(u)s", PasswordDigest="%(p)s", Nonce="%(n)s", Created="%(c)s"'
		value = dict(u=self.username, p=digest, n=base64.b64encode(nonce), c=created)

		wsse = wsse % value
		url = '%s?%s' % (url, urllib.urlencode(params))
		return urllib2.Request(url, data, {'X-WSSE' : wsse, 'Content-Type' : 'application/octet-stream'})


class GStorageIO_OAuth_HMAC_SHA1(GStorageIO):
	def __init__(self):
		GStorageIO.__init__(self)
		self.consumer = None
		self.signature_method = None

	def init(self):
		for key in ['host', 'username', 'password']:
			if eval('self.%s' % key) is None:
				raise Usage()

		self.signature_method = oauth.OAuthSignatureMethod_HMAC_SHA1()
		self.consumer = oauth.OAuthConsumer(self.username, self.password)

	def getRequest(self, url, params, data):
		method = 'GET'
		if data is not None:
			method = 'POST'
		request  = oauth.OAuthRequest.from_consumer_and_token(self.consumer, http_method=method, http_url=url, parameters=params)
		request.sign_request(self.signature_method, self.consumer, None)
		url = request.to_url()
		return urllib2.Request(url, data, {'Content-Type' : 'application/octet-stream'})


class GStorageIO_OAuth_RSA_SHA1(GStorageIO_OAuth_HMAC_SHA1):
	def __init__(self):
		GStorageIO_OAuth_HMAC_SHA1.__init__(self)

	def init(self):
		for key in ['host', 'username', 'privatekey']:
			if eval('self.%s' % key) is None:
				raise Usage()

		class OAuthSignatureMethod_RSA_SHA1_Wrapper(OAuthSignatureMethod_RSA_SHA1):
			def __init__(self, privatekey):
				self.privatekey = privatekey

			def fetch_private_cert(self, oauth_request):
				return self.privatekey

		self.signature_method = OAuthSignatureMethod_RSA_SHA1_Wrapper(self.privatekey)
		self.consumer = oauth.OAuthConsumer(self.username, None)



""" FUSE Stat """

class GStorageStat(fuse.Stat):
	def __init__(self):
		self.st_mode = 0
		self.st_ino = 0
		self.st_dev = 0
		self.st_nlink = 0
		self.st_uid = 0
		self.st_gid = 0
		self.st_size = 0
		self.st_atime = 0
		self.st_mtime = 0
		self.st_ctime = 0



""" FUSE FS """

class GStorageFS(fuse.Fuse):
	def __init__(self, io, *args, **kw):
		fuse.Fuse.__init__(self, *args, **kw)
		self.__io = io
		self.__cache = {}
		self.__buffer = {}

	def getattr(self, path):
		debug("getattr %s" % path)
		try:
			item = {}
			if self.__cache.get(path) and time.time()-3.0<self.__cache[path]['time']:
				item = self.__cache.get(path)
			elif path!='/':
				debug(' ', False)
				for a in self.readdir(re.sub('[^/]+$', '', path), 0):
					pass
				if self.__cache.get(path):
					item = self.__cache.get(path)

			st = GStorageStat()
			if path=='/' or item.get('model')=='dir':
				st.st_mode = stat.S_IFDIR | 0755
				st.st_nlink = 2
			elif item.get('model')=='file':
				st.st_mode = stat.S_IFREG | 0644
				st.st_nlink = 1
				st.st_size = item.get('size', 0)
			else:
				return -errno.ENOENT

			return st
		except Exception, e:
			debug(str(e))
			return -errno.ENOENT

	def readdir(self, path, offset):
		debug("readdir %s %s" % (path, offset))
		try:
			res = self.__io.request('ls', path).read().strip()
			list = ['.', '..']
			# parse
			#self.__cache = {}
			for line in res.split('\n'):
				item = line.split('\t')
				if len(item)==3:
					model = item[0].strip()
					name = item[1]
					size = int(item[2].strip())
					list.append(item[1])
					self.__cache[os.path.join(path, name)] = {
						'model' : model,
						'size' : size,
						'time' : time.time(),
					}
			# return
			for item in list:
				yield fuse.Direntry(item)
		except Exception, e:
			debug(str(e))

	def open(self, path, flags):
		debug("open %s %s" % (path, flags))

	def read(self, path, size, offset):
		debug("read %s %s %s" % (path, size, offset))
		try:
			return self.__io.request('cat', path).read()
		except Exception, e:
			return -errno.ENOENT

	def mknod(self, path, mode, dev):
		debug('mknod %s %s %s' % (path, oct(mode), dev))
		self.__cache[path] = {
			'model' : 'file',
			'time' : time.time(),
		}
		return 0

	def write(self, path, buf, offset):
		debug("write %s %s %s" % (path, len(buf), offset))
		try:
			self.__buffer[path] = self.__buffer.get(path, '') + buf
			return len(buf)
		except Exception, e:
			debug(str(e))
			return -errno.EIO

	def release(self, path, flags):
		debug('release %s %s' % (path, flags))
		try:
			buf = self.__buffer.pop(path, None)
			if buf is not None:
				res = self.__io.request('post', path, buf)
		except Exception, e:
			debug(str(e))
			return -errno.EIO
		return 0

	def truncate(self, path, size):
		debug('truncate %s %s' % (path, size))
		return 0

	def mkdir(self, path, mode):
		debug('mkdir %s %s' % (path, oct(mode)))
		try:
			self.__io.request('mkdir', path)
		except Exception, e:
			debug(str(e))
			return -errno.EIO
		return 0

	def rmdir(self, path):
		debug('rmdir %s' % path)
		try:
			self.__io.request('rm', path)
		except Exception, e:
			debug(str(e))
			return -errno.EIO
		return 0

	def unlink(self, path):
		debug('unlink %s' % path)
		try:
			self.__io.request('rm', path)
		except Exception, e:
			debug(str(e))
			return -errno.EIO
		return 0

	def rename(self, from_path, to_path):
		debug('rename %s %s' % (from_path, to_path))
		try:
			to_path = self.__io.basepath + to_path
			self.__io.request('mv', from_path, {'to':to_path})
		except Exception, e:
			debug(str(e))
			return -errno.EIO
		return 0

	# not implement

	def getdir(self, path):
		"""
		return: [[('file1', 0), ('file2', 0), ... ]]
		"""

		debug('*** getdir %s' % path)
		return -errno.ENOSYS

	def mythread(self):
		debug('*** mythread')
		return -errno.ENOSYS

	def chmod(self, path, mode):
		debug('*** chmod %s %s', path, oct(mode))
		return -errno.ENOSYS

	def chown(self, path, uid, gid):
		debug('*** chown %s %s %s' % (path, uid, gid))
		return -errno.ENOSYS

	def fsync(self, path, isFsyncFile):
		debug('*** fsync %s %s' % (path, isFsyncFile))
		return -errno.ENOSYS

	def link(self, targetPath, linkPath):
		debug('*** link %s %s' % (targetPath, linkPath))
		return -errno.ENOSYS

	def readlink(self, path):
		debug('*** readlink %s' % path)
		return -errno.ENOSYS

	def statfs(self):
		debug('*** statfs')
		return -errno.ENOSYS

	def symlink(self, targetPath, linkPath):
		debug('*** symlink %s %s' % (targetPath, linkPath))
		return -errno.ENOSYS

	def utime(self, path, times):
		debug('*** utime %s %s' % (path, times))
		return -errno.ENOSYS



""" Application start """

def main():
	try:
		io = GStorageIO_WSSE_a()
		#io = GStorageIO_WSSE_b()
		#io = GStorageIO_OAuth_HMAC_SHA1()
		#io = GStorageIO_OAuth_RSA_SHA1()
		io.ssl = False
		io.host = 'g-storage.appspot.com'
		io.basepath = '/storage'
		io.username = 'testuser'
		io.password = 'testpass'
		io.privatekey = None
		io.init()

		# check
		io.request('info', '/')

		# mount
		fs = GStorageFS(io)
		fs.parse(errex=1)
		fs.main()

	except Exception, e:
		print str(e)


if __name__=='__main__':
	main()

 

使い方
# python GStorageFS.py /mnt

 

参考URL

ここいらでfuseを一区切り - KoshigoeBLOG
http://blog.koshigoe.jp/archives/2007/04/fuse.html
 
とりあえずFUSE-pythonを使ってみる - 自称すーじー。
http://d.hatena.ne.jp/suu-g/20080604/1212557931
 
pythonfuse - lolloo-htnの日記
http://d.hatena.ne.jp/lolloo-htn/20091014/1255540299
 
fuse-python で遊んだ - zyxwvの日記
http://d.hatena.ne.jp/zyxwv/20090108/1231433753
 

PythonRSA-SHA1方式のOAuth認証をするクライアント側のライブラリ

easy_install oauthで入れられるライブラリはRSA-SHA1に対応してなかった。
とりあえずそれを継承してRSA-SHA1に対応させた。
使い方は、OAuthSignatureMethod_RSA_SHA1を継承して、fetch_private_certをオーバーライドするだけ。

import base64
from oauth.oauth import *
from tlslite.utils import keyfactory

class OAuthSignatureMethod_RSA_SHA1(OAuthSignatureMethod):
	def get_name(self):
		return 'RSA-SHA1'

	def fetch_private_cert(self, oauth_request):
		abstruct

	def build_signature_base_string(self, oauth_request, consumer, token):
		sig = (
			escape(oauth_request.get_normalized_http_method()),
			escape(oauth_request.get_normalized_http_url()),
			escape(oauth_request.get_normalized_parameters()),
		)
		raw = '&'.join(sig)
		return raw

	def build_signature(self, oauth_request, consumer, token):
		raw = self.build_signature_base_string(oauth_request, consumer, token)
		privatekey = keyfactory.parsePEMKey(self.fetch_private_cert(oauth_request), private=True)
		signature = privatekey.hashAndSign(raw)
		return base64.b64encode(signature)