Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / logic / servers.py @ 3f18f035

History | View | Annotate | Download (20.4 kB)

1 41a7fae7 Christos Stavrakakis
import logging
2 41a7fae7 Christos Stavrakakis
3 41a7fae7 Christos Stavrakakis
from socket import getfqdn
4 41a7fae7 Christos Stavrakakis
from functools import wraps
5 41a7fae7 Christos Stavrakakis
from django import dispatch
6 41a7fae7 Christos Stavrakakis
from django.db import transaction
7 41a7fae7 Christos Stavrakakis
from django.utils import simplejson as json
8 41a7fae7 Christos Stavrakakis
9 41a7fae7 Christos Stavrakakis
from snf_django.lib.api import faults
10 0c09b1c0 Christos Stavrakakis
from django.conf import settings
11 41a7fae7 Christos Stavrakakis
from synnefo import quotas
12 41a7fae7 Christos Stavrakakis
from synnefo.api import util
13 41a7fae7 Christos Stavrakakis
from synnefo.logic import backend
14 41a7fae7 Christos Stavrakakis
from synnefo.logic.backend_allocator import BackendAllocator
15 fae6e5f0 Christos Stavrakakis
from synnefo.db import pools
16 710b1c43 Christos Stavrakakis
from synnefo.db.models import (NetworkInterface, VirtualMachine,
17 fae6e5f0 Christos Stavrakakis
                               VirtualMachineMetadata, IPAddressLog)
18 41a7fae7 Christos Stavrakakis
from vncauthproxy.client import request_forwarding as request_vnc_forwarding
19 41a7fae7 Christos Stavrakakis
20 41a7fae7 Christos Stavrakakis
log = logging.getLogger(__name__)
21 41a7fae7 Christos Stavrakakis
22 41a7fae7 Christos Stavrakakis
# server creation signal
23 41a7fae7 Christos Stavrakakis
server_created = dispatch.Signal(providing_args=["created_vm_params"])
24 41a7fae7 Christos Stavrakakis
25 41a7fae7 Christos Stavrakakis
26 41a7fae7 Christos Stavrakakis
def validate_server_action(vm, action):
27 41a7fae7 Christos Stavrakakis
    if vm.deleted:
28 41a7fae7 Christos Stavrakakis
        raise faults.BadRequest("Server '%s' has been deleted." % vm.id)
29 41a7fae7 Christos Stavrakakis
30 41a7fae7 Christos Stavrakakis
    # Destroyin a server should always be permitted
31 41a7fae7 Christos Stavrakakis
    if action == "DESTROY":
32 41a7fae7 Christos Stavrakakis
        return
33 41a7fae7 Christos Stavrakakis
34 41a7fae7 Christos Stavrakakis
    # Check that there is no pending action
35 41a7fae7 Christos Stavrakakis
    pending_action = vm.task
36 41a7fae7 Christos Stavrakakis
    if pending_action:
37 41a7fae7 Christos Stavrakakis
        if pending_action == "BUILD":
38 41a7fae7 Christos Stavrakakis
            raise faults.BuildInProgress("Server '%s' is being build." % vm.id)
39 41a7fae7 Christos Stavrakakis
        raise faults.BadRequest("Can not perform '%s' action while there is a"
40 41a7fae7 Christos Stavrakakis
                                " pending '%s'." % (action, pending_action))
41 41a7fae7 Christos Stavrakakis
42 41a7fae7 Christos Stavrakakis
    # Check if action can be performed to VM's operstate
43 41a7fae7 Christos Stavrakakis
    operstate = vm.operstate
44 562bf712 Christos Stavrakakis
    if operstate == "BUILD" and action != "BUILD":
45 41a7fae7 Christos Stavrakakis
        raise faults.BuildInProgress("Server '%s' is being build." % vm.id)
46 9599e997 Christos Stavrakakis
    elif (action == "START" and operstate != "STOPPED") or\
47 9599e997 Christos Stavrakakis
         (action == "STOP" and operstate != "STARTED") or\
48 9599e997 Christos Stavrakakis
         (action == "RESIZE" and operstate != "STOPPED") or\
49 9599e997 Christos Stavrakakis
         (action in ["CONNECT", "DISCONNECT"] and operstate != "STOPPED"
50 9599e997 Christos Stavrakakis
          and not settings.GANETI_USE_HOTPLUG):
51 41a7fae7 Christos Stavrakakis
        raise faults.BadRequest("Can not perform '%s' action while server is"
52 41a7fae7 Christos Stavrakakis
                                " in '%s' state." % (action, operstate))
53 41a7fae7 Christos Stavrakakis
    return
54 41a7fae7 Christos Stavrakakis
55 41a7fae7 Christos Stavrakakis
56 41a7fae7 Christos Stavrakakis
def server_command(action):
57 41a7fae7 Christos Stavrakakis
    """Handle execution of a server action.
58 41a7fae7 Christos Stavrakakis

59 41a7fae7 Christos Stavrakakis
    Helper function to validate and execute a server action, handle quota
60 41a7fae7 Christos Stavrakakis
    commission and update the 'task' of the VM in the DB.
61 41a7fae7 Christos Stavrakakis

62 41a7fae7 Christos Stavrakakis
    1) Check if action can be performed. If it can, then there must be no
63 41a7fae7 Christos Stavrakakis
       pending task (with the exception of DESTROY).
64 41a7fae7 Christos Stavrakakis
    2) Handle previous commission if unresolved:
65 41a7fae7 Christos Stavrakakis
       * If it is not pending and it to accept, then accept
66 41a7fae7 Christos Stavrakakis
       * If it is not pending and to reject or is pending then reject it. Since
67 41a7fae7 Christos Stavrakakis
       the action can be performed only if there is no pending task, then there
68 41a7fae7 Christos Stavrakakis
       can be no pending commission. The exception is DESTROY, but in this case
69 41a7fae7 Christos Stavrakakis
       the commission can safely be rejected, and the dispatcher will generate
70 41a7fae7 Christos Stavrakakis
       the correct ones!
71 41a7fae7 Christos Stavrakakis
    3) Issue new commission and associate it with the VM. Also clear the task.
72 41a7fae7 Christos Stavrakakis
    4) Send job to ganeti
73 41a7fae7 Christos Stavrakakis
    5) Update task and commit
74 41a7fae7 Christos Stavrakakis
    """
