PageRenderTime 46ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/utils/db.py

https://bitbucket.org/frosth/dotcloud
Python | 305 lines | 292 code | 11 blank | 2 comment | 12 complexity | a292fd8382520a9a28e18e29163c0e5e MD5 | raw file
Possible License(s): GPL-3.0
  1. import os
  2. import re
  3. import sys
  4. import couchdb
  5. import simplejson
  6. from os.path import dirname, basename
  7. from urlparse import urlsplit, urlunsplit
  8. from dotcloud.utils import shellCommand
  9. class ViewDict(dict):
  10. """ Results of a CouchDB view mapped to a dict """
  11. duplicates = property(lambda self: self._duplicates)
  12. def __init__(self, view):
  13. dict.__init__(self)
  14. self._duplicates = []
  15. for (key, value) in ((r.key, r.value) for r in view.rows):
  16. if key not in self:
  17. dict.__setitem__(self, key, value)
  18. else:
  19. self._duplicates.append((key, value))
  20. def __setitem__(self, key, value):
  21. raise AttributeError()
  22. def __delitem__(self, key):
  23. raise AttributeError()
  24. class TypedDocs(ViewDict):
  25. def __init__(self, typename, db):
  26. self._db = db
  27. self._typename = typename
  28. super(self.__class__, self).__init__(self._view())
  29. # FIXME: delete duplicates
  30. def sync(self, src, pretend=False):
  31. for (key, doc) in self.items():
  32. if key not in src:
  33. if not pretend:
  34. del self._db[doc["_id"]]
  35. for (key, src_doc) in src.items():
  36. if key in self:
  37. doc = self[key]
  38. doc.update(src_doc)
  39. if not pretend:
  40. self._db[doc["_id"]] = doc
  41. else:
  42. doc = dict(src_doc, type=self._typename)
  43. if not pretend:
  44. self._db.create(doc)
  45. def _view(self):
  46. return self._db.view(
  47. "types/%s" % self._typename,
  48. js_map="""if (doc.type == "%s") { emit (doc.name ? doc.name : doc._id, doc); }""" % self._typename
  49. )
  50. class DB(couchdb.Database):
  51. """ A customized CouchDB database """
  52. def __init__(self, url, debug=False):
  53. self._debug = debug
  54. (url_scheme, url_host, url_path, url_query, url_fragment) = urlsplit(url)
  55. server = couchdb.Server(urlunsplit((url_scheme, url_host, dirname(url_path), url_query, url_fragment)))
  56. name = basename(url_path)
  57. couchdb.Database.__init__(
  58. self,
  59. couchdb.client.uri(server.resource.uri, name),
  60. couchdb.client.validate_dbname(name),
  61. http=server.resource.http)
  62. def typed_docs(self, typename):
  63. return TypedDocs(typename, db=self)
  64. def view(self, name, js_map=None, js_reduce=None, *args, **kw):
  65. if js_map:
  66. self.log("UPDATING VIEW %s" % name)
  67. self.set_view(name, js_map=js_map, js_reduce=js_reduce)
  68. return couchdb.Database.view(self, name=name, group=True, *args, **kw)
  69. def log(self, msg, **args):
  70. if self._debug:
  71. sys.stderr.write("[%s] %s%s\n" % (self.resource.uri, msg, args and " "+args or ""))
  72. def __delitem__(self, key):
  73. self.log("DELETING %s" % (key))
  74. return couchdb.Database.__delitem__(self, key)
  75. def __setitem__(self, key, value):
  76. self.log("UPDATING %s" % (key))
  77. return couchdb.Database.__setitem__(self, key, value)
  78. def create(self, value):
  79. result = couchdb.Database.create(self, value)
  80. self.log("CREATED %s" % result)
  81. return result
  82. def get_view(self, path):
  83. (doc_name, view_name) = os.path.split(path)
  84. return self["_design/%s" % doc_name]["views"][view_name]
  85. def set_view(self, path, js_map, js_reduce=None):
  86. (doc_name, view_name) = os.path.split(path)
  87. try:
  88. doc = self["_design/%s" % doc_name]
  89. except:
  90. doc = {"language" : "javascript", "views" : {}}
  91. doc["views"][view_name] = {
  92. "map" : "function(doc) { %s }" % js_map,
  93. "reduce" : "function(key, values, rereduce) { %s }" % js_reduce if js_reduce else None
  94. }
  95. self["_design/%s" % doc_name] = doc
  96. self.log("Uploaded view %s" % path)
  97. return path
  98. def delete_view(self, path):
  99. (doc_name, view_name) = os.path.split(path)
  100. full_doc_name = "_design/%s" % doc_name
  101. if full_doc_name:
  102. del self[full_doc_name]
  103. self.log("Deleted view %s" % path)
  104. class DotCloudDB(DB):
  105. """ Code for the dotCloud runtime mapped to a CouchDB database """
  106. # DOWNLOAD
  107. ###########
  108. def get_config(self):
  109. self.typed_docs("config").sync({"config" : {"type" : "config", "name" : "config", "content" : Config()}})
  110. return ViewDict(self.view("dotcloud/config"))
  111. config = property(get_config)
  112. def get_instances(self):
  113. self.typed_docs("ovz_container").sync(OVZContainers())
  114. return ViewDict(self.view("dotcloud/instances"))
  115. instances = property(get_instances)
  116. def get_volumes(self):
  117. self.typed_docs("volume").sync(Volumes(self.config["path"]))
  118. return ViewDict(self.view("dotcloud/volumes"))
  119. volumes = property(get_volumes)
  120. def view(self, name, *args, **kw):
  121. if name in self.views:
  122. kw["js_map"] = self.views[name]["map"]
  123. kw["js_reduce"] = self.views[name].get("reduce")
  124. return super(self.__class__, self).view(name, *args, **kw)
  125. views = {
  126. "dotcloud/volumes" : {
  127. "map":
  128. """
  129. if (doc.type == "volume") { emit (doc.name, doc); }
  130. """
  131. },
  132. "dotcloud/instances" : {
  133. "map":
  134. """
  135. if (doc.type == "ovz_container") {
  136. emit (doc.name, doc);
  137. }
  138. else if (doc.type == "instance") {
  139. emit (doc.name, doc);
  140. }
  141. """,
  142. "reduce":
  143. """
  144. var result = {"managed": false};
  145. for (var idx in values) {
  146. var object = values[idx];
  147. if (object["type"] == "instance") { result["managed"] = true; }
  148. for (var key in object) {
  149. if (["_id", "_rev", "type"].indexOf(key) != -1) continue;
  150. result[key] = object[key];
  151. }
  152. }
  153. return result;
  154. """
  155. },
  156. "dotcloud/config" : {
  157. "map":
  158. """
  159. if (doc.type == "config" && doc.content) {
  160. for (var key in doc.content) {
  161. emit (key, doc.content[key]);
  162. }
  163. }
  164. """,
  165. "reduce":
  166. """
  167. return values[0];
  168. """
  169. }
  170. }
  171. # System classes
  172. ################
  173. class Config(dict):
  174. def __init__(self, **override):
  175. super(self.__class__, self).__init__()
  176. config_search = os.environ.get("DOTCLOUD_CONFIG_PATH", "/etc/dotcloud").split(":")
  177. for (path, content) in self._load_files(*config_search):
  178. for (key, value) in content.items():
  179. self[key] = value
  180. for (key, value) in override.items():
  181. self[key] = value
  182. def _load_files(self, *search):
  183. def config_paths():
  184. for config_path in map(os.path.abspath, search):
  185. if os.path.isdir(config_path):
  186. for (path, dirnames, filenames) in os.walk(config_path):
  187. for filename in filenames:
  188. yield os.path.normpath(os.path.join(path, filename))
  189. else:
  190. yield os.path.normpath(config_path)
  191. for path in config_paths():
  192. try:
  193. yield (path, simplejson.loads(file(path).read()))
  194. except Exception, e:
  195. sys.stderr.write("Skipping %s: %s\n" % (path, e))
  196. continue
  197. class IPAddresses(dict):
  198. _re = re.compile("^[^:]*: ([^ :]+).*inet ([^ ]*) .*$")
  199. def __init__(self):
  200. dict.__init__(self)
  201. lines = shellCommand("ip -o addr").strip().split("\n")
  202. for line_match in filter(None, map(self._re.match, lines)):
  203. doc = {
  204. "iface" : line_match.groups()[0],
  205. "address" : line_match.groups()[1]
  206. }
  207. self[doc["address"]] = doc
  208. class Volumes(dict):
  209. def __init__(self, dotcloud_path):
  210. dict.__init__(self)
  211. volumes_path = os.path.join(dotcloud_path, "volumes")
  212. for dirname in os.listdir(volumes_path):
  213. path = os.path.normpath(os.path.join(volumes_path, dirname))
  214. self[dirname] = {"name": dirname, "path":path}
  215. class AUFSMounts(dict):
  216. re_mount = re.compile("^aufs on ([^ ]*) type aufs \((.*)\)$")
  217. def __init__(self):
  218. lines = shellCommand("mount").strip().split("\n")
  219. for line_match in filter(None, map(cls.re_mount.match, lines)):
  220. path = os.path.normpath(line_match.groups()[0])
  221. self[path] = {"name" : path, "options" : line_match.groups()[1].split(",")}
  222. class BindFSMounts(dict):
  223. re_mount = re.compile("^bindfs on ([^ ]*) type fuse\.bindfs \((.*)\)$")
  224. def __init__(self):
  225. lines = shellCommand("mount").strip().split("\n")
  226. for line_match in filter(None, map(cls.re_mount.match, lines)):
  227. path = os.path.normpath(line_match.groups()[0])
  228. self[path] = {"name" : path, "option" : line_match.groups()[1].split(",")}
  229. class OVZContainers(dict):
  230. def __init__(self):
  231. dict.__init__(self)
  232. for line in shellCommand("vzlist -a -o name,veid,ip,status -H").strip().split("\n"):
  233. if not line:
  234. continue
  235. container = dict(zip(
  236. ("name", "veid", "ip", "running"),
  237. line.split()
  238. ))
  239. container["running"] = (True if container["running"] == "running" else False)
  240. if (container["ip"] == "-"):
  241. container["ip"] = None
  242. if (container["name"] == "-"):
  243. container["name"] = container["veid"]
  244. else:
  245. container["name"] = cls.get_long_name(container["name"])
  246. self[container["name"]] = container
  247. re_short_name = re.compile("^(........)(....)(....)(....)(............)$")
  248. @classmethod
  249. def get_long_name(cls, short_name):
  250. return "-".join(cls.re_short_name.match(short_name).groups())
  251. @classmethod
  252. def get_short_name(cls, long_name):
  253. return re.sub("-", "", long_name)