1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
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
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
100
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
134
143
152
161
170
179
188
197
206
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
235
244
253
262
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
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
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
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
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
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
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
425
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
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
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
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
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
519
531
544
545
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
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