75 41a7fae7 Christos Stavrakakis
    def decorator(func):
76 41a7fae7 Christos Stavrakakis
        @wraps(func)
77 41a7fae7 Christos Stavrakakis
        @transaction.commit_on_success
78 41a7fae7 Christos Stavrakakis
        def wrapper(vm, *args, **kwargs):
79 41a7fae7 Christos Stavrakakis
            user_id = vm.userid
80 41a7fae7 Christos Stavrakakis
            validate_server_action(vm, action)
81 c9fefba3 Christos Stavrakakis
            vm.action = action
82 41a7fae7 Christos Stavrakakis
83 5c8076b6 Christos Stavrakakis
            commission_name = "client: api, resource: %s" % vm
84 5c8076b6 Christos Stavrakakis
            quotas.handle_resource_commission(vm, action=action,
85 5c8076b6 Christos Stavrakakis
                                              commission_name=commission_name)
86 5c8076b6 Christos Stavrakakis
            vm.save()
87 41a7fae7 Christos Stavrakakis
88 562bf712 Christos Stavrakakis
            # XXX: Special case for server creation!
89 562bf712 Christos Stavrakakis
            if action == "BUILD":
90 562bf712 Christos Stavrakakis
                # Perform a commit, because the VirtualMachine must be saved to
91 562bf712 Christos Stavrakakis
                # DB before the OP_INSTANCE_CREATE job in enqueued in Ganeti.
92 562bf712 Christos Stavrakakis
                # Otherwise, messages will arrive from snf-dispatcher about
93 562bf712 Christos Stavrakakis
                # this instance, before the VM is stored in DB.
94 562bf712 Christos Stavrakakis
                transaction.commit()
95 562bf712 Christos Stavrakakis
                # After committing the locks are released. Refetch the instance
96 562bf712 Christos Stavrakakis
                # to guarantee x-lock.
97 562bf712 Christos Stavrakakis
                vm = VirtualMachine.objects.select_for_update().get(id=vm.id)
98 562bf712 Christos Stavrakakis
99 41a7fae7 Christos Stavrakakis
            # Send the job to Ganeti and get the associated jobID
100 41a7fae7 Christos Stavrakakis
            try:
101 41a7fae7 Christos Stavrakakis
                job_id = func(vm, *args, **kwargs)
102 41a7fae7 Christos Stavrakakis
            except Exception as e:
103 41a7fae7 Christos Stavrakakis
                if vm.serial is not None:
104 41a7fae7 Christos Stavrakakis
                    # Since the job never reached Ganeti, reject the commission
105 41a7fae7 Christos Stavrakakis
                    log.debug("Rejecting commission: '%s', could not perform"
106 41a7fae7 Christos Stavrakakis
                              " action '%s': %s" % (vm.serial,  action, e))
107 41a7fae7 Christos Stavrakakis
                    transaction.rollback()
108 41a7fae7 Christos Stavrakakis
                    quotas.reject_serial(vm.serial)
109 41a7fae7 Christos Stavrakakis
                    transaction.commit()
110 41a7fae7 Christos Stavrakakis
                raise
111 41a7fae7 Christos Stavrakakis
112 88fd91af Christos Stavrakakis
            if action == "BUILD" and vm.serial is not None:
113 88fd91af Christos Stavrakakis
                # XXX: Special case for server creation: we must accept the
114 88fd91af Christos Stavrakakis
                # commission because the VM has been stored in DB. Also, if
115 88fd91af Christos Stavrakakis
                # communication with Ganeti fails, the job will never reach
116 88fd91af Christos Stavrakakis
                # Ganeti, and the commission will never be resolved.
117 88fd91af Christos Stavrakakis
                quotas.accept_serial(vm.serial)
118 88fd91af Christos Stavrakakis
119 41a7fae7 Christos Stavrakakis
            log.info("user: %s, vm: %s, action: %s, job_id: %s, serial: %s",
120 41a7fae7 Christos Stavrakakis
                     user_id, vm.id, action, job_id, vm.serial)
121 41a7fae7 Christos Stavrakakis
122 41a7fae7 Christos Stavrakakis
            # store the new task in the VM
123 41a7fae7 Christos Stavrakakis
            if job_id is not None:
124 41a7fae7 Christos Stavrakakis
                vm.task = action
125 41a7fae7 Christos Stavrakakis
                vm.task_job_id = job_id
126 41a7fae7 Christos Stavrakakis
            vm.save()
127 41a7fae7 Christos Stavrakakis
128 41a7fae7 Christos Stavrakakis
            return vm
129 41a7fae7 Christos Stavrakakis
        return wrapper
130 41a7fae7 Christos Stavrakakis
    return decorator
131 41a7fae7 Christos Stavrakakis
132 41a7fae7 Christos Stavrakakis
133 562bf712 Christos Stavrakakis
@transaction.commit_on_success
134 41a7fae7 Christos Stavrakakis
def create(userid, name, password, flavor, image, metadata={},
135 99988465 Christos Stavrakakis
           personality=[], networks=None, floating_ips=None,
136 5aeb4e93 Christos Stavrakakis
           use_backend=None):
