~0x44/nova/bug838466

« back to all changes in this revision

Viewing changes to bin/nova-manage

  • Committer: Brian Waldon
  • Date: 2011-07-29 16:54:55 UTC
  • mto: This revision was merged to the branch mainline in revision 1364.
  • Revision ID: brian.waldon@rackspace.com-20110729165455-4ebqwv8s5pkscmmg
one last change

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
#    License for the specific language governing permissions and limitations
18
18
#    under the License.
19
19
 
 
20
# Interactive shell based on Django:
 
21
#
 
22
# Copyright (c) 2005, the Lawrence Journal-World
 
23
# All rights reserved.
 
24
#
 
25
# Redistribution and use in source and binary forms, with or without
 
26
# modification, are permitted provided that the following conditions are met:
 
27
#
 
28
#     1. Redistributions of source code must retain the above copyright notice,
 
29
#        this list of conditions and the following disclaimer.
 
30
#
 
31
#     2. Redistributions in binary form must reproduce the above copyright
 
32
#        notice, this list of conditions and the following disclaimer in the
 
33
#        documentation and/or other materials provided with the distribution.
 
34
#
 
35
#     3. Neither the name of Django nor the names of its contributors may be
 
36
#        used to endorse or promote products derived from this software without
 
37
#        specific prior written permission.
 
38
#
 
39
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 
40
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 
41
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 
42
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 
43
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 
44
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 
45
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 
46
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 
47
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 
48
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 
49
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
50
 
 
51
 
20
52
"""
21
53
  CLI interface for nova management.
22
 
  Connects to the running ADMIN api in the api daemon.
23
54
"""
24
55
 
 
56
import gettext
 
57
import glob
 
58
import json
 
59
import netaddr
 
60
import os
25
61
import sys
26
62
import time
27
63
 
 
64
from optparse import OptionParser
 
65
 
 
66
# If ../nova/__init__.py exists, add ../ to Python search path, so that
 
67
# it will override what happens to be installed in /usr/(local/)lib/python...
 
68
POSSIBLE_TOPDIR = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
 
69
                                   os.pardir,
 
70
                                   os.pardir))
 
71
if os.path.exists(os.path.join(POSSIBLE_TOPDIR, 'nova', '__init__.py')):
 
72
    sys.path.insert(0, POSSIBLE_TOPDIR)
 
73
 
 
74
gettext.install('nova', unicode=1)
 
75
 
 
76
from nova import context
 
77
from nova import crypto
 
78
from nova import db
 
79
from nova import exception
28
80
from nova import flags
 
81
from nova import image
 
82
from nova import log as logging
 
83
from nova import quota
 
84
from nova import rpc
29
85
from nova import utils
 
86
from nova import version
 
87
from nova.api.ec2 import ec2utils
30
88
from nova.auth import manager
31
 
from nova.compute import model
32
89
from nova.cloudpipe import pipelib
33
 
from nova.endpoint import cloud
34
 
 
 
90
from nova.compute import instance_types
 
91
from nova.db import migration
35
92
 
36
93
FLAGS = flags.FLAGS
 
94
flags.DECLARE('fixed_range', 'nova.network.manager')
 
95
flags.DECLARE('num_networks', 'nova.network.manager')
 
96
flags.DECLARE('network_size', 'nova.network.manager')
 
97
flags.DECLARE('vlan_start', 'nova.network.manager')
 
98
flags.DECLARE('vpn_start', 'nova.network.manager')
 
99
flags.DECLARE('fixed_range_v6', 'nova.network.manager')
 
100
flags.DECLARE('gateway_v6', 'nova.network.manager')
 
101
flags.DECLARE('libvirt_type', 'nova.virt.libvirt.connection')
 
102
flags.DEFINE_flag(flags.HelpFlag())
 
103
flags.DEFINE_flag(flags.HelpshortFlag())
 
104
flags.DEFINE_flag(flags.HelpXMLFlag())
 
105
 
 
106
 
 
107
# Decorators for actions
 
108
def args(*args, **kwargs):
 
109
    def _decorator(func):
 
110
        func.__dict__.setdefault('options', []).insert(0, (args, kwargs))
 
111
        return func
 
112
    return _decorator
 
113
 
 
114
 
 
115
def param2id(object_id):
 
116
    """Helper function to convert various id types to internal id.
 
117
    args: [object_id], e.g. 'vol-0000000a' or 'volume-0000000a' or '10'
 
118
    """
 
119
    if '-' in object_id:
 
120
        return ec2utils.ec2_id_to_id(object_id)
 
121
    else:
 
122
        return int(object_id)
37
123
 
38
124
 
39
125
class VpnCommands(object):
41
127
 
42
128
    def __init__(self):
43
129
        self.manager = manager.AuthManager()
44
 
        self.instdir = model.InstanceDirectory()
45
 
        self.pipe = pipelib.CloudPipe(cloud.CloudController())
46
 
 
47
 
    def list(self):
48
 
        """Print a listing of the VPNs for all projects."""
 
130
        self.pipe = pipelib.CloudPipe()
 
131
 
 
132
    @args('--project', dest="project", metavar='<Project name>',
 
133
            help='Project name')
 
134
    def list(self, project=None):
 
135
        """Print a listing of the VPN data for one or all projects."""
 
136
 
49
137
        print "%-12s\t" % 'project',
50
 
        print "%-12s\t" % 'ip:port',
 
138
        print "%-20s\t" % 'ip:port',
 
139
        print "%-20s\t" % 'private_ip',
51
140
        print "%s" % 'state'
52
 
        for project in self.manager.get_projects():
 
141
        if project:
 
142
            projects = [self.manager.get_project(project)]
 
143
        else:
 
144
            projects = self.manager.get_projects()
 
145
            # NOTE(vish): This hits the database a lot.  We could optimize
 
146
            #             by getting all networks in one query and all vpns
 
147
            #             in aother query, then doing lookups by project
 
148
        for project in projects:
53
149
            print "%-12s\t" % project.name,
54
 
            print "%s:%s\t" % (project.vpn_ip, project.vpn_port),
55
 
 
56
 
            vpn = self._vpn_for(project.id)
 
150
            ipport = "%s:%s" % (project.vpn_ip, project.vpn_port)
 
151
            print "%-20s\t" % ipport,
 
152
            ctxt = context.get_admin_context()
 
153
            vpn = db.instance_get_project_vpn(ctxt, project.id)
57
154
            if vpn:
58
 
                command = "ping -c1 -w1 %s > /dev/null; echo $?"
59
 
                out, _err = utils.execute(command % vpn['private_dns_name'],
60
 
                                          check_exit_code=False)
61
 
                if out.strip() == '0':
62
 
                    net = 'up'
63
 
                else:
64
 
                    net = 'down'
65
 
                print vpn['private_dns_name'],
66
 
                print vpn['node_name'],
67
 
                print vpn['instance_id'],
 
155
                address = None
 
156
                state = 'down'
 
157
                if vpn.get('fixed_ip', None):
 
158
                    address = vpn['fixed_ip']['address']
 
159
                if project.vpn_ip and utils.vpn_ping(project.vpn_ip,
 
160
                                                     project.vpn_port):
 
161
                    state = 'up'
 
162
                print address,
 
163
                print vpn['host'],
 
164
                print ec2utils.id_to_ec2_id(vpn['id']),
68
165
                print vpn['state_description'],
69
 
                print net
70
 
 
 
166
                print state
71
167
            else:
72
168
                print None
73
169
 
74
 
    def _vpn_for(self, project_id):
75
 
        """Get the VPN instance for a project ID."""
76
 
        for instance in self.instdir.all:
77
 
            if ('image_id' in instance.state
78
 
                and instance['image_id'] == FLAGS.vpn_image_id
79
 
                and not instance['state_description'] in
80
 
                    ['shutting_down', 'shutdown']
81
 
                and instance['project_id'] == project_id):
82
 
                return instance
83
 
 
84
170
    def spawn(self):
85
171
        """Run all VPNs."""
86
172
        for p in reversed(self.manager.get_projects()):
89
175
                self.pipe.launch_vpn_instance(p.id)
90
176
                time.sleep(10)
91
177
 
 
178
    @args('--project', dest="project_id", metavar='<Project name>',
 
179
            help='Project name')
92
180
    def run(self, project_id):
93
181
        """Start the VPN for a given project."""
94
182
        self.pipe.launch_vpn_instance(project_id)
95
183
 
 
184
    @args('--project', dest="project_id", metavar='<Project name>',
 
185
            help='Project name')
 
186
    @args('--ip', dest="ip", metavar='<IP Address>', help='IP Address')
 
187
    @args('--port', dest="port", metavar='<Port>', help='Port')
 
188
    def change(self, project_id, ip, port):
 
189
        """Change the ip and port for a vpn.
 
190
 
 
191
        this will update all networks associated with a project
 
192
        not sure if that's the desired behavior or not, patches accepted
 
193
 
 
194
        """
 
