Package yakumo :: Package nova :: Package v2 :: Module server
[hide private]
[frames] | no frames]

Source Code for Module yakumo.nova.v2.server

  1  # Copyright 2014-2017 by Akira Yoshiyama <akirayoshiyama@gmail.com>. 
  2  # All Rights Reserved. 
  3  # 
  4  #    Licensed under the Apache License, Version 2.0 (the "License"); you may 
  5  #    not use this file except in compliance with the License. You may obtain 
  6  #    a copy of the License at 
  7  # 
  8  #         http://www.apache.org/licenses/LICENSE-2.0 
  9  # 
 10  #    Unless required by applicable law or agreed to in writing, software 
 11  #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 
 12  #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 
 13  #    License for the specific language governing permissions and limitations 
 14  #    under the License. 
 15   
 16  """ 
 17  Resource class and its manager for servers in Compute API v2 
 18  """ 
 19   
 20  import time 
 21   
 22  from yakumo import base 
 23  from yakumo.constant import UNDEF 
 24  from yakumo import exception 
 25  from yakumo import mapper 
 26  from yakumo import utils 
 27  from . import volume_attachment 
 28  from . import interface_attachment 
 29  from yakumo.neutron.v2.network import Resource as Network 
 30  from yakumo.neutron.v2.port import Resource as Port 
 31  from yakumo.cinder.v2.volume import Resource as Volume 
 32  from yakumo.cinder.v2.snapshot import Resource as Snapshot 
 33  from yakumo.glance.v2.image import Resource as Image 
 34   
 35   
 36  ATTRIBUTE_MAPPING = [ 
 37      ('id', 'id', mapper.Noop), 
 38      ('name', 'name', mapper.Noop), 
 39      ('access_ipv4', 'accessIPv4', mapper.Noop), 
 40      ('access_ipv6', 'accessIPv6', mapper.Noop), 
 41      ('addresses', 'addresses', mapper.Noop), 
 42      ('host', 'OS-EXT-SRV-ATTR:host', mapper.Noop), 
 43      ('networks', 'networks', mapper.Noop), 
 44      ('disks', 'block_device_mapping_v2', mapper.Noop), 
 45      ('user_data', 'user_data', mapper.Base64), 
 46      ('progress', 'progress', mapper.Noop), 
 47      ('status', 'status', mapper.Noop), 
 48      ('task_state', 'OS-EXT-STS:task_state', mapper.Noop), 
 49      ('created_at', 'created', mapper.DateTime), 
 50      ('updated_at', 'updated', mapper.DateTime), 
 51      ('metadata', 'metadata', mapper.Noop), 
 52      ('flavor', 'flavorRef', mapper.Resource('nova.flavor')), 
 53      ('image', 'imageRef', mapper.Resource('image')), 
 54      ('project', 'tenant_id', mapper.Resource('project')), 
 55      ('user', 'user_id', mapper.Resource('user')), 
 56      ('key_pair', 'key_name', mapper.Resource('nova.key_pair')), 
 57      ('error_reason', 'fault', mapper.Noop), 
 58      ('availability_zone', 'availability_zone', 
 59       mapper.Resource('nova.availability_zone')), 
 60      ('availability_zone', 'OS-EXT-AZ:availability_zone', 
 61       mapper.Resource('nova.availability_zone')), 
 62  ] 
 63   
 64   