137 41a7fae7 Christos Stavrakakis
    if use_backend is None:
138 562bf712 Christos Stavrakakis
        # Allocate server to a Ganeti backend
139 562bf712 Christos Stavrakakis
        use_backend = allocate_new_server(userid, flavor)
140 41a7fae7 Christos Stavrakakis
141 99988465 Christos Stavrakakis
    if networks is None:
142 99988465 Christos Stavrakakis
        networks = []
143 6193628f Christos Stavrakakis
    if floating_ips is None:
144 6193628f Christos Stavrakakis
        floating_ips = []
145 6193628f Christos Stavrakakis
146 41a7fae7 Christos Stavrakakis
    # Fix flavor for archipelago
147 41a7fae7 Christos Stavrakakis
    disk_template, provider = util.get_flavor_provider(flavor)
148 41a7fae7 Christos Stavrakakis
    if provider:
149 41a7fae7 Christos Stavrakakis
        flavor.disk_template = disk_template
150 41a7fae7 Christos Stavrakakis
        flavor.disk_provider = provider
151 41a7fae7 Christos Stavrakakis
        flavor.disk_origin = None
152 41a7fae7 Christos Stavrakakis
        if provider == 'vlmc':
153 41a7fae7 Christos Stavrakakis
            flavor.disk_origin = image['checksum']
154 41a7fae7 Christos Stavrakakis
            image['backend_id'] = 'null'
155 41a7fae7 Christos Stavrakakis
    else:
156 41a7fae7 Christos Stavrakakis
        flavor.disk_provider = None
157 41a7fae7 Christos Stavrakakis
158 562bf712 Christos Stavrakakis
    # We must save the VM instance now, so that it gets a valid
159 562bf712 Christos Stavrakakis
    # vm.backend_vm_id.
160 562bf712 Christos Stavrakakis
    vm = VirtualMachine.objects.create(name=name,
161 562bf712 Christos Stavrakakis
                                       backend=use_backend,
162 562bf712 Christos Stavrakakis
                                       userid=userid,
163 562bf712 Christos Stavrakakis
                                       imageid=image["id"],
164 562bf712 Christos Stavrakakis
                                       flavor=flavor,
165 562bf712 Christos Stavrakakis
                                       operstate="BUILD")
166 562bf712 Christos Stavrakakis
    log.info("Created entry in DB for VM '%s'", vm)
167 562bf712 Christos Stavrakakis
168 99988465 Christos Stavrakakis
    nics = create_instance_nics(vm, userid, networks, floating_ips)
169 562bf712 Christos Stavrakakis
170 562bf712 Christos Stavrakakis
    for key, val in metadata.items():
171 562bf712 Christos Stavrakakis
        VirtualMachineMetadata.objects.create(
172 562bf712 Christos Stavrakakis
            meta_key=key,
173 562bf712 Christos Stavrakakis
            meta_value=val,
174 562bf712 Christos Stavrakakis
            vm=vm)
175 41a7fae7 Christos Stavrakakis
176 88fd91af Christos Stavrakakis
    # Create the server in Ganeti.
177 e5e62972 Giorgos Korfiatis
    vm = create_server(vm, nics, flavor, image, personality, password)
178 41a7fae7 Christos Stavrakakis
179 41a7fae7 Christos Stavrakakis
    return vm
180 41a7fae7 Christos Stavrakakis
181 41a7fae7 Christos Stavrakakis
182 562bf712 Christos Stavrakakis
@transaction.commit_on_success
183 562bf712 Christos Stavrakakis
def allocate_new_server(userid, flavor):
184 562bf712 Christos Stavrakakis
    """Allocate a new server to a Ganeti backend.
185 562bf712 Christos Stavrakakis

186 562bf712 Christos Stavrakakis
    Allocation is performed based on the owner of the server and the specified
187 562bf712 Christos Stavrakakis
    flavor. Also, backends that do not have a public IPv4 address are excluded
188 562bf712 Christos Stavrakakis
    from server allocation.
189 562bf712 Christos Stavrakakis

190 562bf712 Christos Stavrakakis
    This function runs inside a transaction, because after allocating the
191 562bf712 Christos Stavrakakis
    instance a commit must be performed in order to release all locks.
192 562bf712 Christos Stavrakakis

193 562bf712 Christos Stavrakakis
    """
194 562bf712 Christos Stavrakakis
    backend_allocator = BackendAllocator()
195 562bf712 Christos Stavrakakis
    use_backend = backend_allocator.allocate(userid, flavor)
196 562bf712 Christos Stavrakakis
    if use_backend is None:
197 562bf712 Christos Stavrakakis
        log.error("No available backend for VM with flavor %s", flavor)
198 562bf712 Christos Stavrakakis
        raise faults.ServiceUnavailable("No available backends")
199 562bf712 Christos Stavrakakis
    return use_backend
200 562bf712 Christos Stavrakakis
201 562bf712 Christos Stavrakakis
202 562bf712 Christos Stavrakakis
@server_command("BUILD")
203 562bf712 Christos Stavrakakis
def create_server(vm, nics, flavor, image, personality, password):
204 562bf712 Christos Stavrakakis
    # dispatch server created signal needed to trigger the 'vmapi', which
205 562bf712 Christos Stavrakakis
    # enriches the vm object with the 'config_url' attribute which must be
206 562bf712 Christos Stavrakakis
    # passed to the Ganeti job.