195
        # TODO(tr3buchet): perhaps this shouldn't update all networks
 
196
        # associated with a project in the future
 
197
        project = self.manager.get_project(project_id)
 
198
        if not project:
 
199
            print 'No project %s' % (project_id)
 
200
            return
 
201
        admin_context = context.get_admin_context()
 
202
        networks = db.project_get_networks(admin_context, project_id)
 
203
        for network in networks:
 
204
            db.network_update(admin_context,
 
205
                              network['id'],
 
206
                              {'vpn_public_address': ip,
 
207
                               'vpn_public_port': int(port)})
 
208
 
 
209
 
 
210
class ShellCommands(object):
 
211
    def bpython(self):
 
212
        """Runs a bpython shell.
 
213
 
 
214
        Falls back to Ipython/python shell if unavailable"""
 
215
        self.run('bpython')
 
216
 
 
217
    def ipython(self):
 
218
        """Runs an Ipython shell.
 
219
 
 
220
        Falls back to Python shell if unavailable"""
 
221
        self.run('ipython')
 
222
 
 
223
    def python(self):
 
224
        """Runs a python shell.
 
225
 
 
226
        Falls back to Python shell if unavailable"""
 
227
        self.run('python')
 
228
 
 
229
    @args('--shell', dest="shell", metavar='<bpython|ipython|python >',
 
230
            help='Python shell')
 
231
    def run(self, shell=None):
 
232
        """Runs a Python interactive interpreter."""
 
233
        if not shell:
 
234
            shell = 'bpython'
 
235
 
 
236
        if shell == 'bpython':
 
237
            try:
 
238
                import bpython
 
239
                bpython.embed()
 
240
            except ImportError:
 
241
                shell = 'ipython'
 
242
        if shell == 'ipython':
 
243
            try:
 
244
                import IPython
 
245
                # Explicitly pass an empty list as arguments, because
 
246
                # otherwise IPython would use sys.argv from this script.
 
247
                shell = IPython.Shell.IPShell(argv=[])
 
248
                shell.mainloop()
 
249
            except ImportError:
 
250
                shell = 'python'
 
251
 
 
252
        if shell == 'python':
 
253
            import code
 
254
            try:
 
255
                # Try activating rlcompleter, because it's handy.
 
256
                import readline
 
257
            except ImportError:
 
258
                pass
 
259
            else:
 
260
                # We don't have to wrap the following import in a 'try',
 
261
                # because we already know 'readline' was imported successfully.
 
262
                import rlcompleter
 
263
                readline.parse_and_bind("tab:complete")
 
264
            code.interact()
 
265
 
 
266
    @args('--path', dest='path', metavar='<path>', help='Script path')
 
267
    def script(self, path):
 
268
        """Runs the script from the specifed path with flags set properly.
 
269
        arguments: path"""
 
270
        exec(compile(open(path).read(), path, 'exec'), locals(), globals())
 
271
 
96
272
 
97
273
class RoleCommands(object):
98
274
    """Class for managing roles."""
100
276
    def __init__(self):
101
277
        self.manager = manager.AuthManager()
102
278
 
 
279
    @args('--user', dest="user", metavar='<user name>', help='User name')
 
280
    @args('--role', dest="role", metavar='<user role>', help='User role')
 
281
    @args('--project', dest="project", metavar='<Project name>',
 
282
            help='Project name')
103
283
    def add(self, user, role, project=None):
104
284
        """adds role to user
105
 
        if project is specified, adds project specific role
106
 
        arguments: user, role [project]"""
 
285
        if project is specified, adds project specific role"""
 
286
        if project:
 
287
            projobj = self.manager.get_project(project)
 
288
            if not projobj.has_member(user):
 
289
                print "%s not a member of %s" % (user, project)
 
290
                return
107
291
        self.manager.add_role(user, role, project)
108
292
 
 
293
    @args('--user', dest="user", metavar='<user name>', help='User name')
 
294
    @args('--role', dest="role", metavar='<user role>', help='User role')
 
295
    @args('--project', dest="project", metavar='<Project name>',
 
296
            help='Project name')
109
297
    def has(self, user, role, project=None):
110
298
        """checks to see if user has role
111
299
        if project is specified, returns True if user has
112
 
        the global role and the project role
113
 
        arguments: user, role [project]"""
 
300
        the global role and the project role"""
114
301
        print self.manager.has_role(user, role, project)
115
302
 
 
303
    @args('--user', dest="user", metavar='<user name>', help='User name')
 
304
    @args('--role', dest="role", metavar='<user role>', help='User role')
 
305
    @args('--project', dest="project", metavar='<Project name>',
 
306
            help='Project name')
116
307
    def remove(self, user, role, project=None):
117
308
        """removes role from user
118
 
        if project is specified, removes project specific role
119
 
        arguments: user, role [project]"""
 
309
        if project is specified, removes project specific role"""
120
310
        self.manager.remove_role(user, role, project)
121
311
 
122
312
 
 
313
def _db_error(caught_exception):
 
314
    print caught_exception
 
315
    print _("The above error may show that the database has not "
 
316
            "been created.\nPlease create a database using "
 
317
            "'nova-manage db sync' before running this command.")
 
318
    exit(1)
 
319
 
 
320
 
123
321
class UserCommands(object):
124
322
    """Class for managing users."""
125
323
 
 
324
    @staticmethod
 
325
    def _print_export(user):
 
326
        """Print export variables to use with API."""
 
327
        print 'export EC2_ACCESS_KEY=%s' % user.access
 
328
        print 'export EC2_SECRET_KEY=%s' % user.secret
 
329
 
126
330
    def __init__(self):
127
331
        self.manager = manager.AuthManager()
128
332
 
 
333
    @args('--name', dest="name", metavar='<admin name>', help='Admin name')
 
334
    @args('--access', dest="access", metavar='<access>', help='Access')
 
335
    @args('--secret', dest="secret", metavar='<secret>', help='Secret')
129
336
    def admin(self, name, access=None, secret=None):
130
 
        """creates a new admin and prints exports
131
 
        arguments: name [access] [secret]"""
132
 
        user = self.manager.create_user(name, access, secret, True)
133
 
        print_export(user)
 
337
        """creates a new admin and prints exports"""
 
338
        try:
 
339
            user = self.manager.create_user(name, access, secret, True)
 
340
        except exception.DBError, e:
 
341
            _db_error(e)
 
342
        self._print_export(user)
134
343
 
 
344
    @args('--name', dest="name", metavar='<name>', help='User name')
 
345
    @args('--access', dest="access", metavar='<access>', help='Access')
 
346
    @args('--secret', dest="secret", metavar='<secret>', help='Secret')
135
347
    def create(self, name, access=None, secret=None):
136
 
        """creates a new user and prints exports
137
 
        arguments: name [access] [secret]"""
138
 
        user = self.manager.create_user(name, access, secret, False)
139
 
        print_export(user)
 
348
        """creates a new user and prints exports"""
 
349
        try:
 
350
            user = self.manager.create_user(name, access, secret, False)
 
351
        except exception.DBError, e:
 
352
            _db_error(e)
 
353
        self._print_export(user)
140
354
 
 
355
    @args('--name', dest="name", metavar='<name>', help='User name')
141
356
    def delete(self, name):
142
357
        """deletes an existing user
143
358
        arguments: name"""
144
359
        self.manager.delete_user(name)
145
360
 
 
361
    @args('--name', dest="name", metavar='<admin name>', help='User name')
146
362
    def exports(self, name):
147
 
        """prints access and secrets for user in export format
148
 
        arguments: name"""
 
363
        """prints access and secrets for user in export format"""
149
364
        user = self.manager.get_user(name)
150
365
        if user:
151
 
            print_export(user)
 
366
            self._print_export(user)
152
367
        else:
153
368
            print "User %s doesn't exist" % name
154
369
 
155
370
    def list(self):
156
 
        """lists all users
157
 
        arguments: <none>"""
 
371
        """lists all users"""
158
372
        for user in self.manager.get_users():
159
373
            print user.name
160
374
 
 
375
    @args('--name', dest="name", metavar='<name>', help='User name')
 
376
    @args('--access', dest="access_key", metavar='<access>',
 
377
            help='Access key')
 
378
    @args('--secret', dest="secret_key", metavar='<secret>',
 
379
            help='Secret key')
 
380
    @args('--is_admin', dest='is_admin', metavar="<'T'|'F'>",
 
381
            help='Is admin?')
 
382
    def modify(self, name, access_key, secret_key, is_admin):
 
383
        """update a users keys & admin flag
 
384
        arguments: accesskey secretkey admin
 
385
        leave any field blank to ignore it, admin should be 'T', 'F', or blank
 
