Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / logic / management / commands / server-import.py @ c83d0ada

History | View | Annotate | Download (9.5 kB)

1
# Copyright 2012 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
from optparse import make_option
35

    
36
from django.core.management.base import BaseCommand, CommandError
37
from synnefo.management import common
38

    
39
from synnefo.db.models import VirtualMachine, Network, Flavor
40
from synnefo.logic.utils import id_from_network_name, id_from_instance_name
41
from synnefo.logic.backend import wait_for_job, connect_to_network
42
from synnefo.logic.rapi import GanetiApiError
43
from synnefo.logic import servers
44
from synnefo import quotas
45

    
46

    
47
HELP_MSG = """
48

49
Import an existing Ganeti instance into Synnefo, with the attributes specified
50
by the command line options. In order to be imported, the instance will be
51
turned off, renamed and then turned on again.
52

53
Importing an instance will fail, if the instance has NICs that are connected to
54
a network not belonging to Synnefo. You can either manually modify the instance
55
or use --new-nics option, that will remove all old NICs, and create a new one
56
connected to a public network of Synnefo.
57

58
"""
59

    
60

    
61
class Command(BaseCommand):
62
    help = "Import an existing Ganeti VM into Synnefo." + HELP_MSG
63
    args = "<ganeti_instance_name>"
64
    output_transaction = True
65

    
66
    option_list = BaseCommand.option_list + (
67
        make_option(
68
            "--backend-id",
69
            dest="backend_id",
70
            help="Unique identifier of the Ganeti backend that"
71
                 " hosts the VM. Use snf-manage backend-list to"
72
                 " find out available backends."),
73
        make_option(
74
            "--user-id",
75
            dest="user_id",
76
            help="Unique identifier of the owner of the server"),
77
        make_option(
78
            "--image-id",
79
            dest="image_id",
80
            default=None,
81
            help="Unique identifier of the image."
82
                 " Use snf-manage image-list to find out"
83
                 " available images."),
84
        make_option(
85
            "--flavor-id",
86
            dest="flavor_id",
87
            help="Unique identifier of the flavor"
88
                 " Use snf-manage flavor-list to find out"
89
                 " available flavors."),
90
        make_option(
91
            "--new-nics",
92
            dest='new_nics',
93
            default=False,
94
            action="store_true",
95
            help="Remove old NICs of instance, and create"
96
                 " a new NIC connected to a public network of"
97
                 " Synnefo.")
98
    )
99

    
100
    REQUIRED = ("user-id", "backend-id", "image-id", "flavor-id")
101

    
102
    def handle(self, *args, **options):
103
        if len(args) < 1:
104
            raise CommandError("Please specify a Ganeti instance")
105

    
106
        instance_name = args[0]
107

    
108
        try:
109
            id_from_instance_name(instance_name)
110
            raise CommandError("%s is already a synnefo instance")
111
        except:
112
            pass
113

    
114
        user_id = options['user_id']
115
        backend_id = options['backend_id']
116
        image_id = options['image_id']
117
        flavor_id = options['flavor_id']
118
        new_public_nic = options['new_nics']
119

    
120
        for field in self.REQUIRED:
121
            if not locals()[field.replace("-", "_")]:
122
                raise CommandError(field + " is mandatory")
123

    
124
        import_server(instance_name, backend_id, flavor_id, image_id, user_id,
125
                      new_public_nic, self.stderr)
126

    
127

    
128
def import_server(instance_name, backend_id, flavor_id, image_id,
129
                  user_id, new_public_nic, stream):
130
    flavor = common.get_flavor(flavor_id)
131
    backend = common.get_backend(backend_id)
132

    
133
    backend_client = backend.get_client()
134

    
135
    try:
136
        instance = backend_client.GetInstance(instance_name)
137
    except GanetiApiError as e:
138
        if e.code == 404:
139
            raise CommandError("Instance %s does not exist in backend %s"
140
                               % (instance_name, backend))
141
        else:
142
            raise CommandError("Unexpected error" + str(e))
143

    
144
    if not new_public_nic:
145
        check_instance_nics(instance, stream)
146

    
147
    shutdown_instance(instance, backend_client, stream=stream)
148

    
149
    # Create the VM in DB
150
    stream.write("Creating VM entry in DB\n")