207 562bf712 Christos Stavrakakis
    server_created.send(sender=vm, created_vm_params={
208 562bf712 Christos Stavrakakis
        'img_id': image['backend_id'],
209 562bf712 Christos Stavrakakis
        'img_passwd': password,
210 562bf712 Christos Stavrakakis
        'img_format': str(image['format']),
211 562bf712 Christos Stavrakakis
        'img_personality': json.dumps(personality),
212 562bf712 Christos Stavrakakis
        'img_properties': json.dumps(image['metadata']),
213 562bf712 Christos Stavrakakis
    })
214 562bf712 Christos Stavrakakis
    # send job to Ganeti
215 88fd91af Christos Stavrakakis
    try:
216 88fd91af Christos Stavrakakis
        jobID = backend.create_instance(vm, nics, flavor, image)
217 88fd91af Christos Stavrakakis
    except:
218 88fd91af Christos Stavrakakis
        log.exception("Failed create instance '%s'", vm)
219 88fd91af Christos Stavrakakis
        jobID = None
220 88fd91af Christos Stavrakakis
        vm.operstate = "ERROR"
221 88fd91af Christos Stavrakakis
        vm.backendlogmsg = "Failed to send job to Ganeti."
222 88fd91af Christos Stavrakakis
        vm.save()
223 88fd91af Christos Stavrakakis
        vm.nics.all().update(state="ERROR")
224 88fd91af Christos Stavrakakis
225 562bf712 Christos Stavrakakis
    # At this point the job is enqueued in the Ganeti backend
226 562bf712 Christos Stavrakakis
    vm.backendjobid = jobID
227 562bf712 Christos Stavrakakis
    vm.save()
228 562bf712 Christos Stavrakakis
    log.info("User %s created VM %s, NICs %s, Backend %s, JobID %s",
229 562bf712 Christos Stavrakakis
             vm.userid, vm, nics, backend, str(jobID))
230 562bf712 Christos Stavrakakis
231 562bf712 Christos Stavrakakis
    return jobID
232 562bf712 Christos Stavrakakis
233 562bf712 Christos Stavrakakis
234 99988465 Christos Stavrakakis
def create_instance_nics(vm, userid, networks=[], floating_ips=[]):
235 cb66110b Christos Stavrakakis
    """Create NICs for VirtualMachine.
236 cb66110b Christos Stavrakakis

237 cb66110b Christos Stavrakakis
    Helper function for allocating IP addresses and creating NICs in the DB
238 cb66110b Christos Stavrakakis
    for a VirtualMachine. Created NICs are the combination of the default
239 99988465 Christos Stavrakakis
    network policy (defined by administration settings) and the networks
240 99988465 Christos Stavrakakis
    defined by the user.
241 cb66110b Christos Stavrakakis

242 cb66110b Christos Stavrakakis
    """
243 99988465 Christos Stavrakakis
    ports = []
244 cb66110b Christos Stavrakakis
    for network_id in settings.DEFAULT_INSTANCE_NETWORKS:
245 9446e7e5 Christos Stavrakakis
        if network_id == "SNF:ANY_PUBLIC":
246 710b1c43 Christos Stavrakakis
            ipaddress = util.allocate_public_ip(userid=userid,
247 710b1c43 Christos Stavrakakis
                                                backend=vm.backend)
248 99988465 Christos Stavrakakis
            port = _create_port(userid, network=ipaddress.network,
249 99988465 Christos Stavrakakis
                                use_ipaddress=ipaddress)
250 cb66110b Christos Stavrakakis
        else:
251 cb66110b Christos Stavrakakis
            try:
252 710b1c43 Christos Stavrakakis
                network = util.get_network(network_id, userid,
253 710b1c43 Christos Stavrakakis
                                           non_deleted=True)
254 710b1c43 Christos Stavrakakis
            except faults.ItemNotFound:
255 cb66110b Christos Stavrakakis
                msg = "Invalid configuration. Setting"\
256 cb66110b Christos Stavrakakis
                      " 'DEFAULT_INSTANCE_NETWORKS' contains invalid"\
257 cb66110b Christos Stavrakakis
                      " network '%s'" % network_id
258 cb66110b Christos Stavrakakis
                log.error(msg)
259 710b1c43 Christos Stavrakakis
                raise faults.InternalServerError(msg)
260 99988465 Christos Stavrakakis
            port = _create_port(userid, network)
261 99988465 Christos Stavrakakis
        ports.append(port)
262 99988465 Christos Stavrakakis
263 99988465 Christos Stavrakakis
    for floating_ip_id in floating_ips:
264 09b76b7e Christos Stavrakakis
        floating_ip = util.get_floating_ip_by_id(userid, floating_ip_id,
265 99988465 Christos Stavrakakis
                                                 for_update=True)
266 99988465 Christos Stavrakakis
        port = _create_port(userid, network=floating_ip.network,
267 99988465 Christos Stavrakakis
                            use_ipaddress=floating_ip)
268 99988465 Christos Stavrakakis
        ports.append(port)
269 99988465 Christos Stavrakakis
270 99988465 Christos Stavrakakis
    for net in networks:
271 99988465 Christos Stavrakakis
        port_id = net.get("port")
272 99988465 Christos Stavrakakis
        net_id = net.get("uuid")
273 99988465 Christos Stavrakakis
        if port_id is not None:
274 99988465 Christos Stavrakakis
            port = util.get_port(port_id, userid, for_update=True)
275 99988465 Christos Stavrakakis
            ports.append(port)
276 99988465 Christos Stavrakakis
        elif net_id is not None:
277 99988465 Christos Stavrakakis
            network = util.get_network(net_id, userid, non_deleted=True)
278 99988465 Christos Stavrakakis
            if network.public:
279 99988465 Christos Stavrakakis
                raise faults.Forbidden("Can not connect to public network")
280 99988465 Christos Stavrakakis
            address = net.get("fixed_ip")
281 99988465 Christos Stavrakakis
            port = _create_port(userid, network, address=address)