386
        """
 
387
        if not is_admin:
 
388
            is_admin = None
 
389
        elif is_admin.upper()[0] == 'T':
 
390
            is_admin = True
 
391
        else:
 
392
            is_admin = False
 
393
        self.manager.modify_user(name, access_key, secret_key, is_admin)
161
394
 
162
 
def print_export(user):
163
 
    """Print export variables to use with API."""
164
 
    print 'export EC2_ACCESS_KEY=%s' % user.access
165
 
    print 'export EC2_SECRET_KEY=%s' % user.secret
 
395
    @args('--name', dest="user_id", metavar='<name>', help='User name')
 
396
    @args('--project', dest="project_id", metavar='<Project name>',
 
397
            help='Project name')
 
398
    def revoke(self, user_id, project_id=None):
 
399
        """revoke certs for a user"""
 
400
        if project_id:
 
401
            crypto.revoke_certs_by_user_and_project(user_id, project_id)
 
402
        else:
 
403
            crypto.revoke_certs_by_user(user_id)
166
404
 
167
405
 
168
406
class ProjectCommands(object):
171
409
    def __init__(self):
172
410
        self.manager = manager.AuthManager()
173
411
 
174
 
    def add(self, project, user):
175
 
        """Adds user to project
176
 
        arguments: project user"""
177
 
        self.manager.add_to_project(user, project)
 
412
    @args('--project', dest="project_id", metavar='<Project name>',
 
413
            help='Project name')
 
414
    @args('--user', dest="user_id", metavar='<name>', help='User name')
 
415
    def add(self, project_id, user_id):
 
416
        """Adds user to project"""
 
417
        try:
 
418
            self.manager.add_to_project(user_id, project_id)
 
419
        except exception.UserNotFound as ex:
 
420
            print ex
 
421
            raise
178
422
 
 
423
    @args('--project', dest="name", metavar='<Project name>',
 
424
            help='Project name')
 
425
    @args('--user', dest="project_manager", metavar='<user>',
 
426
            help='Project manager')
 
427
    @args('--desc', dest="description", metavar='<description>',
 
428
            help='Description')
179
429
    def create(self, name, project_manager, description=None):
180
 
        """Creates a new project
181
 
        arguments: name project_manager [description]"""
182
 
        self.manager.create_project(name, project_manager, description)
183
 
 
 
430
        """Creates a new project"""
 
431
        try:
 
432
            self.manager.create_project(name, project_manager, description)
 
433
        except exception.UserNotFound as ex:
 
434
            print ex
 
435
            raise
 
436
 
 
437
    @args('--project', dest="name", metavar='<Project name>',
 
438
            help='Project name')
 
439
    @args('--user', dest="project_manager", metavar='<user>',
 
440
            help='Project manager')
 
441
    @args('--desc', dest="description", metavar='<description>',
 
442
            help='Description')
 
443
    def modify(self, name, project_manager, description=None):
 
444
        """Modifies a project"""
 
445
        try:
 
446
            self.manager.modify_project(name, project_manager, description)
 
447
        except exception.UserNotFound as ex:
 
448
            print ex
 
449
            raise
 
450
 
 
451
    @args('--project', dest="name", metavar='<Project name>',
 
452
            help='Project name')
184
453
    def delete(self, name):
185
 
        """Deletes an existing project
186
 
        arguments: name"""
187
 
        self.manager.delete_project(name)
 
454
        """Deletes an existing project"""
 
455
        try:
 
456
            self.manager.delete_project(name)
 
457
        except exception.ProjectNotFound as ex:
 
458
            print ex
 
459
            raise
188
460
 
 
461
    @args('--project', dest="project_id", metavar='<Project name>',
 
462
            help='Project name')
 
463
    @args('--user', dest="user_id", metavar='<name>', help='User name')
 
464
    @args('--file', dest="filename", metavar='<filename>',
 
465
            help='File name(Default: novarc)')
189
466
    def environment(self, project_id, user_id, filename='novarc'):
190
 
        """Exports environment variables to an sourcable file
