1 = Resources = 2 3 In imip-agent, resources are a special kind of user that act upon requests 4 to schedule events and that perform such scheduling autonomously, meaning 5 that no human intervention is necessary when such resources receive messages 6 containing invitations. 7 8 By default, the [[../AgentPrograms|agent program]] responsible for resources 9 merely attempts to fit a received event into the resource's schedule. However, 10 in some organisations and environments, it is likely to be the case that other 11 policies are needed to ensure that a resource is not misused, overused or made 12 unnecessarily unavailable. 13 14 The [[../Preferences|preferences]] provide a way of controlling the behaviour 15 of resources, just as with any other kind of user, but certain preferences 16 are central to the configuration of resources. 17 18 <<TableOfContents(2,4)>> 19 20 == Scheduling Functions == 21 22 The [[../Preferences#scheduling_function|scheduling_function]] setting 23 indicates the behaviour of a resource when a valid request to schedule an 24 event has been received. By default, a value equivalent to the following is 25 employed: 26 27 {{{{#!table 28 '''Scheduling Functions''' || '''Decision Process''' 29 == 30 <style="vertical-align: top;"> 31 32 {{{ 33 schedule_in_freebusy 34 }}} 35 36 || 37 38 {{{#!graphviz 39 //format=svg 40 //transform=notugly 41 digraph scheduling_decisions { 42 node [shape=box,fontsize="13.0",fontname="Helvetica",tooltip="Scheduling decisions"]; 43 edge [tooltip="Scheduling decisions"]; 44 45 mail [label="Incoming mail",shape=folder,style=filled,fillcolor=cyan]; 46 47 subgraph { 48 rank=same; 49 schedule_in_freebusy [label="Can schedule in free/busy?",shape=ellipse,style=filled,fillcolor=gold]; 50 freebusy [label="Free/busy",shape=folder]; 51 } 52 53 schedule [label="Schedule event for resource",shape=ellipse,style=filled,fillcolor=gold]; 54 55 subgraph { 56 rank=same; 57 accept [label="Accept",shape=folder,style=filled,fillcolor=cyan]; 58 decline [label="Decline",shape=folder,style=filled,fillcolor=cyan]; 59 } 60 61 mail -> schedule_in_freebusy -> schedule -> accept; 62 schedule_in_freebusy -> decline [style=dashed]; 63 freebusy -> schedule_in_freebusy; 64 } 65 }}} 66 67 }}}} 68 69 As described above, this merely attempts to schedule an event in the free 70 periods of the resource's schedule. However, no attempt is made to reject the 71 booking of the resource according to the identity of the organiser. 72 73 === Identity Controls === 74 75 Although identity controls may be implemented in the e-mail system, 76 effectively preventing the messages from addresses other than those within 77 an organisation (for example) from being delivered to the resource, it is 78 possible to use scheduling functions to implement such controls instead. 79 80 ==== Same Domain Membership ==== 81 82 For instance, the following combines the default free/busy check with a 83 test that the organiser belongs to the same Internet mail domain (by using 84 the organiser's address): 85 86 {{{{#!table 87 '''Scheduling Functions''' || '''Decision Process''' 88 == 89 <style="vertical-align: top;"> 90 91 {{{ 92 schedule_in_freebusy 93 same_domain_only 94 }}} 95 96 || 97 98 {{{#!graphviz 99 //format=svg 100 //transform=notugly 101 digraph scheduling_decisions { 102 node [shape=box,fontsize="13.0",fontname="Helvetica",tooltip="Scheduling decisions"]; 103 edge [tooltip="Scheduling decisions"]; 104 105 mail [label="Incoming mail",shape=folder,style=filled,fillcolor=cyan]; 106 107 subgraph { 108 rank=same; 109 schedule_in_freebusy [label="Can schedule in free/busy?",shape=ellipse,style=filled,fillcolor=gold]; 110 freebusy [label="Free/busy",shape=folder]; 111 } 112 113 same_domain_only [label="Organiser has resource domain?",shape=ellipse,style=filled,fillcolor=gold]; 114 115 schedule [label="Schedule event for resource",shape=ellipse,style=filled,fillcolor=gold]; 116 117 subgraph { 118 rank=same; 119 accept [label="Accept",shape=folder,style=filled,fillcolor=cyan]; 120 decline [label="Decline",shape=folder,style=filled,fillcolor=cyan]; 121 } 122 123 mail -> schedule_in_freebusy -> same_domain_only -> schedule -> accept; 124 schedule_in_freebusy -> decline [style=dashed]; 125 same_domain_only -> decline [style=dashed]; 126 freebusy -> schedule_in_freebusy; 127 } 128 }}} 129 130 }}}} 131 132 Note that if the first function is omitted, no check against the resource's 133 schedule will occur, so it is necessary to mention any such function in the 134 list. 135 136 ==== Access Control Lists ==== 137 138 A simple domain-related test may not be sufficient to control access to a 139 resource. Thus, another function is provided to exercise a finer degree of 140 control over event participants. For example: 141 142 {{{{#!table 143 '''Scheduling Functions and Data''' || '''Decision Process''' 144 == 145 <style="vertical-align: top;"> 146 147 {{{ 148 schedule_in_freebusy 149 access_control_list 150 }}} 151 152 Access control list: 153 154 {{{ 155 accept 156 }}} 157 158 || 159 160 {{{#!graphviz 161 //format=svg 162 //transform=notugly 163 digraph scheduling_decisions { 164 node [shape=box,fontsize="13.0",fontname="Helvetica",tooltip="Scheduling decisions"]; 165 edge [tooltip="Scheduling decisions"]; 166 167 mail [label="Incoming mail",shape=folder,style=filled,fillcolor=cyan]; 168 169 subgraph { 170 rank=same; 171 schedule_in_freebusy [label="Can schedule in free/busy?",shape=ellipse,style=filled,fillcolor=gold]; 172 freebusy [label="Free/busy",shape=folder]; 173 } 174 175 subgraph { 176 rank=same; 177 access_control_list [label="Access control list permits booking?",shape=ellipse,style=filled,fillcolor=gold]; 178 acl [label="acl setting",shape=folder]; 179 } 180 181 accept_default [label="Accept invitation by default",shape=ellipse,style=filled,fillcolor=darkorange]; 182 end_acl [label="end",shape=ellipse,style=filled,fillcolor=darkorange]; 183 184 schedule [label="Schedule event for resource",shape=ellipse,style=filled,fillcolor=gold]; 185 186 subgraph { 187 rank=same; 188 accept [label="Accept",shape=folder,style=filled,fillcolor=cyan]; 189 decline [label="Decline",shape=folder,style=filled,fillcolor=cyan]; 190 } 191 192 mail -> schedule_in_freebusy -> access_control_list -> accept_default -> end_acl -> schedule -> accept; 193 end_acl -> decline [style=dashed]; 194 schedule_in_freebusy -> decline [style=dashed]; 195 freebusy -> schedule_in_freebusy; 196 acl -> access_control_list; 197 } 198 }}} 199 200 }}}} 201 202 To accompany the scheduling functions, the [[../Preferences#acl|acl]] setting 203 in the resource's preferences must be set, or if a separate file is more 204 appropriate, its full path may be given as an argument to `access_control_list`: 205 206 {{{ 207 schedule_in_freebusy 208 access_control_list /etc/imip-agent/resources.acl 209 }}} 210 211 Within the file provided by the setting or separate file, a list of rules 212 must describe the handling procedure for an event. For example, the following 213 was given in the above example: 214 215 {{{ 216 accept 217 }}} 218 219 This will merely accept all invitations, anyway. However, it may be 220 appropriate to prevent certain users from using resources. For example: 221 222 {{{{#!table 223 '''Scheduling Functions and Data''' || '''Decision Process''' 224 == 225 <style="vertical-align: top;"> 226 227 {{{ 228 schedule_in_freebusy 229 access_control_list 230 }}} 231 232 Access control list: 233 234 {{{ 235 accept 236 decline attendee simon.skunk@example.com 237 }}} 238 239 || 240 241 {{{#!graphviz 242 //format=svg 243 //transform=notugly 244 digraph scheduling_decisions { 245 node [shape=box,fontsize="13.0",fontname="Helvetica",tooltip="Scheduling decisions"]; 246 edge [tooltip="Scheduling decisions"]; 247 248 mail [label="Incoming mail",shape=folder,style=filled,fillcolor=cyan]; 249 250 subgraph { 251 rank=same; 252 schedule_in_freebusy [label="Can schedule in free/busy?",shape=ellipse,style=filled,fillcolor=gold]; 253 freebusy [label="Free/busy",shape=folder]; 254 } 255 256 subgraph { 257 rank=same; 258 access_control_list [label="Access control list permits booking?",shape=ellipse,style=filled,fillcolor=gold]; 259 acl [label="acl setting",shape=folder]; 260 } 261 262 accept_default [label="Accept invitation by default",shape=ellipse,style=filled,fillcolor=darkorange]; 263 decline_attendee [label="Is attendee simon.skunk@example.com?",shape=ellipse,style=filled,fillcolor=darkorange]; 264 end_acl [label="end",shape=ellipse,style=filled,fillcolor=darkorange]; 265 266 schedule [label="Schedule event for resource",shape=ellipse,style=filled,fillcolor=gold]; 267 268 subgraph { 269 rank=same; 270 accept [label="Accept",shape=folder,style=filled,fillcolor=cyan]; 271 decline [label="Decline",shape=folder,style=filled,fillcolor=cyan]; 272 } 273 274 mail -> schedule_in_freebusy -> access_control_list -> accept_default -> decline_attendee -> end_acl -> schedule -> accept; 275 end_acl -> decline [style=dashed]; 276 schedule_in_freebusy -> decline [style=dashed]; 277 freebusy -> schedule_in_freebusy; 278 acl -> access_control_list; 279 } 280 }}} 281 282 }}}} 283 284 This example indicates that by default, invitations will be accepted, but if 285 one of the attendees of an event is `simon.skunk@example.com`, the invitation 286 will be declined. However, it may be the case that this rule should be 287 overridden under certain circumstances. For example: 288 289 {{{{#!table 290 '''Scheduling Functions and Data''' || '''Decision Process''' 291 == 292 <style="vertical-align: top;"> 293 294 {{{ 295 schedule_in_freebusy 296 access_control_list 297 }}} 298 299 Access control list: 300 301 {{{ 302 accept 303 decline attendee simon.skunk@example.com 304 accept organiser paul.boddie@example.com 305 }}} 306 307 || 308 309 {{{#!graphviz 310 //format=svg 311 //transform=notugly 312 digraph scheduling_decisions { 313 node [shape=box,fontsize="13.0",fontname="Helvetica",tooltip="Scheduling decisions"]; 314 edge [tooltip="Scheduling decisions"]; 315 316 mail [label="Incoming mail",shape=folder,style=filled,fillcolor=cyan]; 317 318 subgraph { 319 rank=same; 320 schedule_in_freebusy [label="Can schedule in free/busy?",shape=ellipse,style=filled,fillcolor=gold]; 321 freebusy [label="Free/busy",shape=folder]; 322 } 323 324 subgraph { 325 rank=same; 326 access_control_list [label="Access control list permits booking?",shape=ellipse,style=filled,fillcolor=gold]; 327 acl [label="acl setting",shape=folder]; 328 } 329 330 accept_default [label="Accept invitation by default",shape=ellipse,style=filled,fillcolor=darkorange]; 331 decline_attendee [label="Is attendee simon.skunk@example.com?",shape=ellipse,style=filled,fillcolor=darkorange]; 332 accept_organiser [label="Is organiser paul.boddie@example.com?",shape=ellipse,style=filled,fillcolor=darkorange]; 333 end_acl [label="end",shape=ellipse,style=filled,fillcolor=darkorange]; 334 335 schedule [label="Schedule event for resource",shape=ellipse,style=filled,fillcolor=gold]; 336 337 subgraph { 338 rank=same; 339 accept [label="Accept",shape=folder,style=filled,fillcolor=cyan]; 340 decline [label="Decline",shape=folder,style=filled,fillcolor=cyan]; 341 } 342 343 mail -> schedule_in_freebusy -> access_control_list -> accept_default -> decline_attendee -> accept_organiser -> end_acl -> schedule -> accept; 344 end_acl -> decline [style=dashed]; 345 schedule_in_freebusy -> decline [style=dashed]; 346 freebusy -> schedule_in_freebusy; 347 acl -> access_control_list; 348 } 349 }}} 350 351 }}}} 352 353 Here, the stated organiser may still arrange a booking of the resource where 354 the previously-mentioned attendee is involved. 355 356 === Quota Controls === 357 358 In contrast to each user's stored information which consolidates information 359 related to that user's own schedule, the quota system consolidates information 360 related to the schedules of one or more resources, thus enabling observations 361 to be made about their collective usage. 362 363 First, consider a resource such as a car where an organiser of an event may be 364 booking the car for travel purposes. A quota prevents the organiser from 365 booking the resource too much and denying other users access to it. 366 367 Now consider a number of separate car resources. An organiser might attempt to 368 get around any individual resource quota by booking a number of different cars. 369 By grouping the resources together, the organiser will exhaust any quota set on 370 the group of resources as they try and make reservations for the different 371 members of the quota group. 372 373 ==== Initialising Quotas ==== 374 375 Within the journal storage area, a quota may be initialised with limits 376 indicating the amount of time that can be occupied by the cumulative total of 377 all events scheduled by an individual user or a group of which they are a 378 member. 379 380 Such limits may be set directly using the `limits` file in a quota directory 381 (described in the [[../FilesystemUsage|filesystem guide]]) or in the 382 `quota_limits` table (described in the [[../DatabaseStore|database guide]]), 383 but a tool is also provided to set such limits. For example: 384 385 {{{ 386 cat <<EOF | set_quota_limits.py 'mailto:resource-car-cadillac@example.com' 387 mailto:vincent.vole@example.com PT10H 388 EOF 389 }}} 390 391 {{{#!wiki tip 392 In the above example, shell syntax is used to indicate a 393 [[http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_07_04|here document]] 394 providing a kind of "inline" file that is terminated by the final `EOF`. 395 The contents of this file are piped to the tool with a single argument given 396 indicating the quota involved. 397 398 You could also just invoke the tool and then enter the limit descriptions, 399 ending the input with Ctrl-D or equivalent end-of-file keystroke, or save 400 the descriptions in a file and then use input redirection with the filename. 401 }}} 402 403 The above example indicates that the given user may only reserve 10 hours 404 of events or less time within the given quota (corresponding to a specific 405 resource in the above example). Attempts to schedule more time will be 406 declined. 407 408 To impose a general quota, the special `*` identity can be used: 409 410 {{{ 411 cat <<EOF | set_quota_limits.py 'mailto:resource-car-cadillac@example.com' 412 * PT10H 413 EOF 414 }}} 415 416 When a user identity is not listed and no general quota is defined, that 417 particular user will be unable to reserve the resource unless defined as a 418 member of a group listed in the `limits` file, as described below. 419 420 ==== Sharing Quotas Across Users ==== 421 422 When the use of resources is to be shared between users in such a way that 423 groups of users will be sharing a single quota, the `groups` file in the 424 quota directory must be defined, mapping each user identity to the group to 425 which they will belong. For example: 426 427 {{{{#!table 428 '''Scheduling Data''' || '''Decision Process''' 429 == 430 <style="vertical-align: top;"> 431 The groups are defined as follows in the `groups` file: 432 433 {{{ 434 mailto:vincent.vole@example.com developers 435 mailto:harvey.horse@example.com developers 436 mailto:paul.boddie@example.com developers 437 mailto:simon.skunk@example.com testers 438 }}} 439 440 The group identity can then be employed in the `limits` file: 441 442 {{{ 443 developers PT10H 444 testers PT20H 445 }}} 446 447 || 448 449 {{{#!graphviz 450 //format=svg 451 //transform=notugly 452 digraph quota_users { 453 node [shape=box,fontsize="13.0",fontname="Helvetica",tooltip="Quota users"]; 454 edge [tooltip="Quota users"]; 455 456 subgraph { 457 rank=same; 458 user1 [label="User is vincent.vole@example.com"]; 459 user2 [label="User is oliver.otter@example.com"]; 460 } 461 462 havegroup [label="Have group for quota?",shape=ellipse,style=filled,fillcolor=gold]; 463 haveuser [label="Have group for user in quota?",shape=ellipse,style=filled,fillcolor=gold]; 464 465 group1 [label="User is vincent.vole@example.com\nGroup is developers"]; 466 group2 [label="User is oliver.otter@example.com"]; 467 468 checkuser1 [label="Have limit for group?",shape=ellipse,style=filled,fillcolor=gold]; 469 checkgeneral1 [label="Have general limit?",shape=ellipse,style=filled,fillcolor=gold]; 470 471 checkuser2 [label="Have limit for user?",shape=ellipse,style=filled,fillcolor=gold]; 472 checkgeneral2 [label="Have general limit?",shape=ellipse,style=filled,fillcolor=gold]; 473 474 accept [label="Quota",shape=folder,style=filled,fillcolor=cyan]; 475 decline [label="No quota",shape=folder,style=filled,fillcolor=cyan]; 476 477 user1 -> havegroup -> haveuser -> group1 -> checkuser1 -> checkgeneral1 -> accept; 478 user2 -> havegroup -> haveuser -> group2 -> checkuser2 -> checkgeneral2 -> decline [style=dashed]; 479 } 480 }}} 481 }}}} 482 483 Where individuals are not assigned to groups, any individual limit will apply 484 to them; otherwise, the general quota applies. Where individuals are assigned 485 to groups, any group limit will apply; otherwise, the general quota applies. 486 487 ==== Individual Resource Quotas ==== 488 489 The trivial case of applying quotas is to give a resource its own quota. This 490 is achieved by not specifying any arguments to the `check_quota` scheduling 491 function or to the `add_to_quota` and `remove_from_quota` functions. 492 493 {{{{#!table 494 '''Scheduling Functions''' || '''Decision Process''' 495 == 496 <style="vertical-align: top;"> 497 498 {{{ 499 check_quota 500 }}} 501 502 || 503 504 {{{#!graphviz 505 //format=svg 506 //transform=notugly 507 digraph scheduling_decisions { 508 node [shape=box,fontsize="13.0",fontname="Helvetica",tooltip="Scheduling decisions"]; 509 edge [tooltip="Scheduling decisions"]; 510 511 subgraph { 512 rank=same; 513 mail [label="Incoming mail\nfrom vincent.vole@example.com",shape=folder,style=filled,fillcolor=cyan]; 514 cancel [label="Incoming cancellation",shape=folder,style=filled,fillcolor=cyan]; 515 } 516 517 subgraph { 518 rank=same; 519 check_quota [label="Is allowed by quota?",shape=ellipse,style=filled,fillcolor=gold]; 520 quota [label="Quota for resource",shape=folder]; 521 quota_for_vole [label="...applying to\nvincent.vole@example.com",shape=folder]; 522 } 523 524 schedule [label="Schedule event for resource",shape=ellipse,style=filled,fillcolor=gold]; 525 526 subgraph { 527 rank=same; 528 accept [label="Accept",shape=folder,style=filled,fillcolor=cyan]; 529 decline [label="Decline",shape=folder,style=filled,fillcolor=cyan]; 530 } 531 532 add_to_quota [label="Add to quota",shape=ellipse,style=filled,fillcolor=darkorange]; 533 remove_from_quota [label="Remove from quota",shape=ellipse,style=filled,fillcolor=darkorange]; 534 535 mail -> check_quota -> schedule -> accept; 536 check_quota -> decline [style=dashed]; 537 schedule -> add_to_quota -> quota; 538 quota -> quota_for_vole -> check_quota; 539 540 cancel -> remove_from_quota -> quota; 541 } 542 }}} 543 544 }}}} 545 546 ==== Common Resource Quotas ==== 547 548 By indicating an argument to the different functions, a common quota can be 549 employed. In the following example, both resources would employ the given 550 function invocations to pool their knowledge about their schedules. 551 552 {{{{#!table 553 '''Scheduling Functions''' || '''Decision Process''' 554 == 555 <style="vertical-align: top;"> 556 557 {{{ 558 check_quota cars 559 }}} 560 561 || 562 563 {{{#!graphviz 564 //format=svg 565 //transform=notugly 566 digraph scheduling_decisions { 567 node [shape=box,fontsize="13.0",fontname="Helvetica",tooltip="Scheduling decisions"]; 568 edge [tooltip="Scheduling decisions"]; 569 570 subgraph { 571 rank=same; 572 mail_cadillac [label="Incoming mail\nfrom vincent.vole@example.com\nto resource-car-cadillac@example.com",shape=folder,style=filled,fillcolor=cyan]; 573 mail_pontiac [label="Incoming mail\nfrom vincent.vole@example.com\nto resource-car-pontiac@example.com",shape=folder,style=filled,fillcolor=cyan]; 574 cancel [label="Incoming cancellation",shape=folder,style=filled,fillcolor=cyan]; 575 } 576 577 subgraph { 578 rank=same; 579 check_quota [label="Is allowed by quota?",shape=ellipse,style=filled,fillcolor=gold]; 580 quota_cars [label="Quota for cars",shape=folder]; 581 quota_cars_vole [label="...applying to\nvincent.vole@example.com",shape=folder]; 582 } 583 584 schedule [label="Schedule event for resource",shape=ellipse,style=filled,fillcolor=gold]; 585 586 subgraph { 587 rank=same; 588 accept [label="Accept",shape=folder,style=filled,fillcolor=cyan]; 589 decline [label="Decline",shape=folder,style=filled,fillcolor=cyan]; 590 } 591 592 add_to_quota [label="Add to quota",shape=ellipse,style=filled,fillcolor=darkorange]; 593 remove_from_quota [label="Remove from quota",shape=ellipse,style=filled,fillcolor=darkorange]; 594 595 mail_cadillac -> check_quota; 596 mail_pontiac -> check_quota -> schedule -> accept; 597 check_quota -> decline [style=dashed]; 598 schedule -> add_to_quota -> quota_cars; 599 quota_cars -> quota_cars_vole -> check_quota; 600 601 cancel -> remove_from_quota -> quota_cars; 602 } 603 }}} 604 605 }}}} 606 607 ==== Collective Scheduling ==== 608 609 Consider two separate resources: both may be reserved at the same time by the 610 same organiser; neither resource would normally decline the reservation on the 611 basis of schedule availability, should the period concerned be free. However, 612 it may be undesirable for one organiser to occupy both resources at the same 613 time. 614 615 Consequently, a mechanism is required to pool the resource schedules in such a 616 way that any reservation performed for one resource at a given point in time 617 prohibits another reservation performed for a related resource at the same 618 point in time by the same user. 619 620 The free/busy records held for a given quota group permit such collective 621 scheduling decisions and are employed as follows: 622 623 {{{{#!table 624 '''Scheduling Functions''' || '''Decision Process''' 625 == 626 <style="vertical-align: top;"> 627 628 {{{ 629 schedule_across_quota cars 630 }}} 631 632 || 633 634 {{{#!graphviz 635 //format=svg 636 //transform=notugly 637 digraph scheduling_decisions { 638 node [shape=box,fontsize="13.0",fontname="Helvetica",tooltip="Scheduling decisions"]; 639 edge [tooltip="Scheduling decisions"]; 640 641 subgraph { 642 rank=same; 643 mail_cadillac [label="Incoming mail\nfrom vincent.vole@example.com\nto resource-car-cadillac@example.com",shape=folder,style=filled,fillcolor=cyan]; 644 mail_pontiac [label="Incoming mail\nfrom vincent.vole@example.com\nto resource-car-pontiac@example.com",shape=folder,style=filled,fillcolor=cyan]; 645 cancel [label="Incoming cancellation",shape=folder,style=filled,fillcolor=cyan]; 646 } 647 648 subgraph { 649 rank=same; 650 schedule_across_quota [label="Can be scheduled within the quota?",shape=ellipse,style=filled,fillcolor=gold]; 651 quota_cars [label="Quota for cars",shape=folder]; 652 freebusy_cars_vole [label="...recording schedule for\nvincent.vole@example.com",shape=folder]; 653 } 654 655 schedule [label="Schedule event for resource",shape=ellipse,style=filled,fillcolor=gold]; 656 657 subgraph { 658 rank=same; 659 accept [label="Accept",shape=folder,style=filled,fillcolor=cyan]; 660 decline [label="Decline",shape=folder,style=filled,fillcolor=cyan]; 661 } 662 663 add_to_quota_freebusy [label="Add to quota free/busy",shape=ellipse,style=filled,fillcolor=darkorange]; 664 remove_from_quota_freebusy [label="Remove from quota free/busy",shape=ellipse,style=filled,fillcolor=darkorange]; 665 666 mail_cadillac -> schedule_across_quota; 667 mail_pontiac -> schedule_across_quota -> schedule -> accept; 668 schedule_across_quota -> decline [style=dashed]; 669 schedule -> add_to_quota_freebusy -> quota_cars -> freebusy_cars_vole; 670 freebusy_cars_vole -> schedule_across_quota; 671 672 cancel -> remove_from_quota_freebusy -> freebusy_cars_vole; 673 } 674 }}} 675 676 }}}}