50 from nova.i18n import _ |
50 from nova.i18n import _ |
51 from nova.image import glance |
51 from nova.image import glance |
52 from nova.network import neutronv2 |
52 from nova.network import neutronv2 |
53 from nova import objects |
53 from nova import objects |
54 from nova.objects import flavor as flavor_obj |
54 from nova.objects import flavor as flavor_obj |
|
55 from nova.openstack.common import excutils |
55 from nova.openstack.common import fileutils |
56 from nova.openstack.common import fileutils |
56 from nova.openstack.common import jsonutils |
57 from nova.openstack.common import jsonutils |
57 from nova.openstack.common import log as logging |
58 from nova.openstack.common import log as logging |
58 from nova.openstack.common import loopingcall |
59 from nova.openstack.common import loopingcall |
59 from nova.openstack.common import processutils |
60 from nova.openstack.common import processutils |
67 |
68 |
68 solariszones_opts = [ |
69 solariszones_opts = [ |
69 cfg.StrOpt('glancecache_dirname', |
70 cfg.StrOpt('glancecache_dirname', |
70 default='$state_path/images', |
71 default='$state_path/images', |
71 help='Default path to Glance cache for Solaris Zones.'), |
72 help='Default path to Glance cache for Solaris Zones.'), |
|
73 cfg.StrOpt('live_migration_cipher', |
|
74 help='Cipher to use for encryption of memory traffic during ' |
|
75 'live migration. If not specified, a common encryption ' |
|
76 'algorithm will be negotiated. Options include: none or ' |
|
77 'the name of a supported OpenSSL cipher algorithm.'), |
72 cfg.StrOpt('solariszones_snapshots_directory', |
78 cfg.StrOpt('solariszones_snapshots_directory', |
73 default='$instances_path/snapshots', |
79 default='$instances_path/snapshots', |
74 help='Location where solariszones driver will store snapshots ' |
80 help='Location to store snapshots before uploading them to the ' |
75 'before uploading them to the Glance image service'), |
81 'Glance image service.'), |
76 ] |
82 ] |
77 |
83 |
78 CONF = cfg.CONF |
84 CONF = cfg.CONF |
79 CONF.register_opts(solariszones_opts) |
85 CONF.register_opts(solariszones_opts) |
80 CONF.import_opt('vncserver_proxyclient_address', 'nova.vnc') |
86 CONF.import_opt('vncserver_proxyclient_address', 'nova.vnc') |
119 ZONE_BRAND_SOLARIS: 'SYSdefault', |
125 ZONE_BRAND_SOLARIS: 'SYSdefault', |
120 ZONE_BRAND_SOLARIS_KZ: 'SYSsolaris-kz', |
126 ZONE_BRAND_SOLARIS_KZ: 'SYSsolaris-kz', |
121 } |
127 } |
122 |
128 |
123 MAX_CONSOLE_BYTES = 102400 |
129 MAX_CONSOLE_BYTES = 102400 |
|
130 |
124 VNC_CONSOLE_BASE_FMRI = 'svc:/application/openstack/nova/zone-vnc-console' |
131 VNC_CONSOLE_BASE_FMRI = 'svc:/application/openstack/nova/zone-vnc-console' |
125 # Required in order to create a zone VNC console SMF service instance |
132 # Required in order to create a zone VNC console SMF service instance |
126 VNC_SERVER_PATH = '/usr/bin/vncserver' |
133 VNC_SERVER_PATH = '/usr/bin/vncserver' |
127 XTERM_PATH = '/usr/bin/xterm' |
134 XTERM_PATH = '/usr/bin/xterm' |
|
135 |
|
136 # The underlying Solaris Zones framework does not expose a specific |
|
137 # version number, instead relying on feature tests to identify what is |
|
138 # and what is not supported. A HYPERVISOR_VERSION is defined here for |
|
139 # Nova's use but it generally should not be changed unless there is a |
|
140 # incompatible change such as concerning kernel zone live migration. |
|
141 HYPERVISOR_VERSION = '5.11' |
128 |
142 |
129 |
143 |
130 def lookup_resource_property(zone, resource, prop, filter=None): |
144 def lookup_resource_property(zone, resource, prop, filter=None): |
131 """Lookup specified property from specified Solaris Zone resource.""" |
145 """Lookup specified property from specified Solaris Zone resource.""" |
132 try: |
146 try: |
2304 :param block_device_info: instance block device information |
2319 :param block_device_info: instance block device information |
2305 :param network_info: instance network information |
2320 :param network_info: instance network information |
2306 :param disk_info: instance disk information |
2321 :param disk_info: instance disk information |
2307 :param migrate_data: implementation specific data dict. |
2322 :param migrate_data: implementation specific data dict. |
2308 """ |
2323 """ |
2309 raise NotImplementedError() |
2324 return {} |
|
2325 |
|
2326 def _live_migration(self, name, dest, dry_run=False): |
|
2327 """Live migration of a Solaris kernel zone to another host.""" |
|
2328 zone = self._get_zone_by_name(name) |
|
2329 if zone is None: |
|
2330 raise exception.InstanceNotFound(instance_id=name) |
|
2331 |
|
2332 options = [] |
|
2333 live_migration_cipher = CONF.live_migration_cipher |
|
2334 if live_migration_cipher is not None: |
|
2335 options.extend(['-c', live_migration_cipher]) |
|
2336 if dry_run: |
|
2337 options.append('-nq') |
|
2338 options.append('ssh://nova@' + dest) |
|
2339 zone.migrate(options) |
2310 |
2340 |
2311 def live_migration(self, context, instance, dest, |
2341 def live_migration(self, context, instance, dest, |
2312 post_method, recover_method, block_migration=False, |
2342 post_method, recover_method, block_migration=False, |
2313 migrate_data=None): |
2343 migrate_data=None): |
2314 """Live migration of an instance to another host. |
2344 """Live migration of an instance to another host. |
2326 expected nova.compute.manager._rollback_live_migration. |
2356 expected nova.compute.manager._rollback_live_migration. |
2327 :param block_migration: if true, migrate VM disk. |
2357 :param block_migration: if true, migrate VM disk. |
2328 :param migrate_data: implementation specific params. |
2358 :param migrate_data: implementation specific params. |
2329 |
2359 |
2330 """ |
2360 """ |
2331 raise NotImplementedError() |
2361 name = instance['name'] |
|
2362 try: |
|
2363 self._live_migration(name, dest, dry_run=False) |
|
2364 except Exception as ex: |
|
2365 with excutils.save_and_reraise_exception(): |
|
2366 LOG.error(_("Unable to live migrate instance '%s' to host " |
|
2367 "'%s' via zonemgr(3RAD): %s") |
|
2368 % (name, dest, ex)) |
|
2369 recover_method(context, instance, dest, block_migration) |
|
2370 |
|
2371 post_method(context, instance, dest, block_migration, migrate_data) |
2332 |
2372 |
2333 def rollback_live_migration_at_destination(self, context, instance, |
2373 def rollback_live_migration_at_destination(self, context, instance, |
2334 network_info, |
2374 network_info, |
2335 block_device_info, |
2375 block_device_info, |
2336 destroy_disks=True, |
2376 destroy_disks=True, |
2344 :param destroy_disks: |
2384 :param destroy_disks: |
2345 if true, destroy disks at destination during cleanup |
2385 if true, destroy disks at destination during cleanup |
2346 :param migrate_data: implementation specific params |
2386 :param migrate_data: implementation specific params |
2347 |
2387 |
2348 """ |
2388 """ |
2349 raise NotImplementedError() |
2389 pass |
2350 |
2390 |
2351 def post_live_migration(self, context, instance, block_device_info, |
2391 def post_live_migration(self, context, instance, block_device_info, |
2352 migrate_data=None): |
2392 migrate_data=None): |
2353 """Post operation of live migration at source host. |
2393 """Post operation of live migration at source host. |
2354 |
2394 |
2355 :param context: security context |
2395 :param context: security context |
2356 :instance: instance object that was migrated |
2396 :instance: instance object that was migrated |
2357 :block_device_info: instance block device information |
2397 :block_device_info: instance block device information |
2358 :param migrate_data: if not None, it is a dict which has data |
2398 :param migrate_data: if not None, it is a dict which has data |
2359 """ |
2399 """ |
2360 pass |
2400 try: |
|
2401 # These methods log if problems occur so no need to double log |
|
2402 # here. Just catch any stray exceptions and allow destroy to |
|
2403 # proceed. |
|
2404 if self._has_vnc_console_service(instance): |
|
2405 self._disable_vnc_console_service(instance) |
|
2406 self._delete_vnc_console_service(instance) |
|
2407 except Exception: |
|
2408 pass |
|
2409 |
|
2410 name = instance['name'] |
|
2411 zone = self._get_zone_by_name(name) |
|
2412 # If instance cannot be found, just return. |
|
2413 if zone is None: |
|
2414 LOG.warning(_("Unable to find instance '%s' via zonemgr(3RAD)") |
|
2415 % name) |
|
2416 return |
|
2417 |
|
2418 try: |
|
2419 self._delete_config(instance) |
|
2420 except Exception as ex: |
|
2421 LOG.error(_("Unable to delete configuration for instance '%s' via " |
|
2422 "zonemgr(3RAD): %s") % (name, ex)) |
|
2423 raise |
2361 |
2424 |
2362 def post_live_migration_at_source(self, context, instance, network_info): |
2425 def post_live_migration_at_source(self, context, instance, network_info): |
2363 """Unplug VIFs from networks at source. |
2426 """Unplug VIFs from networks at source. |
2364 |
2427 |
2365 :param context: security context |
2428 :param context: security context |
2424 :param dst_compute_info: Info about the receiving machine |
2487 :param dst_compute_info: Info about the receiving machine |
2425 :param block_migration: if true, prepare for block migration |
2488 :param block_migration: if true, prepare for block migration |
2426 :param disk_over_commit: if true, allow disk over commit |
2489 :param disk_over_commit: if true, allow disk over commit |
2427 :returns: a dict containing migration info (hypervisor-dependent) |
2490 :returns: a dict containing migration info (hypervisor-dependent) |
2428 """ |
2491 """ |
2429 raise NotImplementedError() |
2492 src_cpu_info = jsonutils.loads(src_compute_info['cpu_info']) |
|
2493 src_cpu_arch = src_cpu_info['arch'] |
|
2494 dst_cpu_info = jsonutils.loads(dst_compute_info['cpu_info']) |
|
2495 dst_cpu_arch = dst_cpu_info['arch'] |
|
2496 if src_cpu_arch != dst_cpu_arch: |
|
2497 reason = (_("CPU architectures between source host '%s' (%s) and " |
|
2498 "destination host '%s' (%s) are incompatible.") |
|
2499 % (src_compute_info['hypervisor_hostname'], src_cpu_arch, |
|
2500 dst_compute_info['hypervisor_hostname'], |
|
2501 dst_cpu_arch)) |
|
2502 raise exception.MigrationPreCheckError(reason=reason) |
|
2503 |
|
2504 extra_specs = self._get_extra_specs(instance) |
|
2505 brand = extra_specs.get('zonecfg:brand', ZONE_BRAND_SOLARIS) |
|
2506 if brand != ZONE_BRAND_SOLARIS_KZ: |
|
2507 # Only Solaris kernel zones are currently supported. |
|
2508 reason = (_("'%s' branded zones do not currently support live " |
|
2509 "migration.") % brand) |
|
2510 raise exception.MigrationPreCheckError(reason=reason) |
|
2511 |
|
2512 if block_migration: |
|
2513 reason = (_('Block migration is not currently supported.')) |
|
2514 raise exception.MigrationPreCheckError(reason=reason) |
|
2515 if disk_over_commit: |
|
2516 reason = (_('Disk overcommit is not currently supported.')) |
|
2517 raise exception.MigrationPreCheckError(reason=reason) |
|
2518 |
|
2519 dest_check_data = { |
|
2520 'hypervisor_hostname': dst_compute_info['hypervisor_hostname'] |
|
2521 } |
|
2522 return dest_check_data |
2430 |
2523 |
2431 def check_can_live_migrate_destination_cleanup(self, context, |
2524 def check_can_live_migrate_destination_cleanup(self, context, |
2432 dest_check_data): |
2525 dest_check_data): |
2433 """Do required cleanup on dest host after check_can_live_migrate calls |
2526 """Do required cleanup on dest host after check_can_live_migrate calls |
2434 |
2527 |
2435 :param context: security context |
2528 :param context: security context |
2436 :param dest_check_data: result of check_can_live_migrate_destination |
2529 :param dest_check_data: result of check_can_live_migrate_destination |
2437 """ |
2530 """ |
2438 raise NotImplementedError() |
2531 pass |
|
2532 |
|
2533 def _check_local_volumes_present(self, block_device_info): |
|
2534 """Check if local volumes are attached to the instance.""" |
|
2535 bmap = block_device_info.get('block_device_mapping') |
|
2536 for entry in bmap: |
|
2537 connection_info = entry['connection_info'] |
|
2538 driver_type = connection_info['driver_volume_type'] |
|
2539 if driver_type == 'local': |
|
2540 reason = (_("Instances with attached '%s' volumes are not " |
|
2541 "currently supported.") % driver_type) |
|
2542 raise exception.MigrationPreCheckError(reason=reason) |
2439 |
2543 |
2440 def check_can_live_migrate_source(self, context, instance, |
2544 def check_can_live_migrate_source(self, context, instance, |
2441 dest_check_data): |
2545 dest_check_data, block_device_info): |
2442 """Check if it is possible to execute live migration. |
2546 """Check if it is possible to execute live migration. |
2443 |
2547 |
2444 This checks if the live migration can succeed, based on the |
2548 This checks if the live migration can succeed, based on the |
2445 results from check_can_live_migrate_destination. |
2549 results from check_can_live_migrate_destination. |
2446 |
2550 |
2447 :param context: security context |
2551 :param context: security context |
2448 :param instance: nova.db.sqlalchemy.models.Instance |
2552 :param instance: nova.db.sqlalchemy.models.Instance |
2449 :param dest_check_data: result of check_can_live_migrate_destination |
2553 :param dest_check_data: result of check_can_live_migrate_destination |
|
2554 :param block_device_info: result of _get_instance_block_device_info |
2450 :returns: a dict containing migration info (hypervisor-dependent) |
2555 :returns: a dict containing migration info (hypervisor-dependent) |
2451 """ |
2556 """ |
2452 raise NotImplementedError() |
2557 self._check_local_volumes_present(block_device_info) |
|
2558 name = instance['name'] |
|
2559 dest = dest_check_data['hypervisor_hostname'] |
|
2560 try: |
|
2561 self._live_migration(name, dest, dry_run=True) |
|
2562 except Exception as ex: |
|
2563 raise exception.MigrationPreCheckError(reason=ex) |
|
2564 return dest_check_data |
2453 |
2565 |
2454 def get_instance_disk_info(self, instance_name, |
2566 def get_instance_disk_info(self, instance_name, |
2455 block_device_info=None): |
2567 block_device_info=None): |
2456 """Retrieve information about actual disk sizes of an instance. |
2568 """Retrieve information about actual disk sizes of an instance. |
2457 |
2569 |