282 99988465 Christos Stavrakakis
            ports.append(port)
283 99988465 Christos Stavrakakis
        else:
284 99988465 Christos Stavrakakis
            raise faults.BadRequest("")
285 99988465 Christos Stavrakakis
286 99988465 Christos Stavrakakis
    for index, port in enumerate(ports):
287 99988465 Christos Stavrakakis
        associate_port_with_machine(port, vm)
288 99988465 Christos Stavrakakis
        port.index = index
289 99988465 Christos Stavrakakis
        port.save()
290 99988465 Christos Stavrakakis
    return ports
291 cb66110b Christos Stavrakakis
292 cb66110b Christos Stavrakakis
293 41a7fae7 Christos Stavrakakis
@server_command("DESTROY")
294 41a7fae7 Christos Stavrakakis
def destroy(vm):
295 41a7fae7 Christos Stavrakakis
    log.info("Deleting VM %s", vm)
296 41a7fae7 Christos Stavrakakis
    return backend.delete_instance(vm)
297 41a7fae7 Christos Stavrakakis
298 41a7fae7 Christos Stavrakakis
299 41a7fae7 Christos Stavrakakis
@server_command("START")
300 41a7fae7 Christos Stavrakakis
def start(vm):
301 41a7fae7 Christos Stavrakakis
    log.info("Starting VM %s", vm)
302 41a7fae7 Christos Stavrakakis
    return backend.startup_instance(vm)
303 41a7fae7 Christos Stavrakakis
304 41a7fae7 Christos Stavrakakis
305 41a7fae7 Christos Stavrakakis
@server_command("STOP")
306 41a7fae7 Christos Stavrakakis
def stop(vm):
307 41a7fae7 Christos Stavrakakis
    log.info("Stopping VM %s", vm)
308 41a7fae7 Christos Stavrakakis
    return backend.shutdown_instance(vm)
309 41a7fae7 Christos Stavrakakis
310 41a7fae7 Christos Stavrakakis
311 41a7fae7 Christos Stavrakakis
@server_command("REBOOT")
312 41a7fae7 Christos Stavrakakis
def reboot(vm, reboot_type):
313 41a7fae7 Christos Stavrakakis
    if reboot_type not in ("SOFT", "HARD"):
314 41a7fae7 Christos Stavrakakis
        raise faults.BadRequest("Malformed request. Invalid reboot"
315 41a7fae7 Christos Stavrakakis
                                " type %s" % reboot_type)
316 41a7fae7 Christos Stavrakakis
    log.info("Rebooting VM %s. Type %s", vm, reboot_type)
317 41a7fae7 Christos Stavrakakis
318 41a7fae7 Christos Stavrakakis
    return backend.reboot_instance(vm, reboot_type.lower())
319 41a7fae7 Christos Stavrakakis
320 41a7fae7 Christos Stavrakakis
321 41a7fae7 Christos Stavrakakis
@server_command("RESIZE")
322 41a7fae7 Christos Stavrakakis
def resize(vm, flavor):
323 41a7fae7 Christos Stavrakakis
    old_flavor = vm.flavor
324 41a7fae7 Christos Stavrakakis
    # User requested the same flavor
325 41a7fae7 Christos Stavrakakis
    if old_flavor.id == flavor.id:
326 41a7fae7 Christos Stavrakakis
        raise faults.BadRequest("Server '%s' flavor is already '%s'."
327 41a7fae7 Christos Stavrakakis
                                % (vm, flavor))
328 41a7fae7 Christos Stavrakakis
        return None
329 41a7fae7 Christos Stavrakakis
    # Check that resize can be performed
330 41a7fae7 Christos Stavrakakis
    if old_flavor.disk != flavor.disk:
331 41a7fae7 Christos Stavrakakis
        raise faults.BadRequest("Can not resize instance disk.")
332 41a7fae7 Christos Stavrakakis
    if old_flavor.disk_template != flavor.disk_template:
333 41a7fae7 Christos Stavrakakis
        raise faults.BadRequest("Can not change instance disk template.")
334 41a7fae7 Christos Stavrakakis
335 41a7fae7 Christos Stavrakakis
    log.info("Resizing VM from flavor '%s' to '%s", old_flavor, flavor)
336 41a7fae7 Christos Stavrakakis
    commission_info = {"cyclades.cpu": flavor.cpu - old_flavor.cpu,
337 f05a25af Christos Stavrakakis
                       "cyclades.ram": 1048576 * (flavor.ram - old_flavor.ram)}
338 41a7fae7 Christos Stavrakakis
    # Save serial to VM, since it is needed by server_command decorator
339 41a7fae7 Christos Stavrakakis
    vm.serial = quotas.issue_commission(user=vm.userid,
340 41a7fae7 Christos Stavrakakis
                                        source=quotas.DEFAULT_SOURCE,
341 9122ffab Christos Stavrakakis
                                        provisions=commission_info,
342 9122ffab Christos Stavrakakis
                                        name="resource: %s. resize" % vm)
343 41a7fae7 Christos Stavrakakis
    return backend.resize_instance(vm, vcpus=flavor.cpu, memory=flavor.ram)
344 41a7fae7 Christos Stavrakakis
345 41a7fae7 Christos Stavrakakis
346 41a7fae7 Christos Stavrakakis
@server_command("SET_FIREWALL_PROFILE")
347 d0545590 Christos Stavrakakis
def set_firewall_profile(vm, profile, nic):
348 d0545590 Christos Stavrakakis
    log.info("Setting VM %s, NIC %s, firewall %s", vm, nic, profile)
349 41a7fae7 Christos Stavrakakis
350 41a7fae7 Christos Stavrakakis
    if profile not in [x[0] for x in NetworkInterface.FIREWALL_PROFILES]:
