~cbehrens/nova/lp844160-build-works-with-zones

« back to all changes in this revision

Viewing changes to vendor/boto/boto/mashups/server.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
"""
 
23
High-level abstraction of an EC2 server
 
24
"""
 
25
import boto
 
26
import boto.utils
 
27
from boto.mashups.iobject import IObject
 
28
from boto.pyami.config import Config, BotoConfigPath
 
29
from boto.mashups.interactive import interactive_shell
 
30
from boto.sdb.db.model import Model
 
31
from boto.sdb.db.property import StringProperty
 
32
import os
 
33
import StringIO
 
34
 
 
35
 
 
36
class ServerSet(list):
 
37
 
 
38
    def __getattr__(self, name):
 
39
        results = []
 
40
        is_callable = False
 
41
        for server in self:
 
42
            try:
 
43
                val = getattr(server, name)
 
44
                if callable(val):
 
45
                    is_callable = True
 
46
                results.append(val)
 
47
            except:
 
48
                results.append(None)
 
49
        if is_callable:
 
50
            self.map_list = results
 
51
            return self.map
 
52
        return results
 
53
 
 
54
    def map(self, *args):
 
55
        results = []
 
56
        for fn in self.map_list:
 
57
            results.append(fn(*args))
 
58
        return results
 
59
 
 
60
class Server(Model):
 
61
 
 
62
    @property
 
63
    def ec2(self):
 
64
        if self._ec2 is None:
 
65
            self._ec2 = boto.connect_ec2()
 
66
        return self._ec2
 
67
 
 
68
    @classmethod
 
69
    def Inventory(cls):
 
70
        """
 
71
        Returns a list of Server instances, one for each Server object
 
72
        persisted in the db
 
73
        """
 
74
        l = ServerSet()
 
75
        rs = cls.find()
 
76
        for server in rs:
 
77
            l.append(server)
 
78
        return l
 
79
 
 
80
    @classmethod
 
81
    def Register(cls, name, instance_id, description=''):
 
82
        s = cls()
 
83
        s.name = name
 
84
        s.instance_id = instance_id
 
85
        s.description = description
 
86
        s.save()
 
87
        return s
 
88
 
 
89
    def __init__(self, id=None, **kw):
 
90
        Model.__init__(self, id, **kw)
 
91
        self._reservation = None
 
92
        self._instance = None
 
93
        self._ssh_client = None
 
94
        self._pkey = None
 
95
        self._config = None
 
96
        self._ec2 = None
 
97
 
 
98
    name = StringProperty(unique=True, verbose_name="Name")
 
99
    instance_id = StringProperty(verbose_name="Instance ID")
 
100
    config_uri = StringProperty()
 
101
    ami_id = StringProperty(verbose_name="AMI ID")
 
102
    zone = StringProperty(verbose_name="Availability Zone")
 
103
    security_group = StringProperty(verbose_name="Security Group", default="default")
 
104
    key_name = StringProperty(verbose_name="Key Name")
 
105
    elastic_ip = StringProperty(verbose_name="Elastic IP")
 
106
    instance_type = StringProperty(verbose_name="Instance Type")
 
107
    description = StringProperty(verbose_name="Description")
 
108
    log = StringProperty()
 
109
 
 
110
    def setReadOnly(self, value):
 
111
        raise AttributeError
 
112
 
 
113
    def getInstance(self):
 
114
        if not self._instance:
 
115
            if self.instance_id:
 
116
                try:
 
117
                    rs = self.ec2.get_all_instances([self.instance_id])
 
118
                except:
 
119
                    return None
 
120
                if len(rs) > 0:
 
121
                    self._reservation = rs[0]
 
122
                    self._instance = self._reservation.instances[0]
 
123
        return self._instance
 
124
 
 
125
    instance = property(getInstance, setReadOnly, None, 'The Instance for the server')
 
126
    
 
127
    def getAMI(self):
 
128
        if self.instance:
 
