PageRenderTime 51ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/boto-2.5.2/boto/mashups/server.py

#
Python | 395 lines | 359 code | 9 blank | 27 comment | 13 complexity | 939e15bc9a119331b3c1afa4c41558de MD5 | raw file
  1. # Copyright (c) 2006,2007 Mitch Garnaat http://garnaat.org/
  2. #
  3. # Permission is hereby granted, free of charge, to any person obtaining a
  4. # copy of this software and associated documentation files (the
  5. # "Software"), to deal in the Software without restriction, including
  6. # without limitation the rights to use, copy, modify, merge, publish, dis-
  7. # tribute, sublicense, and/or sell copies of the Software, and to permit
  8. # persons to whom the Software is furnished to do so, subject to the fol-
  9. # lowing conditions:
  10. #
  11. # The above copyright notice and this permission notice shall be included
  12. # in all copies or substantial portions of the Software.
  13. #
  14. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  15. # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
  16. # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
  17. # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  18. # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  20. # IN THE SOFTWARE.
  21. """
  22. High-level abstraction of an EC2 server
  23. """
  24. import boto
  25. import boto.utils
  26. from boto.mashups.iobject import IObject
  27. from boto.pyami.config import Config, BotoConfigPath
  28. from boto.mashups.interactive import interactive_shell
  29. from boto.sdb.db.model import Model
  30. from boto.sdb.db.property import StringProperty
  31. import os
  32. import StringIO
  33. class ServerSet(list):
  34. def __getattr__(self, name):
  35. results = []
  36. is_callable = False
  37. for server in self:
  38. try:
  39. val = getattr(server, name)
  40. if callable(val):
  41. is_callable = True
  42. results.append(val)
  43. except:
  44. results.append(None)
  45. if is_callable:
  46. self.map_list = results
  47. return self.map
  48. return results
  49. def map(self, *args):
  50. results = []
  51. for fn in self.map_list:
  52. results.append(fn(*args))
  53. return results
  54. class Server(Model):
  55. @property
  56. def ec2(self):
  57. if self._ec2 is None:
  58. self._ec2 = boto.connect_ec2()
  59. return self._ec2
  60. @classmethod
  61. def Inventory(cls):
  62. """
  63. Returns a list of Server instances, one for each Server object
  64. persisted in the db
  65. """
  66. l = ServerSet()
  67. rs = cls.find()
  68. for server in rs:
  69. l.append(server)
  70. return l
  71. @classmethod
  72. def Register(cls, name, instance_id, description=''):
  73. s = cls()
  74. s.name = name
  75. s.instance_id = instance_id
  76. s.description = description
  77. s.save()
  78. return s
  79. def __init__(self, id=None, **kw):
  80. Model.__init__(self, id, **kw)
  81. self._reservation = None
  82. self._instance = None
  83. self._ssh_client = None
  84. self._pkey = None
  85. self._config = None
  86. self._ec2 = None
  87. name = StringProperty(unique=True, verbose_name="Name")
  88. instance_id = StringProperty(verbose_name="Instance ID")
  89. config_uri = StringProperty()
  90. ami_id = StringProperty(verbose_name="AMI ID")
  91. zone = StringProperty(verbose_name="Availability Zone")
  92. security_group = StringProperty(verbose_name="Security Group", default="default")
  93. key_name = StringProperty(verbose_name="Key Name")
  94. elastic_ip = StringProperty(verbose_name="Elastic IP")
  95. instance_type = StringProperty(verbose_name="Instance Type")
  96. description = StringProperty(verbose_name="Description")
  97. log = StringProperty()
  98. def setReadOnly(self, value):
  99. raise AttributeError
  100. def getInstance(self):
  101. if not self._instance:
  102. if self.instance_id:
  103. try:
  104. rs = self.ec2.get_all_instances([self.instance_id])
  105. except:
  106. return None
  107. if len(rs) > 0:
  108. self._reservation = rs[0]
  109. self._instance = self._reservation.instances[0]
  110. return self._instance
  111. instance = property(getInstance, setReadOnly, None, 'The Instance for the server')
  112. def getAMI(self):
  113. if self.instance:
  114. return self.instance.image_id
  115. ami = property(getAMI, setReadOnly, None, 'The AMI for the server')
  116. def getStatus(self):
  117. if self.instance:
  118. self.instance.update()
  119. return self.instance.state
  120. status = property(getStatus, setReadOnly, None,
  121. 'The status of the server')
  122. def getHostname(self):
  123. if self.instance:
  124. return self.instance.public_dns_name
  125. hostname = property(getHostname, setReadOnly, None,
  126. 'The public DNS name of the server')
  127. def getPrivateHostname(self):
  128. if self.instance:
  129. return self.instance.private_dns_name
  130. private_hostname = property(getPrivateHostname, setReadOnly, None,
  131. 'The private DNS name of the server')
  132. def getLaunchTime(self):
  133. if self.instance:
  134. return self.instance.launch_time
  135. launch_time = property(getLaunchTime, setReadOnly, None,
  136. 'The time the Server was started')
  137. def getConsoleOutput(self):
  138. if self.instance:
  139. return self.instance.get_console_output()
  140. console_output = property(getConsoleOutput, setReadOnly, None,
  141. 'Retrieve the console output for server')
  142. def getGroups(self):
  143. if self._reservation:
  144. return self._reservation.groups
  145. else:
  146. return None
  147. groups = property(getGroups, setReadOnly, None,
  148. 'The Security Groups controlling access to this server')
  149. def getConfig(self):
  150. if not self._config:
  151. remote_file = BotoConfigPath
  152. local_file = '%s.ini' % self.instance.id
  153. self.get_file(remote_file, local_file)
  154. self._config = Config(local_file)
  155. return self._config
  156. def setConfig(self, config):
  157. local_file = '%s.ini' % self.instance.id
  158. fp = open(local_file)
  159. config.write(fp)
  160. fp.close()
  161. self.put_file(local_file, BotoConfigPath)
  162. self._config = config
  163. config = property(getConfig, setConfig, None,
  164. 'The instance data for this server')
  165. def set_config(self, config):
  166. """
  167. Set SDB based config
  168. """
  169. self._config = config
  170. self._config.dump_to_sdb("botoConfigs", self.id)
  171. def load_config(self):
  172. self._config = Config(do_load=False)
  173. self._config.load_from_sdb("botoConfigs", self.id)
  174. def stop(self):
  175. if self.instance:
  176. self.instance.stop()
  177. def start(self):
  178. self.stop()
  179. ec2 = boto.connect_ec2()
  180. ami = ec2.get_all_images(image_ids = [str(self.ami_id)])[0]
  181. groups = ec2.get_all_security_groups(groupnames=[str(self.security_group)])
  182. if not self._config:
  183. self.load_config()
  184. if not self._config.has_section("Credentials"):
  185. self._config.add_section("Credentials")
  186. self._config.set("Credentials", "aws_access_key_id", ec2.aws_access_key_id)
  187. self._config.set("Credentials", "aws_secret_access_key", ec2.aws_secret_access_key)
  188. if not self._config.has_section("Pyami"):
  189. self._config.add_section("Pyami")
  190. if self._manager.domain:
  191. self._config.set('Pyami', 'server_sdb_domain', self._manager.domain.name)
  192. self._config.set("Pyami", 'server_sdb_name', self.name)
  193. cfg = StringIO.StringIO()
  194. self._config.write(cfg)
  195. cfg = cfg.getvalue()
  196. r = ami.run(min_count=1,
  197. max_count=1,
  198. key_name=self.key_name,
  199. security_groups = groups,
  200. instance_type = self.instance_type,
  201. placement = self.zone,
  202. user_data = cfg)
  203. i = r.instances[0]
  204. self.instance_id = i.id
  205. self.put()
  206. if self.elastic_ip:
  207. ec2.associate_address(self.instance_id, self.elastic_ip)
  208. def reboot(self):
  209. if self.instance:
  210. self.instance.reboot()
  211. def get_ssh_client(self, key_file=None, host_key_file='~/.ssh/known_hosts',
  212. uname='root'):
  213. import paramiko
  214. if not self.instance:
  215. print 'No instance yet!'
  216. return
  217. if not self._ssh_client:
  218. if not key_file:
  219. iobject = IObject()
  220. key_file = iobject.get_filename('Path to OpenSSH Key file')
  221. self._pkey = paramiko.RSAKey.from_private_key_file(key_file)
  222. self._ssh_client = paramiko.SSHClient()
  223. self._ssh_client.load_system_host_keys()
  224. self._ssh_client.load_host_keys(os.path.expanduser(host_key_file))
  225. self._ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
  226. self._ssh_client.connect(self.instance.public_dns_name,
  227. username=uname, pkey=self._pkey)
  228. return self._ssh_client
  229. def get_file(self, remotepath, localpath):
  230. ssh_client = self.get_ssh_client()
  231. sftp_client = ssh_client.open_sftp()
  232. sftp_client.get(remotepath, localpath)
  233. def put_file(self, localpath, remotepath):
  234. ssh_client = self.get_ssh_client()
  235. sftp_client = ssh_client.open_sftp()
  236. sftp_client.put(localpath, remotepath)
  237. def listdir(self, remotepath):
  238. ssh_client = self.get_ssh_client()
  239. sftp_client = ssh_client.open_sftp()
  240. return sftp_client.listdir(remotepath)
  241. def shell(self, key_file=None):
  242. ssh_client = self.get_ssh_client(key_file)
  243. channel = ssh_client.invoke_shell()
  244. interactive_shell(channel)
  245. def bundle_image(self, prefix, key_file, cert_file, size):
  246. print 'bundling image...'
  247. print '\tcopying cert and pk over to /mnt directory on server'
  248. ssh_client = self.get_ssh_client()
  249. sftp_client = ssh_client.open_sftp()
  250. path, name = os.path.split(key_file)
  251. remote_key_file = '/mnt/%s' % name
  252. self.put_file(key_file, remote_key_file)
  253. path, name = os.path.split(cert_file)
  254. remote_cert_file = '/mnt/%s' % name
  255. self.put_file(cert_file, remote_cert_file)
  256. print '\tdeleting %s' % BotoConfigPath
  257. # delete the metadata.ini file if it exists
  258. try:
  259. sftp_client.remove(BotoConfigPath)
  260. except:
  261. pass
  262. command = 'sudo ec2-bundle-vol '
  263. command += '-c %s -k %s ' % (remote_cert_file, remote_key_file)
  264. command += '-u %s ' % self._reservation.owner_id
  265. command += '-p %s ' % prefix
  266. command += '-s %d ' % size
  267. command += '-d /mnt '
  268. if self.instance.instance_type == 'm1.small' or self.instance_type == 'c1.medium':
  269. command += '-r i386'
  270. else:
  271. command += '-r x86_64'
  272. print '\t%s' % command
  273. t = ssh_client.exec_command(command)
  274. response = t[1].read()
  275. print '\t%s' % response
  276. print '\t%s' % t[2].read()
  277. print '...complete!'
  278. def upload_bundle(self, bucket, prefix):
  279. print 'uploading bundle...'
  280. command = 'ec2-upload-bundle '
  281. command += '-m /mnt/%s.manifest.xml ' % prefix
  282. command += '-b %s ' % bucket
  283. command += '-a %s ' % self.ec2.aws_access_key_id
  284. command += '-s %s ' % self.ec2.aws_secret_access_key
  285. print '\t%s' % command
  286. ssh_client = self.get_ssh_client()
  287. t = ssh_client.exec_command(command)
  288. response = t[1].read()
  289. print '\t%s' % response
  290. print '\t%s' % t[2].read()
  291. print '...complete!'
  292. def create_image(self, bucket=None, prefix=None, key_file=None, cert_file=None, size=None):
  293. iobject = IObject()
  294. if not bucket:
  295. bucket = iobject.get_string('Name of S3 bucket')
  296. if not prefix:
  297. prefix = iobject.get_string('Prefix for AMI file')
  298. if not key_file:
  299. key_file = iobject.get_filename('Path to RSA private key file')
  300. if not cert_file:
  301. cert_file = iobject.get_filename('Path to RSA public cert file')
  302. if not size:
  303. size = iobject.get_int('Size (in MB) of bundled image')
  304. self.bundle_image(prefix, key_file, cert_file, size)
  305. self.upload_bundle(bucket, prefix)
  306. print 'registering image...'
  307. self.image_id = self.ec2.register_image('%s/%s.manifest.xml' % (bucket, prefix))
  308. return self.image_id
  309. def attach_volume(self, volume, device="/dev/sdp"):
  310. """
  311. Attach an EBS volume to this server
  312. :param volume: EBS Volume to attach
  313. :type volume: boto.ec2.volume.Volume
  314. :param device: Device to attach to (default to /dev/sdp)
  315. :type device: string
  316. """
  317. if hasattr(volume, "id"):
  318. volume_id = volume.id
  319. else:
  320. volume_id = volume
  321. return self.ec2.attach_volume(volume_id=volume_id, instance_id=self.instance_id, device=device)
  322. def detach_volume(self, volume):
  323. """
  324. Detach an EBS volume from this server
  325. :param volume: EBS Volume to detach
  326. :type volume: boto.ec2.volume.Volume
  327. """
  328. if hasattr(volume, "id"):
  329. volume_id = volume.id
  330. else:
  331. volume_id = volume
  332. return self.ec2.detach_volume(volume_id=volume_id, instance_id=self.instance_id)
  333. def install_package(self, package_name):
  334. print 'installing %s...' % package_name
  335. command = 'yum -y install %s' % package_name
  336. print '\t%s' % command
  337. ssh_client = self.get_ssh_client()
  338. t = ssh_client.exec_command(command)
  339. response = t[1].read()
  340. print '\t%s' % response
  341. print '\t%s' % t[2].read()
  342. print '...complete!'