351 41a7fae7 Christos Stavrakakis
        raise faults.BadRequest("Unsupported firewall profile")
352 d0545590 Christos Stavrakakis
    backend.set_firewall_profile(vm, profile=profile, nic=nic)
353 41a7fae7 Christos Stavrakakis
    return None
354 41a7fae7 Christos Stavrakakis
355 41a7fae7 Christos Stavrakakis
356 41a7fae7 Christos Stavrakakis
@server_command("CONNECT")
357 6b8dc47c Christos Stavrakakis
def connect(vm, network, port=None):
358 6b8dc47c Christos Stavrakakis
    if port is None:
359 fae6e5f0 Christos Stavrakakis
        port = _create_port(vm.userid, network)
360 fae6e5f0 Christos Stavrakakis
    associate_port_with_machine(port, vm)
361 41a7fae7 Christos Stavrakakis
362 fae6e5f0 Christos Stavrakakis
    log.info("Creating NIC %s with IPv4 Address %s", port, port.ipv4_address)
363 2a2b01e5 Christos Stavrakakis
364 fae6e5f0 Christos Stavrakakis
    return backend.connect_to_network(vm, port)
365 41a7fae7 Christos Stavrakakis
366 41a7fae7 Christos Stavrakakis
367 41a7fae7 Christos Stavrakakis
@server_command("DISCONNECT")
368 7c714455 Christos Stavrakakis
def disconnect(vm, nic):
369 7c714455 Christos Stavrakakis
    log.info("Removing NIC %s from VM %s", nic, vm)
370 41a7fae7 Christos Stavrakakis
    return backend.disconnect_from_network(vm, nic)
371 41a7fae7 Christos Stavrakakis
372 41a7fae7 Christos Stavrakakis
373 41a7fae7 Christos Stavrakakis
def console(vm, console_type):
374 41a7fae7 Christos Stavrakakis
    """Arrange for an OOB console of the specified type
375 41a7fae7 Christos Stavrakakis

376 41a7fae7 Christos Stavrakakis
    This method arranges for an OOB console of the specified type.
377 41a7fae7 Christos Stavrakakis
    Only consoles of type "vnc" are supported for now.
378 41a7fae7 Christos Stavrakakis

379 41a7fae7 Christos Stavrakakis
    It uses a running instance of vncauthproxy to setup proper
380 41a7fae7 Christos Stavrakakis
    VNC forwarding with a random password, then returns the necessary
381 41a7fae7 Christos Stavrakakis
    VNC connection info to the caller.
382 41a7fae7 Christos Stavrakakis

383 41a7fae7 Christos Stavrakakis
    """
384 41a7fae7 Christos Stavrakakis
    log.info("Get console  VM %s, type %s", vm, console_type)
385 41a7fae7 Christos Stavrakakis
386 41a7fae7 Christos Stavrakakis
    # Use RAPI to get VNC console information for this instance
387 41a7fae7 Christos Stavrakakis
    if vm.operstate != "STARTED":
388 41a7fae7 Christos Stavrakakis
        raise faults.BadRequest('Server not in ACTIVE state.')
389 41a7fae7 Christos Stavrakakis
390 41a7fae7 Christos Stavrakakis
    if settings.TEST:
391 41a7fae7 Christos Stavrakakis
        console_data = {'kind': 'vnc', 'host': 'ganeti_node', 'port': 1000}
392 41a7fae7 Christos Stavrakakis
    else:
393 41a7fae7 Christos Stavrakakis
        console_data = backend.get_instance_console(vm)
394 41a7fae7 Christos Stavrakakis
395 41a7fae7 Christos Stavrakakis
    if console_data['kind'] != 'vnc':
396 41a7fae7 Christos Stavrakakis
        message = 'got console of kind %s, not "vnc"' % console_data['kind']
397 41a7fae7 Christos Stavrakakis
        raise faults.ServiceUnavailable(message)
398 41a7fae7 Christos Stavrakakis
399 41a7fae7 Christos Stavrakakis
    # Let vncauthproxy decide on the source port.
400 41a7fae7 Christos Stavrakakis
    # The alternative: static allocation, e.g.
401 41a7fae7 Christos Stavrakakis
    # sport = console_data['port'] - 1000
402 41a7fae7 Christos Stavrakakis
    sport = 0
403 41a7fae7 Christos Stavrakakis
    daddr = console_data['host']
404 41a7fae7 Christos Stavrakakis
    dport = console_data['port']
405 41a7fae7 Christos Stavrakakis
    password = util.random_password()
406 41a7fae7 Christos Stavrakakis
407 41a7fae7 Christos Stavrakakis
    if settings.TEST:
408 41a7fae7 Christos Stavrakakis
        fwd = {'source_port': 1234, 'status': 'OK'}
409 41a7fae7 Christos Stavrakakis
    else:
410 41a7fae7 Christos Stavrakakis
        fwd = request_vnc_forwarding(sport, daddr, dport, password)
411 41a7fae7 Christos Stavrakakis
412 41a7fae7 Christos Stavrakakis
    if fwd['status'] != "OK":
413 41a7fae7 Christos Stavrakakis
        raise faults.ServiceUnavailable('vncauthproxy returned error status')
414 41a7fae7 Christos Stavrakakis
415 41a7fae7 Christos Stavrakakis
    # Verify that the VNC server settings haven't changed
416 41a7fae7 Christos Stavrakakis
    if not settings.TEST:
417 41a7fae7 Christos Stavrakakis
        if console_data != backend.get_instance_console(vm):
418 41a7fae7 Christos Stavrakakis
            raise faults.ServiceUnavailable('VNC Server settings changed.')