129
            return self.instance.image_id
 
130
 
 
131
    ami = property(getAMI, setReadOnly, None, 'The AMI for the server')
 
132
    
 
133
    def getStatus(self):
 
134
        if self.instance:
 
135
            self.instance.update()
 
136
            return self.instance.state
 
137
 
 
138
    status = property(getStatus, setReadOnly, None,
 
139
                      'The status of the server')
 
140
    
 
141
    def getHostname(self):
 
142
        if self.instance:
 
143
            return self.instance.public_dns_name
 
144
 
 
145
    hostname = property(getHostname, setReadOnly, None,
 
146
                        'The public DNS name of the server')
 
147
 
 
148
    def getPrivateHostname(self):
 
149
        if self.instance:
 
150
            return self.instance.private_dns_name
 
151
 
 
152
    private_hostname = property(getPrivateHostname, setReadOnly, None,
 
153
                                'The private DNS name of the server')
 
154
 
 
155
    def getLaunchTime(self):
 
156
        if self.instance:
 
157
            return self.instance.launch_time
 
158
 
 
159
    launch_time = property(getLaunchTime, setReadOnly, None,
 
160
                           'The time the Server was started')
 
161
 
 
162
    def getConsoleOutput(self):
 
163
        if self.instance:
 
164
            return self.instance.get_console_output()
 
165
 
 
166
    console_output = property(getConsoleOutput, setReadOnly, None,
 
167
                              'Retrieve the console output for server')
 
168
 
 
169
    def getGroups(self):
 
170
        if self._reservation:
 
171
            return self._reservation.groups
 
172
        else:
 
173
            return None
 
174
 
 
175
    groups = property(getGroups, setReadOnly, None,
 
176
                      'The Security Groups controlling access to this server')
 
177
 
 
178
    def getConfig(self):
 
179
        if not self._config:
 
180
            remote_file = BotoConfigPath
 
181
            local_file = '%s.ini' % self.instance.id
 
182
            self.get_file(remote_file, local_file)
 
183
            self._config = Config(local_file)
 
184
        return self._config
 
185
 
 
186
    def setConfig(self, config):
 
187
        local_file = '%s.ini' % self.instance.id
 
188
        fp = open(local_file)
 
189
        config.write(fp)
 
190
        fp.close()
 
191
        self.put_file(local_file, BotoConfigPath)
 
192
        self._config = config
 
193
 
 
194
    config = property(getConfig, setConfig, None,
 
195
                      'The instance data for this server')
 
196
 
 
197
    def set_config(self, config):
 
198
        """
 
199
        Set SDB based config
 
200
        """
 
201
        self._config = config
 
202
        self._config.dump_to_sdb("botoConfigs", self.id)
 
203
 
 
204
    def load_config(self):
 
205
        self._config = Config(do_load=False)
 
206
        self._config.load_from_sdb("botoConfigs", self.id)
 
207
 
 
208
    def stop(self):
 
209
        if self.instance:
 
210
            self.instance.stop()
 
211
 
 
212
    def start(self):
 
213
        self.stop()
 
214
        ec2 = boto.connect_ec2()
 
215
        ami = ec2.get_all_images(image_ids = [str(self.ami_id)])[0]
 
216
        groups = ec2.get_all_security_groups(groupnames=[str(self.security_group)])
 
217
        if not self._config:
 
218
            self.load_config()
 
219
        if not self._config.has_section("Credentials"):
 
220
            self._config.add_section("Credentials")
 
221
            self._config.set("Credentials", "aws_access_key_id", ec2.aws_access_key_id)
 
222
            self._config.set("Credentials", "aws_secret_access_key", ec2.aws_secret_access_key)
 
223
 
 
224
        if not self._config.has_section("Pyami"):
 
225
            self._config.add_section("Pyami")
 
226
 
 
227
        if self._manager.domain:
 
228
            self._config.set('Pyami', 'server_sdb_domain', self._manager.domain.name)
 
