Revision ba777b02

b/snf-cyclades-app/synnefo/api/tests/networks.py
37 37
from synnefo.lib.services import get_service_path
38 38
from synnefo.lib import join_urls
39 39
import synnefo.db.models_factory as dbmf
40
from synnefo.db.models import Network
40
from synnefo.db.models import Network, QuotaHolderSerial
41 41
from django.conf import settings
42 42

  
43 43
NETWORK_URL = get_service_path(cyclades_services, 'network',
......
92 92
        self.assertEqual(commission_resources, {"cyclades.network.private": 1})
93 93
        name, args, kwargs =\
94 94
            self.mocked_quotaholder.resolve_commissions.mock_calls[0]
95
        serial = Network.objects.get().serial.serial
95
        serial = QuotaHolderSerial.objects.order_by("-serial")[0]
96 96
        accepted_serials = args[0]
97 97
        rejected_serials = args[1]
98
        self.assertEqual(accepted_serials, [serial])
98
        self.assertEqual(accepted_serials, [serial.serial])
99 99
        self.assertEqual(rejected_serials, [])
100 100

  
101 101
        # test no name
b/snf-cyclades-app/synnefo/logic/backend.py
89 89
        # failed server
90 90
        serial = vm.serial
91 91
        if job_status == rapi.JOB_STATUS_SUCCESS:
92
            quotas.accept_serial(serial)
92
            quotas.accept_resource_serial(vm)
93 93
        elif job_status in [rapi.JOB_STATUS_ERROR, rapi.JOB_STATUS_CANCELED]:
94 94
            log.debug("Job %s failed. Rejecting related serial %s", job_id,
95 95
                      serial)
96
            quotas.reject_serial(serial)
97
        vm.serial = None
96
            quotas.reject_resource_serial(vm)
98 97
    elif job_status == rapi.JOB_STATUS_SUCCESS:
99 98
        commission_info = quotas.get_commission_info(resource=vm,
100 99
                                                     action=action,
......
103 102
            # Commission for this change has not been issued, or the issued
104 103
            # commission was unaware of the current change. Reject all previous
105 104
            # commissions and create a new one in forced mode!
106
            log.debug("Expected job was %s. Processing job %s.",
107
                      vm.task_job_id, job_id)
105
            log.debug("Expected job was %s. Processing job %s. "
106
                      "Attached serial %s",
107
                      vm.task_job_id, job_id, vm.serial)
108 108
            reason = ("client: dispatcher, resource: %s, ganeti_job: %s"
109 109
                      % (vm, job_id))
110
            quotas.handle_resource_commission(vm, action,
111
                                              action_fields=job_fields,
112
                                              commission_name=reason,
113
                                              force=True,
114
                                              auto_accept=True)
115
            log.debug("Issued new commission: %s", vm.serial)
116
            # NOTE: Since we rejected the serial that was associated with the
117
            # 'vm.task_job_id' job, we must also clear the 'vm.serial' field.
118
            # If not, there will be no new commission for the 'vm.task_job_id'
119
            # job!
120
            vm.serial = None
121

  
110
            serial = quotas.handle_resource_commission(
111
                vm, action,
112
                action_fields=job_fields,
113
                commission_name=reason,
114
                force=True,
115
                auto_accept=True)
116
            log.debug("Issued new commission: %s", serial)
122 117
    return vm
123 118

  
124 119

  
b/snf-cyclades-app/synnefo/logic/servers.py
138 138
                    log.debug("Rejecting commission: '%s', could not perform"
139 139
                              " action '%s': %s" % (vm.serial,  action, e))
140 140
                    transaction.rollback()
141
                    quotas.reject_serial(vm.serial)
141
                    quotas.reject_resource_serial(vm)
142 142
                    transaction.commit()
143 143
                raise
144 144

  
......
147 147
                # commission because the VM has been stored in DB. Also, if
148 148
                # communication with Ganeti fails, the job will never reach
149 149
                # Ganeti, and the commission will never be resolved.
150
                quotas.accept_serial(vm.serial)
150
                quotas.accept_resource_serial(vm)
151 151

  
152 152
            log.info("user: %s, vm: %s, action: %s, job_id: %s, serial: %s",
153 153
                     user_id, vm.id, action, job_id, vm.serial)
b/snf-cyclades-app/synnefo/logic/tests/servers.py
197 197
        vm.task_job_id = None
198 198
        vm.save()
199 199
        with mocked_quotaholder():
200
            quotas.accept_serial(vm.serial)
200
            quotas.accept_resource_serial(vm)
201 201
        mrapi().RebootInstance.return_value = 1
202 202
        with mocked_quotaholder():
203 203
            servers.reboot(vm, "HARD")
b/snf-cyclades-app/synnefo/quotas/__init__.py
43 43
log = logging.getLogger(__name__)
44 44

  
45 45

  
46
QUOTABLE_RESOURCES = [VirtualMachine, Network, IPAddress]
47

  
48

  
46 49
DEFAULT_SOURCE = 'system'
47 50
RESOURCES = [
48 51
    "cyclades.vm",
......
115 118
                                             force=force,
116 119
                                             auto_accept=auto_accept)
117 120

  
118
    if serial:
119
        serial_info = {"serial": serial}
120
        if auto_accept:
121
            serial_info["pending"] = False
122
            serial_info["accept"] = True
123
            serial_info["resolved"] = True
124
        return QuotaHolderSerial.objects.create(**serial_info)
125
    else:
121
    if not serial:
126 122
        raise Exception("No serial")
127 123

  
124
    serial_info = {"serial": serial}
125
    if auto_accept:
126
        serial_info["pending"] = False
127
        serial_info["accept"] = True
128
        serial_info["resolved"] = True
129

  
130
    serial = QuotaHolderSerial.objects.create(**serial_info)
131

  
132
    # Correlate the serial with the resource. Resolved serials are not
133
    # attached to resources
134
    if not auto_accept:
135
        resource.serial = serial
136
        resource.save()
128 137

  
129
def accept_serial(serial, strict=True):
138
    return serial
139

  
140

  
141
def accept_resource_serial(resource, strict=True):
142
    serial = resource.serial
130 143
    assert serial.pending or serial.accept, "%s can't be accepted" % serial
131
    response = resolve_commissions(accept=[serial.serial], strict=strict)
132
    return response
144
    log.debug("Accepting serial %s of resource %s", serial, resource)
145
    _resolve_commissions(accept=[serial.serial], strict=strict)
146
    resource.serial = None
147
    resource.save()
148
    return resource
133 149

  
134 150

  
135
def reject_serial(serial, strict=True):
151
def reject_resource_serial(resource, strict=True):
152
    serial = resource.serial
136 153
    assert serial.pending or not serial.accept, "%s can't be rejected" % serial
137
    response = resolve_commissions(reject=[serial.serial], strict=strict)
138
    return response
154
    log.debug("Rejecting serial %s of resource %s", serial, resource)
155
    _resolve_commissions(reject=[serial.serial], strict=strict)
156
    resource.serial = None
157
    resource.save()
158
    return resource
139 159

  
140 160

  
141
def resolve_commissions(accept=None, reject=None, strict=True):
161
def _resolve_commissions(accept=None, reject=None, strict=True):
142 162
    if accept is None:
143 163
        accept = []
144 164
    if reject is None:
......
167 187
    return response
168 188

  
169 189

  
190
def reconcile_resolve_commissions(accept=None, reject=None, strict=True):
191
    response = _resolve_commissions(accept=accept,
192
                                    reject=reject,
193
                                    strict=strict)
194
    affected = response.get("accepted", []) + response.get("rejected", [])
195
    for resource in QUOTABLE_RESOURCES:
196
        resource.objects.filter(serial__in=affected).update(serial=None)
197

  
198

  
170 199
def resolve_pending_commissions():
171 200
    """Resolve quotaholder pending commissions.
172 201

  
......
247 276
                                        action_fields=action_fields,
248 277
                                        commission_name=commission_reason)
249 278

  
279
    if serial is None:
280
        return
281

  
250 282
    # Mark the serial as one to accept and associate it with the resource
251 283
    serial.pending = False
252 284
    serial.accept = True
......
255 287

  
256 288
    try:
257 289
        # Accept the commission to quotaholder
258
        accept_serial(serial)
290
        accept_resource_serial(resource)
259 291
    except:
260 292
        # Do not crash if we can not accept commission to Quotaholder. Quotas
261 293
        # have already been reserved and the resource already exists in DB.
262 294
        # Just log the error
263
        log.exception("Failed to accept commission: %s", serial)
264

  
265
    return serial
295
        log.exception("Failed to accept commission: %s", resource.serial)
266 296

  
267 297

  
268 298
def get_commission_info(resource, action, action_fields=None):
......
342 372
    # The one who succeeds will be finally accepted, and all other will be
343 373
    # rejected
344 374
    force = force or (action == "DESTROY")
345
    resolve_commission(resource.serial, force=force)
375
    resolve_resource_commission(resource, force=force)
346 376

  
347 377
    serial = issue_commission(resource, action, name=commission_name,
348 378
                              force=force, auto_accept=auto_accept,
349 379
                              action_fields=action_fields)
350
    resource.serial = serial
351
    resource.save()
352 380
    return serial
353 381

  
354 382

  
......
356 384
    pass
357 385

  
358 386

  
359
def resolve_commission(serial, force=False):
387
def resolve_resource_commission(resource, force=False):
388
    serial = resource.serial
360 389
    if serial is None or serial.resolved:
361 390
        return
362 391
    if serial.pending and not force:
......
364 393
        raise ResolveError(m)
365 394
    log.warning("Resolving pending commission: %s", serial)
366 395
    if not serial.pending and serial.accept:
367
        accept_serial(serial)
396
        accept_resource_serial(resource)
368 397
    else:
369
        reject_serial(serial)
398
        reject_resource_serial(resource)
b/snf-cyclades-app/synnefo/quotas/management/commands/reconcile-commissions-cyclades.py
63 63

  
64 64
        if fix and (accepted or rejected):
65 65
            self.stdout.write("Fixing pending commissions..\n")
66
            quotas.resolve_commissions(accept=accepted, reject=rejected,
67
                                       strict=False)
66
            quotas.reconcile_resolve_commissions(accept=accepted,
67
                                                 reject=rejected,
68
                                                 strict=False)
68 69

  
69 70

  
70 71
def list_to_string(l):
b/snf-django-lib/snf_django/utils/testing.py
204 204
            return (len(astakos.return_value.issue_one_commission.mock_calls) +
205 205
                    serial)
206 206
        astakos.return_value.issue_one_commission.side_effect = foo
207
        astakos.return_value.resolve_commissions.return_value = {"failed": []}
207
        def resolve_mock(*args, **kwargs):
208
            return {"failed": [],
209
                    "accepted": args[0],
210
                    "rejected": args[1],
211
                    }
212
        astakos.return_value.resolve_commissions.side_effect = resolve_mock
208 213
        yield astakos.return_value
209 214

  
210 215

  

Also available in: Unified diff