419 41a7fae7 Christos Stavrakakis
420 41a7fae7 Christos Stavrakakis
    console = {
421 41a7fae7 Christos Stavrakakis
        'type': 'vnc',
422 41a7fae7 Christos Stavrakakis
        'host': getfqdn(),
423 41a7fae7 Christos Stavrakakis
        'port': fwd['source_port'],
424 41a7fae7 Christos Stavrakakis
        'password': password}
425 41a7fae7 Christos Stavrakakis
426 41a7fae7 Christos Stavrakakis
    return console
427 9ba6bb95 Christos Stavrakakis
428 9ba6bb95 Christos Stavrakakis
429 a52cc1b4 Christos Stavrakakis
def rename(server, new_name):
430 a52cc1b4 Christos Stavrakakis
    """Rename a VirtualMachine."""
431 a52cc1b4 Christos Stavrakakis
    old_name = server.name
432 a52cc1b4 Christos Stavrakakis
    server.name = new_name
433 a52cc1b4 Christos Stavrakakis
    server.save()
434 a52cc1b4 Christos Stavrakakis
    log.info("Renamed server '%s' from '%s' to '%s'", server, old_name,
435 a52cc1b4 Christos Stavrakakis
             new_name)
436 a52cc1b4 Christos Stavrakakis
    return server
437 fae6e5f0 Christos Stavrakakis
438 fae6e5f0 Christos Stavrakakis
439 fae6e5f0 Christos Stavrakakis
@transaction.commit_on_success
440 fae6e5f0 Christos Stavrakakis
def create_port(*args, **kwargs):
441 fae6e5f0 Christos Stavrakakis
    return _create_port(*args, **kwargs)
442 fae6e5f0 Christos Stavrakakis
443 fae6e5f0 Christos Stavrakakis
444 fae6e5f0 Christos Stavrakakis
def _create_port(userid, network, machine=None, use_ipaddress=None,
445 fae6e5f0 Christos Stavrakakis
                 address=None, name="", security_groups=None,
446 fae6e5f0 Christos Stavrakakis
                 device_owner=None):
447 fae6e5f0 Christos Stavrakakis
    """Create a new port on the specified network.
448 fae6e5f0 Christos Stavrakakis

449 fae6e5f0 Christos Stavrakakis
    Create a new Port(NetworkInterface model) on the specified Network. If
450 fae6e5f0 Christos Stavrakakis
    'machine' is specified, the machine will be connected to the network using
451 fae6e5f0 Christos Stavrakakis
    this port. If 'use_ipaddress' argument is specified, the port will be
452 fae6e5f0 Christos Stavrakakis
    assigned this IPAddress. Otherwise, an IPv4 address from the IPv4 subnet
453 fae6e5f0 Christos Stavrakakis
    will be allocated.
454 fae6e5f0 Christos Stavrakakis

455 fae6e5f0 Christos Stavrakakis
    """
456 fae6e5f0 Christos Stavrakakis
    if network.state != "ACTIVE":
457 fae6e5f0 Christos Stavrakakis
        raise faults.BuildInProgress("Can not create port while network is in"
458 fae6e5f0 Christos Stavrakakis
                                     " state %s" % network.state)
459 3f18f035 Christos Stavrakakis
    if network.action == "DESTROY":
460 3f18f035 Christos Stavrakakis
        msg = "Can not create port. Network %s is being deleted."
461 3f18f035 Christos Stavrakakis
        raise faults.Conflict(msg % network.id)
462 fae6e5f0 Christos Stavrakakis
    ipaddress = None
463 fae6e5f0 Christos Stavrakakis
    if use_ipaddress is not None:
464 fae6e5f0 Christos Stavrakakis
        # Use an existing IPAddress object.
465 fae6e5f0 Christos Stavrakakis
        ipaddress = use_ipaddress
466 fae6e5f0 Christos Stavrakakis
        if ipaddress and (ipaddress.network_id != network.id):
467 fae6e5f0 Christos Stavrakakis
            msg = "IP Address %s does not belong to network %s"
468 fae6e5f0 Christos Stavrakakis
            raise faults.Conflict(msg % (ipaddress.address, network.id))
469 fae6e5f0 Christos Stavrakakis
    else:
470 fae6e5f0 Christos Stavrakakis
        # If network has IPv4 subnets, try to allocate the address that the
471 fae6e5f0 Christos Stavrakakis
        # the user specified or a random one.
472 fae6e5f0 Christos Stavrakakis
        if network.subnets.filter(ipversion=4).exists():
473 fae6e5f0 Christos Stavrakakis
            try:
474 fae6e5f0 Christos Stavrakakis
                ipaddress = util.allocate_ip(network, userid=userid,
475 fae6e5f0 Christos Stavrakakis
                                             address=address)
476 fae6e5f0 Christos Stavrakakis
            except pools.ValueNotAvailable:
477 fae6e5f0 Christos Stavrakakis
                msg = "Address %s is already in use." % address
478 fae6e5f0 Christos Stavrakakis
                raise faults.Conflict(msg)
479 fae6e5f0 Christos Stavrakakis
        elif address is not None:
480 fae6e5f0 Christos Stavrakakis
            raise faults.BadRequest("Address %s is not a valid IP for the"
481 fae6e5f0 Christos Stavrakakis
                                    " defined network subnets" % address)
482 fae6e5f0 Christos Stavrakakis
483 fae6e5f0 Christos Stavrakakis
    if ipaddress is not None and ipaddress.nic is not None:
484 fae6e5f0 Christos Stavrakakis
        raise faults.Conflict("IP address '%s' is already in use" %
485 fae6e5f0 Christos Stavrakakis
                              ipaddress.address)