229
            self._config.set("Pyami", 'server_sdb_name', self.name)
 
230
 
 
231
        cfg = StringIO.StringIO()
 
232
        self._config.write(cfg)
 
233
        cfg = cfg.getvalue()
 
234
        r = ami.run(min_count=1,
 
235
                    max_count=1,
 
236
                    key_name=self.key_name,
 
237
                    security_groups = groups,
 
238
                    instance_type = self.instance_type,
 
239
                    placement = self.zone,
 
240
                    user_data = cfg)
 
241
        i = r.instances[0]
 
242
        self.instance_id = i.id
 
243
        self.put()
 
244
        if self.elastic_ip:
 
245
            ec2.associate_address(self.instance_id, self.elastic_ip)
 
246
 
 
247
    def reboot(self):
 
248
        if self.instance:
 
249
            self.instance.reboot()
 
250
 
 
251
    def get_ssh_client(self, key_file=None, host_key_file='~/.ssh/known_hosts',
 
252
                       uname='root'):
 
253
        import paramiko
 
254
        if not self.instance:
 
255
            print 'No instance yet!'
 
256
            return
 
257
        if not self._ssh_client:
 
258
            if not key_file:
 
259
                iobject = IObject()
 
260
                key_file = iobject.get_filename('Path to OpenSSH Key file')
 
261
            self._pkey = paramiko.RSAKey.from_private_key_file(key_file)
 
262
            self._ssh_client = paramiko.SSHClient()
 
263
            self._ssh_client.load_system_host_keys()
 
264
            self._ssh_client.load_host_keys(os.path.expanduser(host_key_file))
 
265
            self._ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
 
266
            self._ssh_client.connect(self.instance.public_dns_name,
 
267
                                     username=uname, pkey=self._pkey)
 
268
        return self._ssh_client
 
269
 
 
270
    def get_file(self, remotepath, localpath):
 
271
        ssh_client = self.get_ssh_client()
 
272
        sftp_client = ssh_client.open_sftp()
 
273
        sftp_client.get(remotepath, localpath)
 
274
 
 
275
    def put_file(self, localpath, remotepath):
 
276
        ssh_client = self.get_ssh_client()
 
277
        sftp_client = ssh_client.open_sftp()
 
278
        sftp_client.put(localpath, remotepath)
 
279
 
 
280
    def listdir(self, remotepath):
 
281
        ssh_client = self.get_ssh_client()
 
282
        sftp_client = ssh_client.open_sftp()
 
283
        return sftp_client.listdir(remotepath)
 
284
 
 
285
    def shell(self, key_file=None):
 
286
        ssh_client = self.get_ssh_client(key_file)
 
287
        channel = ssh_client.invoke_shell()
 
288
        interactive_shell(channel)
 
289
 
 
290
    def bundle_image(self, prefix, key_file, cert_file, size):
 
291
        print 'bundling image...'
 
292
        print '\tcopying cert and pk over to /mnt directory on server'
 
293
        ssh_client = self.get_ssh_client()
 
294
        sftp_client = ssh_client.open_sftp()
 
295
        path, name = os.path.split(key_file)
 
296
        remote_key_file = '/mnt/%s' % name
 
297
        self.put_file(key_file, remote_key_file)
 
298
        path, name = os.path.split(cert_file)
 
299
        remote_cert_file = '/mnt/%s' % name
 
300
        self.put_file(cert_file, remote_cert_file)
 
301
        print '\tdeleting %s' % BotoConfigPath
 
302
        # delete the metadata.ini file if it exists
 
303
        try:
 
304
            sftp_client.remove(BotoConfigPath)
 
305
        except:
 
306
            pass
 
307
        command = 'sudo ec2-bundle-vol '
 
308
        command += '-c %s -k %s ' % (remote_cert_file, remote_key_file)
 
309
        command += '-u %s ' % self._reservation.owner_id
 