65 -class Resource(base.Resource):
66 """Resource class for servers in Compute API v2""" 67 68 _sub_manager_list = { 69 'volume': volume_attachment.Manager, 70 'interface': interface_attachment.Manager, 71 } 72
73 - def wait_for_finished(self, count=10, interval=10):
74 """ 75 Wait for task finished 76 77 @keyword count: Maximum polling time 78 @type count: int 79 @keyword interval: Polling interval in seconds 80 @type interval: int 81 @rtype: None 82 """ 83 for i in range(count): 84 time.sleep(interval) 85 try: 86 self.reload() 87 except exception.NotFound: 88 return 89 if not self.task_state: 90 return
91
92 - def start(self):
93 """ 94 Start a server 95 96 @rtype: None 97 """ 98 self._http.post(self._url_resource_path, self._id, 'action', 99 data=utils.get_json_body("os-start"))
100
101 - def stop(self):
102 """ 103 Stop a server 104 105 @rtype: None 106 """ 107 self._http.post(self._url_resource_path, self._id, 'action', 108 data=utils.get_json_body("os-stop"))
109
110 - def reboot(self, force=False):
111 """ 112 Reboot a server 113 114 @keyword force: Whether reboot type is hard or soft. force=True means 115 hard reboot. 116 @type type: bool 117 @rtype: None 118 """ 119 if force: 120 type = "HARD" 121 else: 122 type = "SOFT" 123 self._http.post(self._url_resource_path, self._id, 'action', 124 data=utils.get_json_body("reboot", type=type))
125
126 - def pause(self):
127 """ 128 Pause a server (save to RAM if server is a VM) 129 130 @rtype: None 131 """ 132 self._http.post(self._url_resource_path, self._id, 'action', 133 data=utils.get_json_body("pause"))
134
135 - def unpause(self):
136 """ 137 Unpause a server 138 139 @rtype: None 140 """ 141 self._http.post(self._url_resource_path, self._id, 'action', 142 data=utils.get_json_body("unpause"))
143
144 - def suspend(self):
145 """ 146 Suspend a server (save to disk if server is a VM) 147 148 @rtype: None 149 """ 150 self._http.post(self._url_resource_path, self._id, 'action', 151 data=utils.get_json_body("suspend"))
152
153 - def resume(self):
154 """ 155 Resume a server 156 157 @rtype: None 158 """ 159 self._http.post(self._url_resource_path, self._id, 'action', 160 data=utils.get_json_body("resume"))
161
162 - def reset_network(self):
163 """ 164 Reset networking of a server 165 166 @rtype: None 167 """ 168 self._http.post(self._url_resource_path, self._id, 'action', 169 data=utils.get_json_body("resetNetwork"))
170
171 - def inject_network_info(self):
172 """ 173 Inject network information to a server 174 175 @rtype: None 176 """ 177 self._http.post(self._url_resource_path, self._id, 'action', 178 data=utils.get_json_body("injectNetworkInfo"))
179
180 - def lock(self):
181 """ 182 Lock a server 183 184 @rtype: None 185 """ 186 self._http.post(self._url_resource_path, self._id, 'action', 187 data=utils.get_json_body("lock"))
188
189 - def unlock(self):
190 """ 191 Unlock a server 192 193 @rtype: None 194 """ 195 self._http.post(self._url_resource_path, self._id, 'action', 196 data=utils.get_json_body("unlock"))
197
198 - def force_delete(self):
199 """ 200 Force to delete a server 201 202 @rtype: None 203 """ 204 self._http.post(self._url_resource_path, self._id, 'action', 205 data=utils.get_json_body("forceDelete"))
206
207 - def restore(self):
208 """ 209 Restore a defered-deleted server if available 210 211 @rtype: None 212 """ 213 self._http.post(self._url_resource_path, self._id, 'action', 214 data=utils.get_json_body("restore"))
215
216 - def rescue(self, password=None):
217 """ 218 Create rescue environment for the server 219 220 @keyword password: password of the rescue OS 221 @type password: str 222 @rtype: None 223 """ 224 self._http.post(self._url_resource_path, self._id, 'action', 225 data=utils.get_json_body("rescue", dminPass=password))
226
227 - def unrescue(self):
228 """ 229 Terminate the rescue environment 230 231 @rtype: None 232 """ 233 self._http.post(self._url_resource_path, self._id, 'action', 234 data=utils.get_json_body("unrescue"))
235
236 - def shelve(self):
237 """ 238 Shelve a running server 239 240 @rtype: None 241 """ 242 self._http.post(self._url_resource_path, self._id, 'action', 243 data=utils.get_json_body("shelve"))
244
245 - def unshelve(self):
246 """ 247 Restore a shelved server 248 249 @rtype: None 250 """ 251 self._http.post(self._url_resource_path, self._id, 'action', 252 data=utils.get_json_body("unshelve"))
253
254 - def delete_shelve(self):
255 """ 256 Delete a shelved server 257 258 @rtype: None 259 """ 260 self._http.post(self._url_resource_path, self._id, 'action', 261 data=utils.get_json_body("shelveOffload"))
262
263 - def create_image(self, name=None, metadata=None):
264 """ 265 Create server image 266 267 @keyword name: Image name 268 @type name: str 269 @keyword metadata: Metadata 270 @type metadata: dict 271 @rtype: None 272 """ 273 self._http.post(self._url_resource_path, self._id, 'action', 274 data=utils.get_json_body( 275 "createImage", 276 name=name, 277 metadata=metadata))
278
279 - def backup(self, name=None, backup_type=None, rotation=None):
280 """ 281 Create server backup 282 283 @keyword name: name of the backup data 284 @type name: str 285 @keyword backup_type: 'daily' or 'weekly' 286 @type backup_type: str 287 @keyword rotation: number of backups to maintain 288 @type rotation: int 289 @rtype: None 290 """ 291 self._http.post(self._url_resource_path, self._id, 'action', 292 data=utils.get_json_body( 293 "createBackup", 294 name=name, 295 backup_type=backup_type, 296 rotation=rotation))
297
298 - def live_migration(self, host=None, disk_over_commit=False):
299 """ 300 Move a server to another host without rebooting 301 302 @keyword host: Destination host 303 @type host: str 304 @keyword disk_over_commit: do disk over commit or not 305 @type disk_over_commit: bool 306 @rtype: None 307 """ 308 self._http.post(self._url_resource_path, self._id, 'action', 309 data=utils.get_json_body( 310 "os-migrateLive", 311 host=host, 312 block_migration=False, 313 disk_over_commit=disk_over_commit))
314
315 - def block_migration(self, host=None, disk_over_commit=False):
316 """ 317 Move a server to another host without rebooting, with disk copy 318 319 @keyword host: Destination host 320 @type host: str 321 @keyword disk_over_commit: do disk over commit or not 322 @type disk_over_commit: bool 323 @rtype: None 324 """ 325 self._http.post(self._url_resource_path, self._id, 'action', 326 data=utils.get_json_body( 327 "os-migrateLive", 328 host=host, 329 block_migration=True, 330 disk_over_commit=disk_over_commit))
331
332 - def evacuate(self, host=None, password=None, shared=True):
333 """ 334 Move a server to another host without rebooting, with disk copy 335 336 @keyword host: Destination host 337 @type host: str 338 @keyword password: new administrator password 339 @type password: str 340 @keyword shared: whether the vm is on the shared storage 341 @type shared: bool 342 @rtype: None 343 """ 344 self._http.post(self._url_resource_path, self._id, 'action', 345 data=utils.get_json_body( 346 "evacuate", 347 host=host, 348 adminPass=password, 349 onSharedStorage=shared))
350
351 - def reset_status(self, status=None):
352 """ 353 Move a server to another host 354 355 @keyword status: new status of the server ('active', 'pause', ...) 356 @type status: str 357 @rtype: None 358 """ 359 self._http.post(self._url_resource_path, self._id, 'action', 360 data=utils.get_json_body( 361 "os-resetState", state=status))
362
363 - def get_vnc_console(self, type='novnc'):
364 """ 365 Get VNC console 366 367 @keyword type: 'novnc' or 'xvpvnc' (required) 368 @type type: str 369 @return: Console information 370 @rtype: dict 371 """ 372 ret = self._http.post(self._url_resource_path, self._id, 'action', 373 data=utils.get_json_body( 374 "os-getVNCConsole", 375 type=type)) 376 return ret.get('console')
377
378 - def get_console_log(self, lines=50):
379 """ 380 Get console output 381 382 @keyword lines: number of lines 383 @type lines: int 384 @return: Console logs 385 @rtype: dict 386 """ 387 ret = self._http.post(self._url_resource_path, self._id, 'action', 388 data=utils.get_json_body( 389 "os-getConsoleOutput", 390 length=lines)) 391 return ret.get('output')
392
393 - def get_diagnostics(self):
394 """ 395 Get diagnostics 396 397 @return: Diagnostics 398 @rtype: dict 399 """ 400 return self._http.get(self._url_resource_path, self._id, 'diagnostics')
401
402 - def resize(self, flavor=None, disk_config='AUTO'):
403 """ 404 Get console output 405 406 @keyword flavor: Flavor (required) 407 @type flavor: yakumo.nova.v2.flavor.Resource 408 @keyword disk_config: disk configuration ('AUTO') 409 @type disk_config: str 410 @rtype: None 411 """ 412 self._http.post(self._url_resource_path, self._id, 'action', 413 data={"resize": { 414 "flavorRef": flavor.id, 415 "OS-DCF:diskConfig": disk_config}})
416
417 - def confirm_resize(self):
418 """ 419 Confirm resizing of a server 420 421 @rtype: None 422 """ 423 self._http.post(self._url_resource_path, self._id, 'action', 424 data=utils.get_json_body("confirmResize"))
425
426 - def revert_resize(self):
427 """ 428 Revert resizing of a server 429 430 @rtype: None 431 """ 432 self._http.post(self._url_resource_path, self._id, 'action', 433 data=utils.get_json_body("revertResize"))
434
435 - def rebuild(self, image=None, disk_config='AUTO', password=None, 436 ipv4=None, ipv6=None, personality=None):
437 """ 438 Rebuild a server 439 440 @keyword image: Image 441 @type image: yakumo.image.Resource 442 @keyword disk_config: disk configuration ('AUTO') 443 @type disk_config: str 444 @keyword password: admin password 445 @type password: str 446 @keyword ipv4: IPv4 address 447 @type ipv4: str 448 @keyword ipv6: IPv6 address 449 @type ipv6: str 450 @keyword persoality: personality data 451 @type persoality: [str] 452 453 @rtype: None 454 """ 455 json_body = utils.get_json_body( 456 "rebuild", 457 imageRef=image.id, 458 adminPass=password, 459 accessIPv4=ipv4, 460 accessIPv6=ipv6, 461 personality=personality) 462 if disk_config is not None: 463 json_body['rebuild']['OS-DCF:diskConfig'] = disk_config 464 465 self._http.post(self._url_resource_path, self._id, 'action', 466 data=json_body)
467
468 - def get_actions(self):
469 """ 470 Get instance actions 471 472 @rtype: dict 473 """ 474 ret = self._http.get(self._url_resource_path, self._id, 475 'os-instance-actions') 476 return ret.get("instanceActions")
477
478 - def get_password(self):
479 """ 480 Get instance password 481 482 @rtype: dict 483 """ 484 ret = self._http.get(self._url_resource_path, self._id, 485 'os-server-password') 486 return ret.get("password")
487
488 - def clear_password(self):
489 """ 490 Clear instance password 491 492 @rtype: None 493 """ 494 self._http.delete(self._url_resource_path, self._id, 495 'os-server-password')
496
497 - def get_security_groups(self):
498 """ 499 Get security group list for a server 500 501 @return: Security group list 502 @rtype: [str] 503 """ 504 ret = self._http.get(self._url_resource_path, self._id, 505 'os-security-groups') 506 return [self._client.security_group_nova. 507 get_empty(x.get('id')) 508 for x in ret.get('security_groups', [])]
509
510 - def get_metadata(self):
511 """ 512 Get instance metadata 513 514 @return: Metadata 515 @rtype: dict 516 """ 517 ret = self._http.get(self._url_resource_path, self._id, 'metadata') 518 return ret.get('metadata')
519
520 - def set_metadata(self, **metadata):
521 """ 522 Update instance metadata 523 524 @keyword metadata: key=value style. 525 @type metadata: dict 526 @rtype: None 527 """ 528 self._http.post(self._url_resource_path, self._id, 'metadata', 529 data={'metadata': metadata}) 530 self.reload()
531
532 - def unset_metadata(self, *keys):
533 """ 534 Delete instance metadata 535 536 @param key: key of the metadata 537 @type keys: [str] 538 @rtype: None 539 """ 540 for key in keys: 541 self._http.delete(self._url_resource_path, self._id, 542 'metadata', key) 543 self.reload()
544 545
546 -class Manager(base.Manager):
547 """Manager class for servers in Compute API v2""" 548 549 resource_class = Resource 550 service_type = 'compute' 551 _attr_mapping = ATTRIBUTE_MAPPING 552 _hidden_methods = ["update"] 553 _json_resource_key = 'server' 554 _json_resources_key = 'servers' 555 _url_resource_path = '/servers' 556 _url_resource_list_path = '/servers/detail' 557
558 - def _json2attr(self, json_params):
559 flavor_id = json_params.pop('flavor', {}).get('id') 560 ret = super(Manager, self)._json2attr(json_params) 561 if flavor_id: 562 ret['flavor'] = self._client.flavor.get_empty(flavor_id) 563 return ret
564
565 - def create(self, name=UNDEF, image=UNDEF, flavor=UNDEF, 566 personality=UNDEF, disks=UNDEF, max_count=UNDEF, 567 min_count=UNDEF, networks=UNDEF, security_groups=UNDEF, 568 availability_zone=UNDEF, metadata=UNDEF, 569 config_drive=UNDEF, key_pair=UNDEF, user_data=UNDEF):
570 """Create a new server 571 572 @keyword name: name of the new server (required) 573 @type name: str 574 @keyword flavor: Flavor object to use (required) 575 @type flavor: yakumo.nova.v2.flavor.Resource 576 @keyword image: Image object to use for ephemeral disk 577 @type image: yakumo.image.Resource 578 @keyword key_pair: KeyPair object to use 579 @type key_pair: yakumo.nova.v2.key_pair.Resource 580 @keyword networks: list of networks or ones with tag and/or fixed IP 581 @type networks: [yakumo.network.Resource] 582 @keyword security_groups: list of SecurityGroup object(s) to use 583 @type security_groups: [yakumo.nova.v2.security_group.Resource] 584 @keyword disks: block device mapping 585 @type disks: [dict] 586 @keyword personality: file path and the content to embed 587 @type personality: dict 588 @keyword max_count: the maximum number of server(s) to create 589 @type max_count: int 590 @keyword min_count: the minimun number of server(s) to create 591 @type min_count: int 592 @keyword availability_zone: Availability Zone 593 @type availability_zone: yakumo.availability_zone.Resource 594 @keyword metadata: Metadata 595 @type metadata: dict 596 @keyword config_drive: config drive exists or not (bool) 597 @type config_drive: bool 598 @keyword user_data: content of a batch file (str) 599 @type user_data: str 600 @return: Created server 601 @rtype: yakumo.nova.v2.server.Resource 602 """ 603 if networks == UNDEF: 604 networks = [] 605 if disks == UNDEF: 606 disks = [] 607 _networks = [] 608 for net in networks: 609 _network = {} 610 if isinstance(net, dict): 611 if 'tag' in net: 612 _network['tag'] = net['tag'] 613 if 'fixed_ip' in net: 614 _network['fixed_ip'] = net['fixed_ip'] 615 net = net.get('network', net.get('port')) 616 if isinstance(net, Network): 617 _network['uuid'] = net.get_id() 618 if isinstance(net, Port): 619 _network['port'] = net.get_id() 620 _networks.append(_network) 621 622 _disks = [] 623 boot_index = 0 624 for disk in disks: 625 _disk = {} 626 if 'tag' in disk: 627 _disk['tag'] = disk['tag'] 628 if 'size' in disk: 629 _disk['volume_size'] = disk['size'] 630 if 'source' in disk: 631 _disk['uuid'] = disk['source'].get_id() 632 if isinstance(disk['source'], Volume): 633 _disk['source_type'] = 'volume' 634 _disk['destination_type'] = 'volume' 635 elif isinstance(disk['source'], Snapshot): 636 _disk['source_type'] = 'snapshot' 637 _disk['destination_type'] = 'volume' 638 elif isinstance(disk['source'], Image): 639 _disk['source_type'] = 'image' 640 _disk['destination_type'] = \ 641 disk.get('destination_type', 'volume') 642 else: 643 _disk['source_type'] = 'blank' 644 _disk['destination_type'] = \ 645 disk.get('destination_type', 'volume') 646 if 'delete_on_termination' in disk: 647 _disk['delete_on_termination'] = \ 648 disk['delete_on_termination'] 649 if 'guest_format' in disk: 650 _disk['guest_format'] = disk['guest_format'] 651 _disk['boot_index'] = boot_index 652 _disks.append(_disk) 653 boot_index += 1 654 655 return super(Manager, self).create(name=name, image=image, 656 flavor=flavor, 657 personality=personality, 658 disks=_disks, 659 max_count=max_count, 660 min_count=min_count, 661 networks=_networks, 662 security_groups=security_groups, 663 availability_zone=availability_zone, 664 config_drive=config_drive, 665 key_pair=key_pair, 666 metadata=metadata, 667 user_data=user_data)
668