486 fae6e5f0 Christos Stavrakakis
487 fae6e5f0 Christos Stavrakakis
    port = NetworkInterface.objects.create(network=network,
488 fae6e5f0 Christos Stavrakakis
                                           state="DOWN",
489 fae6e5f0 Christos Stavrakakis
                                           userid=userid,
490 fae6e5f0 Christos Stavrakakis
                                           device_owner=None,
491 fae6e5f0 Christos Stavrakakis
                                           name=name)
492 fae6e5f0 Christos Stavrakakis
493 fae6e5f0 Christos Stavrakakis
    # add the security groups if any
494 fae6e5f0 Christos Stavrakakis
    if security_groups:
495 fae6e5f0 Christos Stavrakakis
        port.security_groups.add(*security_groups)
496 fae6e5f0 Christos Stavrakakis
497 fae6e5f0 Christos Stavrakakis
    if ipaddress is not None:
498 fae6e5f0 Christos Stavrakakis
        # Associate IPAddress with the Port
499 fae6e5f0 Christos Stavrakakis
        ipaddress.nic = port
500 fae6e5f0 Christos Stavrakakis
        ipaddress.save()
501 fae6e5f0 Christos Stavrakakis
502 fae6e5f0 Christos Stavrakakis
    if machine is not None:
503 ae2da8a2 Christos Stavrakakis
        # Connect port to the instance.
504 fae6e5f0 Christos Stavrakakis
        machine = connect(machine, network, port)
505 fae6e5f0 Christos Stavrakakis
        jobID = machine.task_job_id
506 fae6e5f0 Christos Stavrakakis
        log.info("Created Port %s with IP %s. Ganeti Job: %s",
507 fae6e5f0 Christos Stavrakakis
                 port, ipaddress, jobID)
508 fae6e5f0 Christos Stavrakakis
    else:
509 fae6e5f0 Christos Stavrakakis
        log.info("Created Port %s with IP %s not attached to any instance",
510 fae6e5f0 Christos Stavrakakis
                 port, ipaddress)
511 fae6e5f0 Christos Stavrakakis
512 fae6e5f0 Christos Stavrakakis
    return port
513 fae6e5f0 Christos Stavrakakis
514 fae6e5f0 Christos Stavrakakis
515 fae6e5f0 Christos Stavrakakis
def associate_port_with_machine(port, machine):
516 fae6e5f0 Christos Stavrakakis
    """Associate a Port with a VirtualMachine.
517 fae6e5f0 Christos Stavrakakis

518 fae6e5f0 Christos Stavrakakis
    Associate the port with the VirtualMachine and add an entry to the
519 fae6e5f0 Christos Stavrakakis
    IPAddressLog if the port has a public IPv4 address from a public network.
520 fae6e5f0 Christos Stavrakakis

521 fae6e5f0 Christos Stavrakakis
    """
522 ae2da8a2 Christos Stavrakakis
    if port.machine is not None:
523 ae2da8a2 Christos Stavrakakis
        raise faults.Conflict("Port %s is already in use." % port.id)
524 fae6e5f0 Christos Stavrakakis
    if port.network.public:
525 fae6e5f0 Christos Stavrakakis
        ipv4_address = port.ipv4_address
526 fae6e5f0 Christos Stavrakakis
        if ipv4_address is not None:
527 fae6e5f0 Christos Stavrakakis
            ip_log = IPAddressLog.objects.create(server_id=machine.id,
528 fae6e5f0 Christos Stavrakakis
                                                 network_id=port.network_id,
529 fae6e5f0 Christos Stavrakakis
                                                 address=ipv4_address,
530 fae6e5f0 Christos Stavrakakis
                                                 active=True)
531 fae6e5f0 Christos Stavrakakis
            log.debug("Created IP log entry %s", ip_log)
532 fae6e5f0 Christos Stavrakakis
    port.machine = machine
533 fae6e5f0 Christos Stavrakakis
    port.state = "BUILD"
534 fae6e5f0 Christos Stavrakakis
    port.device_owner = "vm"
535 fae6e5f0 Christos Stavrakakis
    port.save()
536 fae6e5f0 Christos Stavrakakis
    return port
537 fae6e5f0 Christos Stavrakakis
538 fae6e5f0 Christos Stavrakakis
539 fae6e5f0 Christos Stavrakakis
@transaction.commit_on_success
540 fae6e5f0 Christos Stavrakakis
def delete_port(port):
541 fae6e5f0 Christos Stavrakakis
    """Delete a port by removing the NIC card from the instance.
542 fae6e5f0 Christos Stavrakakis

543 fae6e5f0 Christos Stavrakakis
    Send a Job to remove the NIC card from the instance. The port
544 fae6e5f0 Christos Stavrakakis
    will be deleted and the associated IPv4 addressess will be released
545 fae6e5f0 Christos Stavrakakis
    when the job completes successfully.
546 fae6e5f0 Christos Stavrakakis

547 fae6e5f0 Christos Stavrakakis
    """
548 fae6e5f0 Christos Stavrakakis
549 fae6e5f0 Christos Stavrakakis
    if port.machine is not None:
550 fae6e5f0 Christos Stavrakakis
        vm = disconnect(port.machine, port)
551 fae6e5f0 Christos Stavrakakis
        log.info("Removing port %s, Job: %s", port, vm.task_job_id)
552 fae6e5f0 Christos Stavrakakis
    else:
553 fae6e5f0 Christos Stavrakakis
        backend.remove_nic_ips(port)
554 fae6e5f0 Christos Stavrakakis
        port.delete()
555 fae6e5f0 Christos Stavrakakis
        log.info("Removed port %s", port)
556 fae6e5f0 Christos Stavrakakis
557 fae6e5f0 Christos Stavrakakis
    return port