Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / api / management / commands / server-import.py @ d443e1dd

History | View | Annotate | Download (9.6 kB)

1 b344501e Christos Stavrakakis
# Copyright 2012 GRNET S.A. All rights reserved.
2 b344501e Christos Stavrakakis
#
3 b344501e Christos Stavrakakis
# Redistribution and use in source and binary forms, with or
4 b344501e Christos Stavrakakis
# without modification, are permitted provided that the following
5 b344501e Christos Stavrakakis
# conditions are met:
6 b344501e Christos Stavrakakis
#
7 b344501e Christos Stavrakakis
#   1. Redistributions of source code must retain the above
8 b344501e Christos Stavrakakis
#      copyright notice, this list of conditions and the following
9 b344501e Christos Stavrakakis
#      disclaimer.
10 b344501e Christos Stavrakakis
#
11 b344501e Christos Stavrakakis
#   2. Redistributions in binary form must reproduce the above
12 b344501e Christos Stavrakakis
#      copyright notice, this list of conditions and the following
13 b344501e Christos Stavrakakis
#      disclaimer in the documentation and/or other materials
14 b344501e Christos Stavrakakis
#      provided with the distribution.
15 b344501e Christos Stavrakakis
#
16 b344501e Christos Stavrakakis
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 b344501e Christos Stavrakakis
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 b344501e Christos Stavrakakis
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 b344501e Christos Stavrakakis
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 b344501e Christos Stavrakakis
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 b344501e Christos Stavrakakis
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 b344501e Christos Stavrakakis
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 b344501e Christos Stavrakakis
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 b344501e Christos Stavrakakis
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 b344501e Christos Stavrakakis
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 b344501e Christos Stavrakakis
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 b344501e Christos Stavrakakis
# POSSIBILITY OF SUCH DAMAGE.
28 b344501e Christos Stavrakakis
#
29 b344501e Christos Stavrakakis
# The views and conclusions contained in the software and
30 b344501e Christos Stavrakakis
# documentation are those of the authors and should not be
31 b344501e Christos Stavrakakis
# interpreted as representing official policies, either expressed
32 b344501e Christos Stavrakakis
# or implied, of GRNET S.A.
33 b344501e Christos Stavrakakis
34 b344501e Christos Stavrakakis
from optparse import make_option
35 b344501e Christos Stavrakakis
36 b344501e Christos Stavrakakis
from django.core.management.base import BaseCommand, CommandError
37 b344501e Christos Stavrakakis
from synnefo.management import common
38 b344501e Christos Stavrakakis
39 b344501e Christos Stavrakakis
from synnefo.db.models import VirtualMachine, Network, Flavor
40 b344501e Christos Stavrakakis
from synnefo.logic.utils import id_from_network_name, id_from_instance_name
41 b344501e Christos Stavrakakis
from synnefo.logic.backend import wait_for_job
42 b344501e Christos Stavrakakis
from synnefo.logic.rapi import GanetiApiError
43 b344501e Christos Stavrakakis
from synnefo.api.util import allocate_public_address
44 b344501e Christos Stavrakakis
45 b344501e Christos Stavrakakis
import sys
46 b344501e Christos Stavrakakis
47 b344501e Christos Stavrakakis
48 b344501e Christos Stavrakakis
HELP_MSG = """
49 b344501e Christos Stavrakakis

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

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

59 b344501e Christos Stavrakakis
"""
60 b344501e Christos Stavrakakis
61 b344501e Christos Stavrakakis
62 b344501e Christos Stavrakakis
class Command(BaseCommand):
63 b344501e Christos Stavrakakis
    help = "Import an existing Ganeti VM into Synnefo." + HELP_MSG
64 b344501e Christos Stavrakakis
    args = "<ganeti_instance_name>"
65 b344501e Christos Stavrakakis
    output_transaction = True