191
 
        arguments: project_id user_id [filename='novarc]"""
192
 
        rc = self.manager.get_environment_rc(project_id, user_id)
193
 
        with open(filename, 'w') as f:
194
 
            f.write(rc)
 
467
        """Exports environment variables to an sourcable file"""
 
468
        try:
 
469
            rc = self.manager.get_environment_rc(user_id, project_id)
 
470
        except (exception.UserNotFound, exception.ProjectNotFound) as ex:
 
471
            print ex
 
472
            raise
 
473
        if filename == "-":
 
474
            sys.stdout.write(rc)
 
475
        else:
 
476
            with open(filename, 'w') as f:
 
477
                f.write(rc)
195
478
 
196
 
    def list(self):
197
 
        """Lists all projects
198
 
        arguments: <none>"""
199
 
        for project in self.manager.get_projects():
 
479
    @args('--user', dest="username", metavar='<username>', help='User name')
 
480
    def list(self, username=None):
 
481
        """Lists all projects"""
 
482
        for project in self.manager.get_projects(username):
200
483
            print project.name
201
484
 
202
 
    def remove(self, project, user):
203
 
        """Removes user from project
204
 
        arguments: project user"""
205
 
        self.manager.remove_from_project(user, project)
206
 
 
 
485
    @args('--project', dest="project_id", metavar='<Project name>',
 
486
            help='Project name')
 
487
    @args('--key', dest="key", metavar='<key>', help='Key')
 
488
    @args('--value', dest="value", metavar='<value>', help='Value')
 
489
    def quota(self, project_id, key=None, value=None):
 
490
        """Set or display quotas for project"""
 
491
        ctxt = context.get_admin_context()
 
492
        if key:
 
493
            if value.lower() == 'unlimited':
 
494
                value = None
 
495
            try:
 
496
                db.quota_update(ctxt, project_id, key, value)
 
497
            except exception.ProjectQuotaNotFound:
 
498
                db.quota_create(ctxt, project_id, key, value)
 
499
        project_quota = quota.get_project_quotas(ctxt, project_id)
 
500
        for key, value in project_quota.iteritems():
 
501
            if value is None:
 
502
                value = 'unlimited'
 
503
            print '%s: %s' % (key, value)
 
504
 
 
505
    @args('--project', dest="project_id", metavar='<Project name>',
 
506
            help='Project name')
 
507
    @args('--user', dest="user_id", metavar='<name>', help='User name')
 
508
    def remove(self, project_id, user_id):
 
509
        """Removes user from project"""
 
510
        try:
 
511
            self.manager.remove_from_project(user_id, project_id)
 
512
        except (exception.UserNotFound, exception.ProjectNotFound) as ex:
 
513
            print ex
 
514
            raise
 
515
 
 
516
    @args('--project', dest="project_id", metavar='<Project name>',
 
517
            help='Project name')
 
518
    def scrub(self, project_id):
 
519
        """Deletes data associated with project"""
 
520
        admin_context = context.get_admin_context()
 
521
        networks = db.project_get_networks(admin_context, project_id)
 
522
        for network in networks:
 
523
            db.network_disassociate(admin_context, network['id'])
 
524
        groups = db.security_group_get_by_project(admin_context, project_id)
 
525
        for group in groups:
 
526
            db.security_group_destroy(admin_context, group['id'])
 
527
 
 
528
    @args('--project', dest="project_id", metavar='<Project name>',
 
529
            help='Project name')
 
530
    @args('--user', dest="user_id", metavar='<name>', help='User name')
 
531
    @args('--file', dest="filename", metavar='<filename>',
 
532
            help='File name(Default: nova.zip)')
207
533
    def zipfile(self, project_id, user_id, filename='nova.zip'):
208
 
        """Exports credentials for project to a zip file
209
 
        arguments: project_id user_id [filename='nova.zip]"""
210
 
        zip_file = self.manager.get_credentials(user_id, project_id)
211
 
        with open(filename, 'w') as f:
212
 
            f.write(zip_file)
 
534
        """Exports credentials for project to a zip file"""
 
535
        try:
 
536
            zip_file = self.manager.get_credentials(user_id, project_id)
 
537
            if filename == "-":
 
538
                sys.stdout.write(zip_file)
 
539
            else:
 
540
                with open(filename, 'w') as f:
 
541
                    f.write(zip_file)
 
542
        except (exception.UserNotFound, exception.ProjectNotFound) as ex:
 
543
            print ex
 
544
            raise
 
545
        except db.api.NoMoreNetworks:
 
546
            print _('No more networks available. If this is a new '
 
547
                    'installation, you need\nto call something like this:\n\n'
 
548
                    '  nova-manage network create pvt 10.0.0.0/8 10 64\n\n')
 
549
        except exception.ProcessExecutionError, e:
 
550
            print e
 
551
            print _("The above error may show that the certificate db has "
 
552
                    "not been created.\nPlease create a database by running "
 
553
                    "a nova-api server on this host.")
 
554
 
 
555
AccountCommands = ProjectCommands
 
556
 
 
557
 
 
558
class FixedIpCommands(object):
 
559
    """Class for managing fixed ip."""
 
560
 
 
561
    @args('--host', dest="host", metavar='<host>', help='Host')
 
562
    def list(self, host=None):
 
563
        """Lists all fixed ips (optionally by host)"""
 
564
        ctxt = context.get_admin_context()
 
565
 
 
566
        try:
 
567
            if host is None:
 
568
                fixed_ips = db.fixed_ip_get_all(ctxt)
 
569
            else:
 
570
                fixed_ips = db.fixed_ip_get_all_by_instance_host(ctxt, host)
 
571
        except exception.NotFound as ex:
 
572
            print "error: %s" % ex
 
573
            sys.exit(2)
 
574
 
 
575
        print "%-18s\t%-15s\t%-17s\t%-15s\t%s" % (_('network'),
 
576
                                                  _('IP address'),
 
577
                                                  _('MAC address'),
 
578
                                                  _('hostname'),
 
579
                                                  _('host'))
 
580
        for fixed_ip in fixed_ips:
 
581
            hostname = None
 
582
            host = None
 
583
            mac_address = None
 
584
            if fixed_ip['instance']:
 
585
                instance = fixed_ip['instance']
 
586
                hostname = instance['hostname']
 
587
                host = instance['host']
 
588
                mac_address = fixed_ip['virtual_interface']['address']
 
589
            print "%-18s\t%-15s\t%-17s\t%-15s\t%s" % (
 
590
                    fixed_ip['network']['cidr'],
 
591
                    fixed_ip['address'],
 
592
                    mac_address, hostname, host)
 
593
 
 
594
 
 
595
class FloatingIpCommands(object):
 
596
    """Class for managing floating ip."""
 
597
 
 
598
    @args('--ip_range', dest="range", metavar='<range>', help='IP range')
 
599
    def create(self, range):
 
600
        """Creates floating ips for zone by range"""
 
601
        for address in netaddr.IPNetwork(range):
 
602
            db.floating_ip_create(context.get_admin_context(),
 
603
                                  {'address': str(address)})
 
604
 
 
605
    @args('--ip_range', dest="ip_range", metavar='<range>', help='IP range')
 
606
    def delete(self, ip_range):
 
607
        """Deletes floating ips by range"""
 
608
        for address in netaddr.IPNetwork(ip_range):
 
609
            db.floating_ip_destroy(context.get_admin_context(),
 
610
                                   str(address))
 
611
 
 
612
    @args('--host', dest="host", metavar='<host>', help='Host')
 
613
    def list(self, host=None):
 
614
        """Lists all floating ips (optionally by host)
 
615
        Note: if host is given, only active floating IPs are returned"""
 
616
        ctxt = context.get_admin_context()
 
617
        if host is None:
 
618
            floating_ips = db.floating_ip_get_all(ctxt)
 
619
        else:
 
620
            floating_ips = db.floating_ip_get_all_by_host(ctxt, host)
 
621
        for floating_ip in floating_ips:
 
622
            instance = None
 
623
            if floating_ip['fixed_ip']:
 
624
                instance = floating_ip['fixed_ip']['instance']['hostname']
 
625
            print "%s\t%s\t%s" % (floating_ip['host'],
 
626
                                  floating_ip['address'],
 
627
                                  instance)
 
628
 
 
629
 
 
630
class NetworkCommands(object):
 
631
    """Class for managing networks."""
 
632
 
 
633
    @args('--label', dest="label", metavar='<label>',
 
634
            help='Label for network (ex: public)')
 
635
    @args('--fixed_range_v4', dest="fixed_range_v4", metavar='<x.x.x.x/yy>',
 
636
            help='IPv4 subnet (ex: 10.0.0.0/8)')
 
637
    @args('--num_networks', dest="num_networks", metavar='<number>',
 
638
            help='Number of networks to create')
 
639
    @args('--network_size', dest="network_size", metavar='<number>',
 
640
            help='Number of IPs per network')
 
641
    @args('--vlan', dest="vlan_start", metavar='<vlan id>', help='vlan id')
 
642
    @args('--vpn', dest="vpn_start", help='vpn start')
 
643
    @args('--fixed_range_v6', dest="fixed_range_v6",
 
644
          help='IPv6 subnet (ex: fe80::/64')
 
645
    @args('--gateway_v6', dest="gateway_v6", help='ipv6 gateway')
 
646
    @args('--bridge', dest="bridge",
 
647
            metavar='<bridge>',
 
648
            help='VIFs on this network are connected to this bridge')
 
649
    @args('--bridge_interface', dest="bridge_interface",
 
650
            metavar='<bridge interface>',
 
651
            help='the bridge is connected to this interface')
 
652
    @args('--multi_host', dest="multi_host", metavar="<'T'|'F'>",
 
653
            help='Multi host')
 
654
    @args('--dns1', dest="dns1", metavar="<DNS Address>", help='First DNS')
 
655
    @args('--dns2', dest="dns2", metavar="<DNS Address>", help='Second DNS')
 
656
    def create(self, label=None, fixed_range_v4=None, num_networks=None,
 
657
               network_size=None, multi_host=None, vlan_start=None,
 
658
               vpn_start=None, fixed_range_v6=None, gateway_v6=None,
 
659
               bridge=None, bridge_interface=None, dns1=None, dns2=None):
 
660
        """Creates fixed ips for host by range"""
 
661
 
 
662
        # check for certain required inputs
 
663
        if not label:
 
664
            raise exception.NetworkNotCreated(req='--label')
 
665
        if not fixed_range_v4:
 
666
            raise exception.NetworkNotCreated(req='--fixed_range_v4')
 
667
 
 
668
        bridge = bridge or FLAGS.flat_network_bridge
 
669
        if not bridge:
 
670
            bridge_required = ['nova.network.manager.FlatManager',
 
671
                               'nova.network.manager.FlatDHCPManager']
 
672
            if FLAGS.network_manager in bridge_required:
 
673
                # TODO(tr3buchet) - swap print statement and following line for
 
674
                #                   raise statement in diablo 4
 
675
                print _('--bridge parameter required or FLAG '
 
676
                        'flat_network_bridge must be set to create networks\n'
 
677
                        'WARNING! ACHTUNG! Setting the bridge to br100 '
 
678
                        'automatically is deprecated and will be removed in '
 
679
                        'Diablo milestone 4. Prepare yourself accordingly.')
 
680
                time.sleep(10)
 
681
                bridge = 'br100'
 
682
                #raise exception.NetworkNotCreated(req='--bridge')
 
683
 
 
684
        bridge_interface = bridge_interface or FLAGS.flat_interface or \
 
685
                           FLAGS.vlan_interface
 
686
        if not bridge_interface:
 
687
            interface_required = ['nova.network.manager.FlatDHCPManager',
 
688
                                  'nova.network.manager.VlanManager']
 
689
            if FLAGS.network_manager in interface_required:
 
690
                raise exception.NetworkNotCreated(req='--bridge_interface')
 
691
 
 
692
        if FLAGS.use_ipv6:
 
693
            fixed_range_v6 = fixed_range_v6 or FLAGS.fixed_range_v6
 
694
            if not fixed_range_v6:
 
695
                raise exception.NetworkNotCreated(req='with use_ipv6, '
 
696
                                                      '--fixed_range_v6')
 
697
            gateway_v6 = gateway_v6 or FLAGS.gateway_v6
 
698
            if not gateway_v6:
 
699
                raise exception.NetworkNotCreated(req='with use_ipv6, '
 
700
                                                      '--gateway_v6')
 
701
 
 
702
        # sanitize other input using FLAGS if necessary
 
703
        if not num_networks:
 
704
            num_networks = FLAGS.num_networks
 
705
        if not network_size:
 
706
            network_size = FLAGS.network_size
 
707
        if not multi_host:
 
708
            multi_host = FLAGS.multi_host
 
709
        else:
 
710
            multi_host = multi_host == 'T'
 
711
        if not vlan_start:
 
712
            vlan_start = FLAGS.vlan_start
 
713
        if not vpn_start:
 
714
            vpn_start = FLAGS.vpn_start
 
715
        if not dns1 and FLAGS.flat_network_dns:
 
716
            dns1 = FLAGS.flat_network_dns
 
717
 
 
718
        # create the network
 
719
        net_manager = utils.import_object(FLAGS.network_manager)
 
720
        net_manager.create_networks(context.get_admin_context(),
 
721
                                    label=label,
 
722
                                    cidr=fixed_range_v4,
 
723
                                    multi_host=multi_host,
 
724
                                    num_networks=int(num_networks),
 
725
                                    network_size=int(network_size),
 
726
                                    vlan_start=int(vlan_start),
 
727
                                    vpn_start=int(vpn_start),
 
728
                                    cidr_v6=fixed_range_v6,
 
729
                                    gateway_v6=gateway_v6,
 
730
                                    bridge=bridge,
 
731
                                    bridge_interface=bridge_interface,
 
732
                                    dns1=dns1,
 
733
                                    dns2=dns2)
 
734
 
 
735
    def list(self):
 
736
        """List all created networks"""
 
737
        print "%-18s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s" % (
 
738
                                                  _('network'),
 
739
                                                  _('netmask'),
 
740
                                                  _('start address'),
 
741
                                                  _('DNS1'),
 
742
                                                  _('DNS2'),
 
743
                                                  _('VlanID'),
 
744
                                                  'project')
 
745
        for network in db.network_get_all(context.get_admin_context()):
 
746
            print "%-18s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s" % (
 
747
                                        network.cidr,
 
748
                                        network.netmask,
 
749
                                        network.dhcp_start,
 
750
                                        network.dns1,
 
751
                                        network.dns2,
 
752
                                        network.vlan,
 
753
                                        network.project_id)
 
754
 
 
755
    @args('--network', dest="fixed_range", metavar='<x.x.x.x/yy>',
 
756
            help='Network to delete')
 
757
    def delete(self, fixed_range):
 
758
        """Deletes a network"""
 
759
        network = db.network_get_by_cidr(context.get_admin_context(), \
 
760
                                         fixed_range)
 
761
        if network.project_id is not None:
 
762
            raise ValueError(_('Network must be disassociated from project %s'
 
763
                               ' before delete' % network.project_id))
 
764
        db.network_delete_safe(context.get_admin_context(), network.id)
 
765
 
 
766
 
 
767
class VmCommands(object):
 
768
    """Class for mangaging VM instances."""
 
769
 
 
770
    @args('--host', dest="host", metavar='<host>', help='Host')
 
771
    def list(self, host=None):
 
772
        """Show a list of all instances"""
 
773
 
 
774
        print "%-10s %-15s %-10s %-10s %-26s %-9s %-9s %-9s" \
 
775
              "  %-10s %-10s %-10s %-5s" % (
 
776
            _('instance'),
 
777
            _('node'),
 
778
            _('type'),
 
779
            _('state'),
 
780
            _('launched'),
 
781
            _('image'),
 
782
            _('kernel'),
 
783
            _('ramdisk'),
 
784
            _('project'),
 
785
            _('user'),
 
786
            _('zone'),
 
787
            _('index'))
 
788
 
 
789
        if host is None:
 
790
            instances = db.instance_get_all(context.get_admin_context())
 
791
        else:
 
792
            instances = db.instance_get_all_by_host(
 
793
                           context.get_admin_context(), host)
 
794
 
 
795
        for instance in instances:
 
796
            print "%-10s %-15s %-10s %-10s %-26s %-9s %-9s %-9s" \
 
797
                  "  %-10s %-10s %-10s %-5d" % (
 
798
                instance['hostname'],
 
799
                instance['host'],
 
800
                instance['instance_type'].name,
 
801
                instance['state_description'],
 
802
                instance['launched_at'],
 
803
                instance['image_ref'],
 
804
                instance['kernel_id'],
 
805
                instance['ramdisk_id'],
 
806
                instance['project_id'],
 
807
                instance['user_id'],
 
808
                instance['availability_zone'],
 
809
                instance['launch_index'])
 
810
 
 
811
    @args('--ec2_id', dest='ec2_id', metavar='<ec2 id>', help='EC2 ID')
 
812
    @args('--dest', dest='dest', metavar='<Destanation>',
 
813
            help='destanation node')
 
814
    def live_migration(self, ec2_id, dest):
 
815
        """Migrates a running instance to a new machine."""
 
816
 
 
817
        ctxt = context.get_admin_context()
 
818
        instance_id = ec2utils.ec2_id_to_id(ec2_id)
 
819
 
 
820
        if (FLAGS.connection_type != 'libvirt' or
 
821
           (FLAGS.connection_type == 'libvirt' and
 
822
            FLAGS.libvirt_type not in ['kvm', 'qemu'])):
 
823
            msg = _('Only KVM and QEmu are supported for now. Sorry!')
 
824
            raise exception.Error(msg)
 
825
 
 
826
        if (FLAGS.volume_driver != 'nova.volume.driver.AOEDriver' and \
 
827
            FLAGS.volume_driver != 'nova.volume.driver.ISCSIDriver'):
 
828
            msg = _("Support only AOEDriver and ISCSIDriver. Sorry!")
 
829
            raise exception.Error(msg)
 
830
 
 
831
        rpc.call(ctxt,
 
832
                 FLAGS.scheduler_topic,
 
833
                 {"method": "live_migration",
 
834
                  "args": {"instance_id": instance_id,
 
835
                           "dest": dest,
 
836
                           "topic": FLAGS.compute_topic}})
 
837
 
 
838
        print _('Migration of %s initiated.'
 
839
               'Check its progress using euca-describe-instances.') % ec2_id
 
840
 
 
841
 
 
842
class ServiceCommands(object):
 
843
    """Enable and disable running services"""
 
844
 
 
845
    @args('--host', dest='host', metavar='<host>', help='Host')
 
846
    @args('--service', dest='service', metavar='<service>',
 
847
            help='Nova service')
 
848
    def list(self, host=None, service=None):
 
849
        """
 
850
        Show a list of all running services. Filter by host & service name.
 
851
        """
 
852
        ctxt = context.get_admin_context()
 
853
        now = utils.utcnow()
 
854
        services = db.service_get_all(ctxt)
 
855
        if host:
 
856
            services = [s for s in services if s['host'] == host]
 
857
        if service:
 
858
            services = [s for s in services if s['binary'] == service]
 
859
        for svc in services:
 
860
            delta = now - (svc['updated_at'] or svc['created_at'])
 
861
            alive = (delta.seconds <= 15)
 
862
            art = (alive and ":-)") or "XXX"
 
863
            active = 'enabled'
 
864
            if svc['disabled']:
 
865
                active = 'disabled'
 
866
            print "%-10s %-10s %-8s %s %s" % (svc['host'], svc['binary'],
 
867
                                              active, art,
 
868
                                              svc['updated_at'])
 
869
 
 
870
    @args('--host', dest='host', metavar='<host>', help='Host')
 
871
    @args('--service', dest='service', metavar='<service>',
 
872
            help='Nova service')
 
873
    def enable(self, host, service):
 
874
        """Enable scheduling for a service"""
 
875
        ctxt = context.get_admin_context()
 
876
        svc = db.service_get_by_args(ctxt, host, service)
 
877
        if not svc:
 
878
            print "Unable to find service"
 
879
            return
 
880
        db.service_update(ctxt, svc['id'], {'disabled': False})
 
881
 
 
882
    @args('--host', dest='host', metavar='<host>', help='Host')
 
883
    @args('--service', dest='service', metavar='<service>',
 
884
            help='Nova service')
 
885
    def disable(self, host, service):
 
886
        """Disable scheduling for a service"""
 
887
        ctxt = context.get_admin_context()
 
888
        svc = db.service_get_by_args(ctxt, host, service)
 
889
        if not svc:
 
890
            print "Unable to find service"
 
891
            return
 
892
        db.service_update(ctxt, svc['id'], {'disabled': True})
 
893
 
 
894
    @args('--host', dest='host', metavar='<host>', help='Host')
 
895
    def describe_resource(self, host):
 
896
        """Describes cpu/memory/hdd info for host."""
 
897
 
 
898
        result = rpc.call(context.get_admin_context(),
 
899
                     FLAGS.scheduler_topic,
 
900
                     {"method": "show_host_resources",
 
901
                      "args": {"host": host}})
 
902
 
 
903
        if type(result) != dict:
 
904
            print _('An unexpected error has occurred.')
 
905
            print _('[Result]'), result
 
906
        else:
 
907
            cpu = result['resource']['vcpus']
 
908
            mem = result['resource']['memory_mb']
 
909
            hdd = result['resource']['local_gb']
 
910
            cpu_u = result['resource']['vcpus_used']
 
911
            mem_u = result['resource']['memory_mb_used']
 
912
            hdd_u = result['resource']['local_gb_used']
 
913
 
 
914
            print 'HOST\t\t\tPROJECT\t\tcpu\tmem(mb)\tdisk(gb)'
 
915
            print '%s(total)\t\t\t%s\t%s\t%s' % (host, cpu, mem, hdd)
 
916
            print '%s(used)\t\t\t%s\t%s\t%s' % (host, cpu_u, mem_u, hdd_u)
 
917
            for p_id, val in result['usage'].items():
 
918
                print '%s\t\t%s\t\t%s\t%s\t%s' % (host,
 
919
                                                  p_id,
 
920
                                                  val['vcpus'],
 
921
                                                  val['memory_mb'],
 
922
                                                  val['local_gb'])
 
923
 
 
924
    @args('--host', dest='host', metavar='<host>', help='Host')
 
925
    def update_resource(self, host):
 
926
        """Updates available vcpu/memory/disk info for host."""
 
927
 
 
928
        ctxt = context.get_admin_context()
 
929
        service_refs = db.service_get_all_by_host(ctxt, host)
 
930
        if len(service_refs) <= 0:
 
931
            raise exception.Invalid(_('%s does not exist.') % host)
 
932
 
 
933
        service_refs = [s for s in service_refs if s['topic'] == 'compute']
 
934
        if len(service_refs) <= 0:
 
935
            raise exception.Invalid(_('%s is not compute node.') % host)
 
936
 
 
937
        rpc.call(ctxt,
 
938
                 db.queue_get_for(ctxt, FLAGS.compute_topic, host),
 
939
                 {"method": "update_available_resource"})
 
940
 
 
941
 
 
942
class HostCommands(object):
 
943
    """List hosts"""
 
944
 
 
945
    def list(self, zone=None):
 
946
        """Show a list of all physical hosts. Filter by zone.
 
947
        args: [zone]"""
 
948
        print "%-25s\t%-15s" % (_('host'),
 
949
                                _('zone'))
 
950
        ctxt = context.get_admin_context()
 
951
        now = utils.utcnow()
 
952
        services = db.service_get_all(ctxt)
 
953
        if zone:
 
954
            services = [s for s in services if s['availability_zone'] == zone]
 
955
        hosts = []
 
956
        for srv in services:
 
957
            if not [h for h in hosts if h['host'] == srv['host']]:
 
958
                hosts.append(srv)
 
959
 
 
960
        for h in hosts:
 
961
            print "%-25s\t%-15s" % (h['host'], h['availability_zone'])
 
962
 
 
963
 
 
964
class DbCommands(object):
 
965
    """Class for managing the database."""
 
966
 
 
967
    def __init__(self):
 
968
        pass
 
969
 
 
970
    @args('--version', dest='version', metavar='<version>',
 
971
            help='Database version')
 
972
    def sync(self, version=None):
 
973
        """Sync the database up to the most recent version."""
 
974
        return migration.db_sync(version)
 
975
 
 
976
    def version(self):
 
977
        """Print the current database version."""
 
978
        print migration.db_version()
 
979
 
 
980
 
 
981
class VersionCommands(object):
 
982
    """Class for exposing the codebase version."""
 
983
 
 
984
    def __init__(self):
 
985
        pass
 
986
 
 
987
    def list(self):
 
988
        print _("%s (%s)") %\
 
989
                (version.version_string(), version.version_string_with_vcs())
 
990
 
 
991
    def __call__(self):
 
992
        self.list()
 
993
 
 
994
 
 
995
class VolumeCommands(object):
 
996
    """Methods for dealing with a cloud in an odd state"""
 
997
 
 
998
    @args('--volume', dest='volume_id', metavar='<volume id>',
 
999
            help='Volume ID')
 
1000
    def delete(self, volume_id):
 
1001
        """Delete a volume, bypassing the check that it
 
1002
        must be available."""
 
1003
        ctxt = context.get_admin_context()
 
1004
        volume = db.volume_get(ctxt, param2id(volume_id))
 
1005
        host = volume['host']
 
1006
 
 
1007
        if not host:
 
1008
            print "Volume not yet assigned to host."
 
1009
            print "Deleting volume from database and skipping rpc."
 
1010
            db.volume_destroy(ctxt, param2id(volume_id))
 
1011
            return
 
1012
 
 
1013
        if volume['status'] == 'in-use':
 
1014
            print "Volume is in-use."
 
1015
            print "Detach volume from instance and then try again."
 
1016
            return
 
1017
 
 
1018
        rpc.cast(ctxt,
 
1019
                 db.queue_get_for(ctxt, FLAGS.volume_topic, host),
 
1020
                 {"method": "delete_volume",
 
1021
                  "args": {"volume_id": volume['id']}})
 
1022
 
 
1023
    @args('--volume', dest='volume_id', metavar='<volume id>',
 
1024
            help='Volume ID')
 
1025
    def reattach(self, volume_id):
 
1026
        """Re-attach a volume that has previously been attached
 
1027
        to an instance.  Typically called after a compute host
 
1028
        has been rebooted."""
 
1029
        ctxt = context.get_admin_context()
 
1030
        volume = db.volume_get(ctxt, param2id(volume_id))
 
1031
        if not volume['instance_id']:
 
1032
            print "volume is not attached to an instance"
 
1033
            return
 
1034
        instance = db.instance_get(ctxt, volume['instance_id'])
 
1035
        host = instance['host']
 
1036
        rpc.cast(ctxt,
 
1037
                 db.queue_get_for(ctxt, FLAGS.compute_topic, host),
 
1038
                 {"method": "attach_volume",
 
1039
                  "args": {"instance_id": instance['id'],
 
1040
                           "volume_id": volume['id'],
 
1041
                           "mountpoint": volume['mountpoint']}})
 
1042
 
 
1043
 
 
1044
class InstanceTypeCommands(object):
 
1045
    """Class for managing instance types / flavors."""
 
1046
 
 
1047
    def _print_instance_types(self, name, val):
 
1048
        deleted = ('', ', inactive')[val["deleted"] == 1]
 
1049
        print ("%s: Memory: %sMB, VCPUS: %s, Storage: %sGB, FlavorID: %s, "
 
1050
            "Swap: %sGB, RXTX Quota: %sGB, RXTX Cap: %sMB%s") % (
 
1051
            name, val["memory_mb"], val["vcpus"], val["local_gb"],
 
1052
            val["flavorid"], val["swap"], val["rxtx_quota"],
 
1053
            val["rxtx_cap"], deleted)
 
1054
 
 
1055
    @args('--name', dest='name', metavar='<name>',
 
1056
            help='Name of instance type/flavor')
 
1057
    @args('--memory', dest='memory', metavar='<memory size>',
 
1058
            help='Memory size')
 
1059
    @args('--cpu', dest='vcpus', metavar='<num cores>', help='Number cpus')
 
1060
    @args('--local_gb', dest='local_gb', metavar='<local_gb>',
 
1061
            help='local_gb')
 
1062
    @args('--flavor', dest='flavorid', metavar='<flavor  id>',
 
1063
            help='Flavor ID')
 
1064
    @args('--swap', dest='swap', metavar='<swap>', help='Swap')
 
1065
    @args('--rxtx_quota', dest='rxtx_quota', metavar='<rxtx_quota>',
 
1066
            help='rxtx_quota')
 
1067
    @args('--rxtx_cap', dest='rxtx_cap', metavar='<rxtx_cap>',
 
1068
            help='rxtx_cap')
 
1069
    def create(self, name, memory, vcpus, local_gb, flavorid,
 
1070
               swap=0, rxtx_quota=0, rxtx_cap=0):
 
1071
        """Creates instance types / flavors"""
 
1072
        try:
 
1073
            instance_types.create(name, memory, vcpus, local_gb,
 
1074
                                  flavorid, swap, rxtx_quota, rxtx_cap)
 
1075
        except exception.InvalidInput, e:
 
1076
            print "Must supply valid parameters to create instance_type"
 
1077
            print e
 
1078
            sys.exit(1)
 
1079
        except exception.ApiError, e:
 
1080
            print "\n\n"
 
1081
            print "\n%s" % e
 
1082
            print "Please ensure instance_type name and flavorid are unique."
 
1083
            print "To complete remove a instance_type, use the --purge flag:"
 
1084
            print "\n     # nova-manage instance_type delete <name> --purge\n"
 
1085
            print "Currently defined instance_type names and flavorids:"
 
1086
            self.list("--all")
 
1087
            sys.exit(2)
 
1088
        except:
 
1089
            print "Unknown error"
 
1090
            sys.exit(3)
 
1091
        else:
 
1092
            print "%s created" % name
 
1093
 
 
1094
    @args('--name', dest='name', metavar='<name>',
 
1095
            help='Name of instance type/flavor')
 
1096
    def delete(self, name, purge=None):
 
1097
        """Marks instance types / flavors as deleted"""
 
1098
        try:
 
1099
            if purge == "--purge":
 
1100
                instance_types.purge(name)
 
1101
                verb = "purged"
 
1102
            else:
 
1103
                instance_types.destroy(name)
 
1104
                verb = "deleted"
 
1105
        except exception.ApiError:
 
1106
            print "Valid instance type name is required"
 
1107
            sys.exit(1)
 
1108
        except exception.DBError, e:
 
1109
            print "DB Error: %s" % e
 
1110
            sys.exit(2)
 
1111
        except:
 
1112
            sys.exit(3)
 
1113
        else:
 
1114
            print "%s %s" % (name, verb)
 
1115
 
 
1116
    @args('--name', dest='name', metavar='<name>',
 
1117
            help='Name of instance type/flavor')
 
1118
    def list(self, name=None):
 
1119
        """Lists all active or specific instance types / flavors"""
 
1120
        try:
 
1121
            if name is None:
 
1122
                inst_types = instance_types.get_all_types()
 
1123
            elif name == "--all":
 
1124
                inst_types = instance_types.get_all_types(True)
 
1125
            else:
 
1126
                inst_types = instance_types.get_instance_type_by_name(name)
 
1127
        except exception.DBError, e:
 
1128
            _db_error(e)
 
1129
        if isinstance(inst_types.values()[0], dict):
 
1130
            for k, v in inst_types.iteritems():
 
1131
                self._print_instance_types(k, v)
 
1132
        else:
 
1133
            self._print_instance_types(name, inst_types)
 
1134
 
 
1135
 
 
1136
class ImageCommands(object):
 
1137
    """Methods for dealing with a cloud in an odd state"""
 
1138
 
 
1139
    def __init__(self, *args, **kwargs):
 
1140
        self.image_service = image.get_default_image_service()
 
1141
 
 
1142
    def _register(self, container_format, disk_format,
 
1143
                  path, owner, name=None, is_public='T',
 
1144
                  architecture='x86_64', kernel_id=None, ramdisk_id=None):
 
1145
        meta = {'is_public': (is_public == 'T'),
 
1146
                'name': name,
 
1147
                'container_format': container_format,
 
1148
                'disk_format': disk_format,
 
1149
                'properties': {'image_state': 'available',
 
1150
                               'project_id': owner,
 
1151
                               'architecture': architecture,
 
1152
                               'image_location': 'local'}}
 
1153
        if kernel_id:
 
1154
            meta['properties']['kernel_id'] = int(kernel_id)
 
1155
        if ramdisk_id:
 
1156
            meta['properties']['ramdisk_id'] = int(ramdisk_id)
 
1157
        elevated = context.get_admin_context()
 
1158
        try:
 
1159
            with open(path) as ifile:
 
1160
                image = self.image_service.create(elevated, meta, ifile)
 
1161
            new = image['id']
 
1162
            print _("Image registered to %(new)s (%(new)08x).") % locals()
 
1163
            return new
 
1164
        except Exception as exc:
 
1165
            print _("Failed to register %(path)s: %(exc)s") % locals()
 
1166
 
 
1167
    @args('--image', dest='image', metavar='<image>', help='Image')
 
1168
    @args('--kernel', dest='kernel', metavar='<kernel>', help='Kernel')
 
1169
    @args('--ram', dest='ramdisk', metavar='<ramdisk>', help='RAM disk')
 
1170
    @args('--owner', dest='owner', metavar='<owner>', help='Image owner')
 
1171
    @args('--name', dest='name', metavar='<name>', help='Image name')
 
1172
    @args('--public', dest='is_public', metavar="<'T'|'F'>",
 
1173
            help='Image public or not')
 
1174
    @args('--arch', dest='architecture', metavar='<arch>',
 
1175
            help='Architecture')
 
1176
    def all_register(self, image, kernel, ramdisk, owner, name=None,
 
1177
                 is_public='T', architecture='x86_64'):
 
1178
        """Uploads an image, kernel, and ramdisk into the image_service"""
 
1179
        kernel_id = self.kernel_register(kernel, owner, None,
 
1180
                                   is_public, architecture)
 
1181
        ramdisk_id = self.ramdisk_register(ramdisk, owner, None,
 
1182
                                    is_public, architecture)
 
1183
        self.image_register(image, owner, name, is_public,
 
1184
                            architecture, 'ami', 'ami',
 
1185
                            kernel_id, ramdisk_id)
 
1186
 
 
1187
    @args('--path', dest='path', metavar='<path>', help='Image path')
 
1188
    @args('--owner', dest='owner', metavar='<owner>', help='Image owner')
 
1189
    @args('--name', dest='name', metavar='<name>', help='Image name')
 
1190
    @args('--public', dest='is_public', metavar="<'T'|'F'>",
 
1191
            help='Image public or not')
 
1192
    @args('--arch', dest='architecture', metavar='<arch>',
 
1193
            help='Architecture')
 
1194
    @args('--cont_format', dest='container_format',
 
1195
            metavar='<container format>',
 
1196
            help='Container format(default bare)')
 
1197
    @args('--disk_format', dest='disk_format', metavar='<disk format>',
 
1198
            help='Disk format(default: raw)')
 
1199
    @args('--kernel', dest='kernel_id', metavar='<kernel>', help='Kernel')
 
1200
    @args('--ram', dest='ramdisk_id', metavar='<ramdisk>', help='RAM disk')
 
1201
    def image_register(self, path, owner, name=None, is_public='T',
 
1202
                       architecture='x86_64', container_format='bare',
 
1203
                       disk_format='raw', kernel_id=None, ramdisk_id=None):
 
1204
        """Uploads an image into the image_service"""
 
1205
        return self._register(container_format, disk_format, path,
 
1206
                              owner, name, is_public, architecture,
 
1207
                              kernel_id, ramdisk_id)
 
1208
 
 
1209
    @args('--path', dest='path', metavar='<path>', help='Image path')
 
1210
    @args('--owner', dest='owner', metavar='<owner>', help='Image owner')
 
1211
    @args('--name', dest='name', metavar='<name>', help='Image name')
 
1212
    @args('--public', dest='is_public', metavar="<'T'|'F'>",
 
1213
            help='Image public or not')
 
1214
    @args('--arch', dest='architecture', metavar='<arch>',
 
1215
            help='Architecture')
 
1216
    def kernel_register(self, path, owner, name=None, is_public='T',
 
1217
               architecture='x86_64'):
 
1218
        """Uploads a kernel into the image_service"""
 
1219
        return self._register('aki', 'aki', path, owner, name,
 
1220
                              is_public, architecture)
 
1221
 
 
1222
    @args('--path', dest='path', metavar='<path>', help='Image path')
 
1223
    @args('--owner', dest='owner', metavar='<owner>', help='Image owner')
 
1224
    @args('--name', dest='name', metavar='<name>', help='Image name')
 
1225
    @args('--public', dest='is_public', metavar="<'T'|'F'>",
 
1226
            help='Image public or not')
 
1227
    @args('--arch', dest='architecture', metavar='<arch>',
 
1228
            help='Architecture')
 
1229
    def ramdisk_register(self, path, owner, name=None, is_public='T',
 
1230
                architecture='x86_64'):
 
1231
        """Uploads a ramdisk into the image_service"""
 
1232
        return self._register('ari', 'ari', path, owner, name,
 
1233
                              is_public, architecture)
 
1234
 
 
1235
    def _lookup(self, old_image_id):
 
1236
        try:
 
1237
            internal_id = ec2utils.ec2_id_to_id(old_image_id)
 
1238
            image = self.image_service.show(context, internal_id)
 
1239
        except (exception.InvalidEc2Id, exception.ImageNotFound):
 
1240
            image = self.image_service.show_by_name(context, old_image_id)
 
1241
        return image['id']
 
1242
 
 
1243
    def _old_to_new(self, old):
 
1244
        mapping = {'machine': 'ami',
 
1245
                   'kernel': 'aki',
 
1246
                   'ramdisk': 'ari'}
 
1247
        container_format = mapping[old['type']]
 
1248
        disk_format = container_format
 
1249
        if container_format == 'ami' and not old.get('kernelId'):
 
1250
            container_format = 'bare'
 
1251
            disk_format = 'raw'
 
1252
        new = {'disk_format': disk_format,
 
1253
               'container_format': container_format,
 
1254
               'is_public': old['isPublic'],
 
1255
               'name': old['imageId'],
 
1256
               'properties': {'image_state': old['imageState'],
 
1257
                              'project_id': old['imageOwnerId'],
 
1258
                              'architecture': old['architecture'],
 
1259
                              'image_location': old['imageLocation']}}
 
1260
        if old.get('kernelId'):
 
1261
            new['properties']['kernel_id'] = self._lookup(old['kernelId'])
 
1262
        if old.get('ramdiskId'):
 
1263
            new['properties']['ramdisk_id'] = self._lookup(old['ramdiskId'])
 
1264
        return new
 
1265
 
 
1266
    def _convert_images(self, images):
 
1267
        elevated = context.get_admin_context()
 
1268
        for image_path, image_metadata in images.iteritems():
 
1269
            meta = self._old_to_new(image_metadata)
 
1270
            old = meta['name']
 
1271
            try:
 
1272
                with open(image_path) as ifile:
 
1273
                    image = self.image_service.create(elevated, meta, ifile)
 
1274
                new = image['id']
 
1275
                print _("Image %(old)s converted to " \
 
1276
                        "%(new)s (%(new)08x).") % locals()
 
1277
            except Exception as exc:
 
1278
                print _("Failed to convert %(old)s: %(exc)s") % locals()
 
1279
 
 
1280
    @args('--dir', dest='directory', metavar='<path>',
 
1281
            help='Images directory')
 
1282
    def convert(self, directory):
 
1283
        """Uploads old objectstore images in directory to new service"""
 
1284
        machine_images = {}
 
1285
        other_images = {}
 
1286
        directory = os.path.abspath(directory)
 
1287
        for fn in glob.glob("%s/*/info.json" % directory):
 
1288
            try:
 
1289
                image_path = os.path.join(fn.rpartition('/')[0], 'image')
 
1290
                with open(fn) as metadata_file:
 
1291
                    image_metadata = json.load(metadata_file)
 
1292
                if image_metadata['type'] == 'machine':
 
1293
                    machine_images[image_path] = image_metadata
 
1294
                else:
 
1295
                    other_images[image_path] = image_metadata
 
1296
            except Exception:
 
1297
                print _("Failed to load %(fn)s.") % locals()
 
1298
        # NOTE(vish): do kernels and ramdisks first so images
 
1299
        self._convert_images(other_images)
 
1300
        self._convert_images(machine_images)
 
1301
 
 
1302
 
 
1303
class AgentBuildCommands(object):
 
1304
    """Class for managing agent builds."""
 
1305
 
 
1306
    def create(self, os, architecture, version, url, md5hash,
 
1307
                hypervisor='xen'):
 
1308
        """Creates a new agent build."""
 
1309
        ctxt = context.get_admin_context()
 
1310
        agent_build = db.agent_build_create(ctxt,
 
1311
                                            {'hypervisor': hypervisor,
 
1312
                                             'os': os,
 
1313
                                             'architecture': architecture,
 
1314
                                             'version': version,
 
1315
                                             'url': url,
 
1316
                                             'md5hash': md5hash})
 
1317
 
 
1318
    def delete(self, os, architecture, hypervisor='xen'):
 
1319
        """Deletes an existing agent build."""
 
1320
        ctxt = context.get_admin_context()
 
1321
        agent_build_ref = db.agent_build_get_by_triple(ctxt,
 
1322
                                  hypervisor, os, architecture)
 
1323
        db.agent_build_destroy(ctxt, agent_build_ref['id'])
 
1324
 
 
1325
    def list(self, hypervisor=None):
 
1326
        """Lists all agent builds.
 
1327
        arguments: <none>"""
 
1328
        fmt = "%-10s  %-8s  %12s  %s"
 
1329
        ctxt = context.get_admin_context()
 
1330
        by_hypervisor = {}
 
1331
        for agent_build in db.agent_build_get_all(ctxt):
 
1332
            buildlist = by_hypervisor.get(agent_build.hypervisor)
 
1333
            if not buildlist:
 
1334
                buildlist = by_hypervisor[agent_build.hypervisor] = []
 
1335
 
 
1336
            buildlist.append(agent_build)
 
1337
 
 
1338
        for key, buildlist in by_hypervisor.iteritems():
 
1339
            if hypervisor and key != hypervisor:
 
1340
                continue
 
1341
 
 
1342
            print "Hypervisor: %s" % key
 
1343
            print fmt % ('-' * 10, '-' * 8, '-' * 12, '-' * 32)
 
1344
            for agent_build in buildlist:
 
1345
                print fmt % (agent_build.os, agent_build.architecture,
 
1346
                             agent_build.version, agent_build.md5hash)
 
1347
                print '    %s' % agent_build.url
 
1348
 
 
1349
            print
 
1350
 
 
1351
    def modify(self, os, architecture, version, url, md5hash,
 
1352
               hypervisor='xen'):
 
1353
        """Update an existing agent build."""
 
1354
        ctxt = context.get_admin_context()
 
1355
        agent_build_ref = db.agent_build_get_by_triple(ctxt,
 
1356
                                  hypervisor, os, architecture)
 
1357
        db.agent_build_update(ctxt, agent_build_ref['id'],
 
1358
                              {'version': version,
 
1359
                               'url': url,
 
1360
                               'md5hash': md5hash})
 
1361
 
 
1362
 
 
1363
class ConfigCommands(object):
 
1364
    """Class for exposing the flags defined by flag_file(s)."""
 
1365
 
 
1366
    def __init__(self):
 
1367
        pass
 
1368
 
 
1369
    def list(self):
 
1370
        print FLAGS.FlagsIntoString()
213
1371
 
214
1372
 
215
1373
CATEGORIES = [
216
 
    ('user', UserCommands),
 
1374
    ('account', AccountCommands),
 
1375
    ('agent', AgentBuildCommands),
 
1376
    ('config', ConfigCommands),
 
1377
    ('db', DbCommands),
 
1378
    ('fixed', FixedIpCommands),
 
1379
    ('flavor', InstanceTypeCommands),
 
1380
    ('floating', FloatingIpCommands),
 
1381
    ('host', HostCommands),
 
1382
    ('instance_type', InstanceTypeCommands),
 
1383
    ('image', ImageCommands),
 
1384
    ('network', NetworkCommands),
217
1385
    ('project', ProjectCommands),
218
1386
    ('role', RoleCommands),
219
 
    ('vpn', VpnCommands),
220
 
]
 
1387
    ('service', ServiceCommands),
 
1388
    ('shell', ShellCommands),
 
1389
    ('user', UserCommands),
 
1390
    ('version', VersionCommands),
 
1391
    ('vm', VmCommands),
 
1392
    ('volume', VolumeCommands),
 
1393
    ('vpn', VpnCommands)]
