之前想写nova的policy的实现, 但是发现网上,有人写的很不错了。
但是个人认为存在一些问题。 ref: http://www.cnblogs.com/shaohef/p/4527436.html
希望 microversion 还没有人写。
microversion实现
microversion实现的机制,就是在http的头部增加一个请求的小版本, nova的serve大家 搜索 new, type, metaclass, 都会介绍。r 根据这个小版本号,做相应的action。
实在是没有什么好介绍了。
这个我想像的microversion有些gap。 我开始以为是在url中指定版本号,而不是在head中。
microversion的值得研究的是如下这段代码。
1 class subContorller(wsgi.Controller) 2 @wsgi.Controller.api_version("2.1", "2.3") 3 def my_api_method(self, req, id): 4 .... method_1 ... 5 6 @wsgi.Controller.api_version("2.4") #noqa 7 def my_api_method(self, req, id): 8 .... method_2 ...
来自: http://docs.openstack.org/developer/nova/devref/api_microversions.html#changing-a-method-s-behaviour
这段代码,有点诡异,在一个类中实现了两个同名的属性,后面的那个将覆盖前面的那个。
如果是熟悉python的类和实例创建过程的,肯定认为很easy。
瞬间就能想到实现原理, 比如说 不正直的绅士, 他直接做过类似的代码。
其实在IBM的kvm的team,做发行版本的同事,也能想到怎么实现的。
因为我之前给他们介绍个python的类和实例创建过程,其实google/so 一大堆。
我的介绍肯定不如他们自己学习效果好,我纯粹就是显摆而已。
我们要在magnum上,实现microversion,所以我我按照自己的思路尝试自己实现一个。
首先,在同一个类中,定义多个同名函数,最后一个函数, 会覆盖其他的。
怎么办呢?
跟大家一样,第一想法是重载 __getattribute__ 的类。
做法是重名的函数,想办法重新命名。 然后在__getattribute__中,知道期望的函数。
发现,没有找到一个合适的位置,来hack重名的函数。
下面代码中的第5行,是我找到的唯一可能问位置,但是,这个代码只有类的实例才会调用。 ~~~~
1 def operater(min, max): 2 def operate(fn): 3 def wraper(self, *args, **kv): 4 if fn.func_name not in self.funs: 5 self.funs[fn.func_name] = [(fn.func_name+"_"+min+"_"+max, wraper)] 6 else: 7 self.funs[fn.func_name].append((fn.func_name+"_"+min+"_"+max, wraper)) 8 print getattr(self, fn.func_name) 9 print "begin decorate" 10 return fn(self, *args, **kv) 11 print "end decorate" 12 type(self) 13 return wraper 14 return operate 15 16 17 class Controller(object): 18 funs = {} 19 def __init__(self): 20 print self 21 22 def __getattribute__(self, name): 23 if name in self.funs: 24 all_funs = self.funs[name] 25 fun = self.funs[0][1] 26 return fun 27 return object.__getattribute__(self, name) 28 29 @operater("1.0", "1.5") # noqa 30 def fun1(self): 31 print self 32 print "inline fun1" 33 34 @operater("1.0", "1.6") # noqa 35 def fun1(self): 36 print self 37 print "inline fun2" 38 39 if __name__ == '__main__': 40 print "start main" 41 import pdb 42 pdb.set_trace() 43 i = Controller() 44 print dir(i) 45 i.fun1()
大家可以尝试写一下, 不知道有没有实现的可能, 过程中可能会有不少坑。
实在是给大家做过太多的培训了,类合实例的创建过程,还是比较清楚的。
休息了一下,立马清醒。 发现自己比较傻逼,这个位置是创建类的时候。
大家 搜索 new, type, metaclass, 都会介绍。
查看nova的代码,果真如此。
nova采用了six.add_metaclass 来构造类。把nova的相关代码摘取如下, 很简单,都不需要解释。
1 import six 2 3 class VersionedMethod(object): 4 5 def __init__(self, name, start_version, end_version, func): 6 self.name = name 7 self.start_version = start_version 8 self.end_version = end_version 9 self.func = func 10 11 def __str__(self): 12 return ("Version Method %s: min: %s, max: %s" 13 % (self.name, self.start_version, self.end_version)) 14 15 16 VER_METHOD_ATTR = 'versioned_methods' 17 obj_min_ver = "2.0" 18 obj_max_ver = "2.3" 19 20 21 class ControllerMetaclass(type): 22 def __new__(mcs, name, bases, cls_dict): 23 versioned_methods = None 24 # start with wsgi actions from base classes 25 for base in bases: 26 # actions.update(getattr(base, 'wsgi_actions', {})) 27 28 if base.__name__ == "Controller": 29 # NOTE(cyeoh): This resets the VER_METHOD_ATTR attribute 30 # between API controller class creations. This allows us 31 # to use a class decorator on the API methods that doesn't 32 # require naming explicitly what method is being versioned as 33 # it can be implicit based on the method decorated. It is a bit 34 # ugly. 35 print "+" * 80 36 print base.__dict__ 37 if VER_METHOD_ATTR in base.__dict__: 38 versioned_methods = getattr(base, VER_METHOD_ATTR) 39 delattr(base, VER_METHOD_ATTR) 40 41 for key, value in cls_dict.items(): 42 if not callable(value): 43 continue 44 if versioned_methods: 45 cls_dict[VER_METHOD_ATTR] = versioned_methods 46 47 return super(ControllerMetaclass, mcs).__new__(mcs, name, bases, 48 cls_dict) 49 50 51 class abc(object): 52 pass 53 54 @six.add_metaclass(ControllerMetaclass) 55 class Controller(abc): 56 57 58 def __getattribute__(self, key): 59 def version_select(*args, **kwargs): 60 # The first arg to all versioned methods is always the request 61 # object. The version for the request is attached to the 62 # request object 63 func_list = self.versioned_methods[key] 64 print func_list 65 for func in func_list: 66 print "^" * 80 67 return func.func(self, *args, **kwargs) 68 # print func.func_name, func.obj_min_ver, func.obj_max_ver 69 return func.func(self, *args, **kwargs) 70 # No version match 71 raise exception.VersionNotFoundForAPIMethod(version=ver) 72 73 try: 74 # super(LockerDecorator, self).__getattribute__(self, name) 75 version_meth_dict = abc.__getattribute__(self, VER_METHOD_ATTR) 76 except AttributeError: 77 # No versioning on this class 78 return abc.__getattribute__(self, key) 79 80 if version_meth_dict and 81 key in abc.__getattribute__(self, VER_METHOD_ATTR): 82 return version_select 83 84 return abc.__getattribute__(self, key) 85 86 87 # NOTE(cyeoh): This decorator MUST appear first (the outermost 88 # decorator) on an API method for it to work correctly 89 @classmethod 90 def api_version(cls, min_ver, max_ver=None): 91 def decorator(f): 92 93 # Add to list of versioned methods registered 94 func_name = f.__name__ 95 new_func = VersionedMethod(func_name, obj_min_ver, obj_max_ver, f) 96 97 func_dict = getattr(cls, VER_METHOD_ATTR, {}) 98 if not func_dict: 99 setattr(cls, VER_METHOD_ATTR, func_dict) 100 101 # global func_list 102 func_list = func_dict.get(func_name, []) 103 if not func_list: 104 func_dict[func_name] = func_list 105 func_list.append(new_func) 106 func_list.sort(key=lambda f: f.start_version, reverse=True) 107 108 return f 109 110 return decorator 111 112 113 class MyController(Controller): 114 @Controller.api_version("2.2") 115 def create(self, req, body): 116 print "create v2.2" 117 118 @Controller.api_version("2.1", "2.1") # noqa 119 def create(self, req, body): 120 print "create v2.1" 121 122 def delete(self, req, body): 123 print "delete no version" 124 125 if __name__ == "__main__": 126 i = MyController() 127 i.create("1", "2") 128 i.delete("1", "2") 129 print "exit"