66 b344501e Christos Stavrakakis
67 b344501e Christos Stavrakakis
    option_list = BaseCommand.option_list + (
68 b344501e Christos Stavrakakis
            make_option("--backend-id", dest="backend_id",
69 b344501e Christos Stavrakakis
                        help="Unique identifier of the Ganeti backend that"
70 b344501e Christos Stavrakakis
                             " hosts the VM. Use snf-manage backend-list to"
71 b344501e Christos Stavrakakis
                             " find out available backends."),
72 b344501e Christos Stavrakakis
            make_option("--user-id", dest="user_id",
73 b344501e Christos Stavrakakis
                        help="Unique identifier of the owner of the server"),
74 b344501e Christos Stavrakakis
            make_option("--image-id", dest="image_id",
75 b344501e Christos Stavrakakis
                        default=None,
76 b344501e Christos Stavrakakis
                        help="Unique identifier of the image."
77 b344501e Christos Stavrakakis
                             " Use snf-manage image-list to find out"
78 b344501e Christos Stavrakakis
                             " available images."),
79 b344501e Christos Stavrakakis
            make_option("--flavor-id", dest="flavor_id",
80 b344501e Christos Stavrakakis
                        help="Unique identifier of the flavor"
81 b344501e Christos Stavrakakis
                             " Use snf-manage flavor-list to find out"
82 b344501e Christos Stavrakakis
                             " available flavors."),
83 b344501e Christos Stavrakakis
            make_option("--new-nics", dest='new_nics',
84 b344501e Christos Stavrakakis
                        default=False,
85 b344501e Christos Stavrakakis
                        action="store_true",
86 b344501e Christos Stavrakakis
                        help="Remove old NICs of instance, and create"
87 b344501e Christos Stavrakakis
                             " a new NIC connected to a public network of"
88 b344501e Christos Stavrakakis
                             " Synnefo.")
89 b344501e Christos Stavrakakis
        )
90 b344501e Christos Stavrakakis
91 b344501e Christos Stavrakakis
    REQUIRED = ("user-id", "backend-id", "image-id", "flavor-id")
92 b344501e Christos Stavrakakis
93 b344501e Christos Stavrakakis
    def handle(self, *args, **options):
94 b344501e Christos Stavrakakis
        if len(args) < 1:
95 b344501e Christos Stavrakakis
            raise CommandError("Please specify a Ganeti instance")
96 b344501e Christos Stavrakakis
97 b344501e Christos Stavrakakis
        instance_name = args[0]
98 b344501e Christos Stavrakakis
99 b344501e Christos Stavrakakis
        try:
100 b344501e Christos Stavrakakis
            id_from_instance_name(instance_name)
101 b344501e Christos Stavrakakis
            raise CommandError("%s is already a synnefo instance")
102 b344501e Christos Stavrakakis
        except:
103 b344501e Christos Stavrakakis
            pass
104 b344501e Christos Stavrakakis
105 b344501e Christos Stavrakakis
        user_id = options['user_id']
106 b344501e Christos Stavrakakis
        backend_id = options['backend_id']
107 b344501e Christos Stavrakakis
        image_id = options['image_id']
108 b344501e Christos Stavrakakis
        flavor_id = options['flavor_id']
109 b344501e Christos Stavrakakis
        new_public_nic = options['new_nics']
110 b344501e Christos Stavrakakis
111 b344501e Christos Stavrakakis
        for field in self.REQUIRED:
112 b344501e Christos Stavrakakis
            if not locals()[field.replace("-", "_")]:
113 b344501e Christos Stavrakakis
                raise CommandError(field + " is mandatory")
114 b344501e Christos Stavrakakis
115 b344501e Christos Stavrakakis
        import_server(instance_name, backend_id, flavor_id, image_id, user_id,
116 b344501e Christos Stavrakakis
                      new_public_nic, self.stdout)
117 b344501e Christos Stavrakakis
118 b344501e Christos Stavrakakis
119 b344501e Christos Stavrakakis
def import_server(instance_name, backend_id, flavor_id, image_id, user_id,
120 b344501e Christos Stavrakakis
                  new_public_nic, stream=sys.stdout):
121 b344501e Christos Stavrakakis
    flavor = common.get_flavor(flavor_id)
122 b344501e Christos Stavrakakis
    backend = common.get_backend(backend_id)
123 b344501e Christos Stavrakakis
124 b344501e Christos Stavrakakis
    backend_client = backend.get_client()
125 b344501e Christos Stavrakakis
126 b344501e Christos Stavrakakis
    try:
127 b344501e Christos Stavrakakis
        instance = backend_client.GetInstance(instance_name)
128 b344501e Christos Stavrakakis
    except GanetiApiError as e:
129 b344501e Christos Stavrakakis
        if e.code == 404:
130 b344501e Christos Stavrakakis
            raise CommandError("Instance %s does not exist in backend %s"\
131 b344501e Christos Stavrakakis
                              % (instance_name, backend))
132 b344501e Christos Stavrakakis
        else:
133 b344501e Christos Stavrakakis
            raise CommandError("Unexpected error" + str(e))
134 b344501e Christos Stavrakakis
135 b344501e Christos Stavrakakis
    if new_public_nic:
136 b344501e Christos Stavrakakis
        remove_instance_nics(instance, backend_client,
137 b344501e Christos Stavrakakis
                             stream=stream)
138 b344501e Christos Stavrakakis
        (network, address) = allocate_public_address(backend)
139 b344501e Christos Stavrakakis
        if address is None:
140 b344501e Christos Stavrakakis
            raise CommandError("Can not allocate a public address."\
141 b344501e Christos Stavrakakis
                               " No available public network.")
142 b344501e Christos Stavrakakis
        nic = {'ip': address, 'network': network.backend_id}
143 b344501e Christos Stavrakakis
        add_public_nic(instance_name, nic, backend_client,
144 b344501e Christos Stavrakakis
                       stream=stream)
145 b344501e Christos Stavrakakis
    else:
146 b344501e Christos Stavrakakis
        check_instance_nics(instance)
147 b344501e Christos Stavrakakis
148 b344501e Christos Stavrakakis
    shutdown_instance(instance, backend_client, stream=stream)
149 b344501e Christos Stavrakakis
150 b344501e Christos Stavrakakis
    # Create the VM in DB
151 b344501e Christos Stavrakakis
    stream.write("Creating VM entry in DB\n")
152 b344501e Christos Stavrakakis
    vm = VirtualMachine.objects.create(name=instance_name,
153 b344501e Christos Stavrakakis
                                       backend=backend,
154 b344501e Christos Stavrakakis
                                       userid=user_id,
155 b344501e Christos Stavrakakis
                                       imageid=image_id,
156 b344501e Christos Stavrakakis
                                       flavor=flavor)
157 b344501e Christos Stavrakakis
158 b344501e Christos Stavrakakis
    # Rename instance
159 b344501e Christos Stavrakakis
    rename_instance(instance_name, vm.backend_vm_id, backend_client,
160 b344501e Christos Stavrakakis
                    stream)
161 b344501e Christos Stavrakakis
    # Startup instance
162 b344501e Christos Stavrakakis
    startup_instance(vm.backend_vm_id, backend_client, stream=stream)
163 b344501e Christos Stavrakakis
164 b344501e Christos Stavrakakis
    backend.put_client(backend_client)
165 b344501e Christos Stavrakakis
    return
166 b344501e Christos Stavrakakis
167 b344501e Christos Stavrakakis
168 b344501e Christos Stavrakakis
def flavor_from_instance(instance, flavor, stream=sys.stdout):
169 b344501e Christos Stavrakakis
    beparams = instance['beparams']
170 b344501e Christos Stavrakakis
    disk_sizes = instance['disk.sizes']
171 b344501e Christos Stavrakakis
    if len(disk_sizes) != 1:
172 b344501e Christos Stavrakakis
        stream.write("Instance has more than one disk.\n")
173 b344501e Christos Stavrakakis
174 b344501e Christos Stavrakakis
    disk = disk_sizes[0]
175 b344501e Christos Stavrakakis
    disk_template = instance['disk_template']
176 b344501e Christos Stavrakakis
    cpu = beparams['vcpus']
177 b344501e Christos Stavrakakis
    ram = beparams['memory']
178 b344501e Christos Stavrakakis
179 b344501e Christos Stavrakakis
    return Flavor.objects.get_or_create(disk=disk, disk_template=disk_template,
180 b344501e Christos Stavrakakis
                                        cpu=cpu, ram=ram)
181 b344501e Christos Stavrakakis
182 b344501e Christos Stavrakakis
183 b344501e Christos Stavrakakis
def check_instance_nics(instance):
184 b344501e Christos Stavrakakis
    instance_name = instance['name']
185 b344501e Christos Stavrakakis
    networks = instance['nic.networks']
186 b344501e Christos Stavrakakis
    try:
187 b344501e Christos Stavrakakis
        networks = map(id_from_network_name, networks)
188 b344501e Christos Stavrakakis
    except Network.InvalidBackendIdError:
189 b344501e Christos Stavrakakis
        raise CommandError("Instance %s has NICs that do not belong to a"
190 b344501e Christos Stavrakakis
                          " network belonging to synnefo. Either manually"
191 b344501e Christos Stavrakakis
                          " modify the instance NICs or specify --new-nics"
192 b344501e Christos Stavrakakis
                          " to clear the old NICs and create a new NIC to"
193 b344501e Christos Stavrakakis
                          " a public network of synnefo." % instance_name)