221
1394
 
222
1395
 
223
1396
def lazy_match(name, key_value_tuples):
253
1426
 
254
1427
def main():
255
1428
    """Parse options and call the appropriate class/method."""
256
 
    utils.default_flagfile('/etc/nova/nova-manage.conf')
 
1429
    utils.default_flagfile()
257
1430
    argv = FLAGS(sys.argv)
 
1431
    logging.setup()
 
1432
 
258
1433
    script_name = argv.pop(0)
259
1434
    if len(argv) < 1:
 
1435
        print _("\nOpenStack Nova version: %s (%s)\n") %\
 
1436
                (version.version_string(), version.version_string_with_vcs())
260
1437
        print script_name + " category action [<args>]"
261
 
        print "Available categories:"
262
 
        for k, _ in CATEGORIES:
 
1438
        print _("Available categories:")
 
1439
        for k, _v in CATEGORIES:
263
1440
            print "\t%s" % k
264
1441
        sys.exit(2)
265
1442
    category = argv.pop(0)
269
1446
    command_object = fn()
270
1447
    actions = methods_of(command_object)
271
1448
    if len(argv) < 1:
272
 
        print script_name + " category action [<args>]"
273
 
        print "Available actions for %s category:" % category
274
 
        for k, _v in actions:
275
 
            print "\t%s" % k