310
        command += '-p %s ' % prefix
 
311
        command += '-s %d ' % size
 
312
        command += '-d /mnt '
 
313
        if self.instance.instance_type == 'm1.small' or self.instance_type == 'c1.medium':
 
314
            command += '-r i386'
 
315
        else:
 
316
            command += '-r x86_64'
 
317
        print '\t%s' % command
 
318
        t = ssh_client.exec_command(command)
 
319
        response = t[1].read()
 
320
        print '\t%s' % response
 
321
        print '\t%s' % t[2].read()
 
322
        print '...complete!'
 
323
 
 
324
    def upload_bundle(self, bucket, prefix):
 
325
        print 'uploading bundle...'
 
326
        command = 'ec2-upload-bundle '
 
327
        command += '-m /mnt/%s.manifest.xml ' % prefix
 
328
        command += '-b %s ' % bucket
 
329
        command += '-a %s ' % self.ec2.aws_access_key_id
 
330
        command += '-s %s ' % self.ec2.aws_secret_access_key
 
331
        print '\t%s' % command
 
332
        ssh_client = self.get_ssh_client()
 
333
        t = ssh_client.exec_command(command)
 
334
        response = t[1].read()
 
335
        print '\t%s' % response
 
336
        print '\t%s' % t[2].read()
 
337
        print '...complete!'
 
338
 
 
339
    def create_image(self, bucket=None, prefix=None, key_file=None, cert_file=None, size=None):
 
340
        iobject = IObject()
 
341
        if not bucket:
 
342
            bucket = iobject.get_string('Name of S3 bucket')
 
343
        if not prefix:
 
344
            prefix = iobject.get_string('Prefix for AMI file')
 
345
        if not key_file:
 
346
            key_file = iobject.get_filename('Path to RSA private key file')
 
347
        if not cert_file:
 
348
            cert_file = iobject.get_filename('Path to RSA public cert file')
 
349
        if not size:
 
350
            size = iobject.get_int('Size (in MB) of bundled image')
 
351
        self.bundle_image(prefix, key_file, cert_file, size)
 
352
        self.upload_bundle(bucket, prefix)
 
353
        print 'registering image...'
 
354
        self.image_id = self.ec2.register_image('%s/%s.manifest.xml' % (bucket, prefix))
 
355
        return self.image_id
 
356
 
 
357
    def attach_volume(self, volume, device="/dev/sdp"):
 
358
        """
 
359
        Attach an EBS volume to this server
 
360
 
 
361
        :param volume: EBS Volume to attach
 
362
        :type volume: boto.ec2.volume.Volume
 
363
 
 
364
        :param device: Device to attach to (default to /dev/sdp)
 
365
        :type device: string
 
366
        """
 
367
        if hasattr(volume, "id"):
 
368
            volume_id = volume.id
 
369
        else:
 
370
            volume_id = volume
 
371
        return self.ec2.attach_volume(volume_id=volume_id, instance_id=self.instance_id, device=device)
 
372
 
 
373
    def detach_volume(self, volume):
 
374
        """
 
375
        Detach an EBS volume from this server
 
376
 
 
377
        :param volume: EBS Volume to detach
 
378
        :type volume: boto.ec2.volume.Volume
 
379
        """
 
380
        if hasattr(volume, "id"):
 
381
            volume_id = volume.id
 
382
        else:
 
383
            volume_id = volume
 
384
        return self.ec2.detach_volume(volume_id=volume_id, instance_id=self.instance_id)
 
385
 
 
386
    def install_package(self, package_name):
 
387
        print 'installing %s...' % package_name
 
388
        command = 'yum -y install %s' % package_name
 
389
        print '\t%s' % command
 
390
        ssh_client = self.get_ssh_client()
 
391
        t = ssh_client.exec_command(command)
 
392
        response = t[1].read()
 
393
        print '\t%s' % response
 
394
        print '\t%s' % t[2].read()
 
395
        print '...complete!'