194 b344501e Christos Stavrakakis
195 b344501e Christos Stavrakakis
196 b344501e Christos Stavrakakis
def remove_instance_nics(instance, backend_client, stream=sys.stdout):
197 b344501e Christos Stavrakakis
    instance_name = instance['name']
198 b344501e Christos Stavrakakis
    ips = instance['nic.ips']
199 b344501e Christos Stavrakakis
    nic_indexes = xrange(0, len(ips))
200 b344501e Christos Stavrakakis
    op = map(lambda x: ('remove', x, {}), nic_indexes)
201 b344501e Christos Stavrakakis
    stream.write("Removing instance nics\n")
202 b344501e Christos Stavrakakis
    op.reverse()
203 b344501e Christos Stavrakakis
    jobid = backend_client.ModifyInstance(instance_name, nics=op)
204 b344501e Christos Stavrakakis
    (status, error) = wait_for_job(backend_client, jobid)
205 b344501e Christos Stavrakakis
    if status != 'success':
206 b344501e Christos Stavrakakis
        raise CommandError("Can not rename instance: %s" % error)
207 b344501e Christos Stavrakakis
208 b344501e Christos Stavrakakis
209 b344501e Christos Stavrakakis
def add_public_nic(instance_name, nic, backend_client, stream=sys.stdout):
210 b344501e Christos Stavrakakis
    stream.write("Adding public NIC %s\n" % nic)
211 b344501e Christos Stavrakakis
    jobid = backend_client.ModifyInstance(instance_name, nics=[('add', nic)])
212 b344501e Christos Stavrakakis
    (status, error) = wait_for_job(backend_client, jobid)
213 b344501e Christos Stavrakakis
    if status != 'success':
214 b344501e Christos Stavrakakis
        raise CommandError("Can not rename instance: %s" % error)
215 b344501e Christos Stavrakakis
216 b344501e Christos Stavrakakis
217 b344501e Christos Stavrakakis
def shutdown_instance(instance, backend_client, stream=sys.stdout):
218 b344501e Christos Stavrakakis
    instance_name = instance['name']
219 b344501e Christos Stavrakakis
    if instance['status'] != 'ADMIN_down':
220 b344501e Christos Stavrakakis
        stream.write("Instance is not down. Shutting down"
221 b344501e Christos Stavrakakis
                          " instance\n")
222 b344501e Christos Stavrakakis
        jobid = backend_client.ShutdownInstance(instance_name)
223 b344501e Christos Stavrakakis
        (status, error) = wait_for_job(backend_client, jobid)
224 b344501e Christos Stavrakakis
        if status != 'success':
225 b344501e Christos Stavrakakis
            raise CommandError("Can not shutdown instance: %s" % error)
226 b344501e Christos Stavrakakis
227 b344501e Christos Stavrakakis
228 b344501e Christos Stavrakakis
def rename_instance(old_name, new_name, backend_client, stream=sys.stdout):
229 b344501e Christos Stavrakakis
    stream.write("Renaming instance to %s\n" % new_name)
230 b344501e Christos Stavrakakis
231 b344501e Christos Stavrakakis
    jobid = backend_client.RenameInstance(old_name, new_name,
232 b344501e Christos Stavrakakis
                                         ip_check=False, name_check=False)
233 b344501e Christos Stavrakakis
    (status, error) = wait_for_job(backend_client, jobid)
234 b344501e Christos Stavrakakis
    if status != 'success':
235 b344501e Christos Stavrakakis
        raise CommandError("Can not rename instance: %s" % error)
236 b344501e Christos Stavrakakis
237 b344501e Christos Stavrakakis
238 b344501e Christos Stavrakakis
def startup_instance(name, backend_client, stream=sys.stdout):
239 b344501e Christos Stavrakakis
    stream.write("Starting instance %s\n" % name)
240 b344501e Christos Stavrakakis
    jobid = backend_client.StartupInstance(name)
241 b344501e Christos Stavrakakis
    (status, error) = wait_for_job(backend_client, jobid)
242 b344501e Christos Stavrakakis
    if status != 'success':
243 b344501e Christos Stavrakakis
        raise CommandError("Can not rename instance: %s" % error)