276
 
        sys.exit(2)
277
 
    action = argv.pop(0)
278
 
    matches = lazy_match(action, actions)
279
 
    action, fn = matches[0]
 
1449
        if hasattr(command_object, '__call__'):
 
1450
            action = ''
 
1451
            fn = command_object.__call__
 
1452
        else:
 
1453
            print script_name + " category action [<args>]"
 
1454
            print _("Available actions for %s category:") % category
 
1455
            for k, _v in actions:
 
1456
                print "\t%s" % k
 
1457
            sys.exit(2)
 
1458
    else:
 
1459
        action = argv.pop(0)
 
1460
        matches = lazy_match(action, actions)
 
1461
        action, fn = matches[0]
 
1462
 
 
1463
    # For not decorated methods
 
1464
    options = getattr(fn, 'options', [])
 
1465
 
 
1466
    usage = "%%prog %s %s <args> [options]" % (category, action)
 
1467
    parser = OptionParser(usage=usage)
 
1468
    for ar, kw in options:
 
1469
        parser.add_option(*ar, **kw)
 
1470
    (opts, fn_args) = parser.parse_args(argv)
 
1471
    fn_kwargs = vars(opts)
 
1472
 
 
1473
    for k, v in fn_kwargs.items():
 
1474
        if v is None:
 
1475
            del fn_kwargs[k]
 
1476
 
280
1477
    # call the action with the remaining arguments
281
1478
    try:
282
 
        fn(*argv)
 
1479
        fn(*fn_args, **fn_kwargs)
283
1480
        sys.exit(0)
284
1481
    except TypeError:
285
 
        print "Wrong number of arguments supplied"
286
 
        print "%s %s: %s" % (category, action, fn.__doc__)
287
 
        sys.exit(2)
 
1482
        print _("Possible wrong number of arguments supplied")
 
1483
        print fn.__doc__
 
1484
        parser.print_help()
 
1485
        raise
 
1486
    except Exception:
 
1487
        print _("Command failed, please check log for more info")
 
1488
        raise
288
1489
 
289
1490
if __name__ == '__main__':
290
1491
    main()