151
    vm = VirtualMachine.objects.create(name=instance_name,
152
                                       backend=backend,
153
                                       userid=user_id,
154
                                       imageid=image_id,
155
                                       flavor=flavor)
156

    
157
    quotas.issue_and_accept_commission(vm)
158

    
159
    if new_public_nic:
160
        remove_instance_nics(instance, backend_client,
161
                             stream=stream)
162

    
163
    # Rename instance
164
    rename_instance(instance_name, vm.backend_vm_id, backend_client,
165
                    stream)
166

    
167
    if new_public_nic:
168
        ports = servers.create_instance_ports(user_id)
169
        stream.write("Adding new NICs to server")
170
        [servers.associate_port_with_machine(port, vm)
171
         for port in ports]
172
        [connect_to_network(vm, port) for port in ports]
173

    
174
    # Startup instance
175
    startup_instance(vm.backend_vm_id, backend_client, stream=stream)
176

    
177
    backend.put_client(backend_client)
178
    return
179

    
180

    
181
def flavor_from_instance(instance, flavor, stream):
182
    beparams = instance['beparams']
183
    disk_sizes = instance['disk.sizes']
184
    if len(disk_sizes) != 1:
185
        stream.write("Instance has more than one disk.\n")
186

    
187
    disk = disk_sizes[0]
188
    disk_template = instance['disk_template']
189
    cpu = beparams['vcpus']
190
    ram = beparams['memory']
191

    
192
    return Flavor.objects.get_or_create(disk=disk, disk_template=disk_template,
193
                                        cpu=cpu, ram=ram)
194

    
195

    
196
def check_instance_nics(instance, stream):
197
    instance_name = instance['name']
198
    networks = instance['nic.networks.names']
199
    stream.write(str(networks))
200
    try:
201
        networks = map(id_from_network_name, networks)
202
    except Network.InvalidBackendIdError:
203
        raise CommandError("Instance %s has NICs that do not belong to a"
204
                           " network belonging to synnefo. Either manually"
205
                           " modify the instance NICs or specify --new-nics"
206
                           " to clear the old NICs and create a new NIC to"
207
                           " a public network of synnefo." % instance_name)
208

    
209

    
210
def remove_instance_nics(instance, backend_client, stream):
211
    instance_name = instance['name']
212
    ips = instance['nic.ips']
213
    nic_indexes = xrange(0, len(ips))
214
    op = map(lambda x: ('remove', x, {}), nic_indexes)
215
    stream.write("Removing instance nics\n")
216
    op.reverse()
217
    jobid = backend_client.ModifyInstance(instance_name, nics=op)
218
    (status, error) = wait_for_job(backend_client, jobid)
219
    if status != 'success':
220
        raise CommandError("Cannot remove instance NICs: %s" % error)
221

    
222

    
223
def add_public_nic(instance_name, nic, backend_client, stream):
224
    stream.write("Adding public NIC %s\n" % nic)
225
    jobid = backend_client.ModifyInstance(instance_name, nics=[('add', nic)])
226
    (status, error) = wait_for_job(backend_client, jobid)
227
    if status != 'success':
228
        raise CommandError("Cannot rename instance: %s" % error)
229

    
230

    
231
def shutdown_instance(instance, backend_client, stream):
232
    instance_name = instance['name']
233
    if instance['status'] != 'ADMIN_down':
234
        stream.write("Instance is not down. Shutting down instance...\n")
235
        jobid = backend_client.ShutdownInstance(instance_name)
236
        (status, error) = wait_for_job(backend_client, jobid)
237
        if status != 'success':
238
            raise CommandError("Cannot shutdown instance: %s" % error)
239

    
240

    
241
def rename_instance(old_name, new_name, backend_client, stream):
242
    stream.write("Renaming instance to %s\n" % new_name)
243

    
244
    jobid = backend_client.RenameInstance(old_name, new_name,
245
                                          ip_check=False, name_check=False)
246
    (status, error) = wait_for_job(backend_client, jobid)
247
    if status != 'success':
248
        raise CommandError("Cannot rename instance: %s" % error)
249

    
250

    
251
def startup_instance(name, backend_client, stream):
252
    stream.write("Starting instance %s\n" % name)
253
    jobid = backend_client.StartupInstance(name)
254
    (status, error) = wait_for_job(backend_client, jobid)
255
    if status != 'success':
256
        raise CommandError("Cannot rename instance: %s" % error)