Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion SoftLayer/CLI/dedicatedhost/detail.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
@click.option('--guests', is_flag=True, help='Show guests on dedicated host')
@environment.pass_env
def cli(env, identifier, price=False, guests=False):
"""Get details for a virtual server."""
"""Get details for a dedicated host."""
dhost = SoftLayer.DedicatedHostManager(env.client)

table = formatting.KeyValueTable(['name', 'value'])
Expand Down
1 change: 1 addition & 0 deletions SoftLayer/CLI/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
('virtual:credentials', 'SoftLayer.CLI.virt.credentials:cli'),
('virtual:capacity', 'SoftLayer.CLI.virt.capacity:cli'),
('virtual:placementgroup', 'SoftLayer.CLI.virt.placementgroup:cli'),
('virtual:migrate', 'SoftLayer.CLI.virt.migrate:cli'),

('dedicatedhost', 'SoftLayer.CLI.dedicatedhost'),
('dedicatedhost:list', 'SoftLayer.CLI.dedicatedhost.list:cli'),
Expand Down
82 changes: 82 additions & 0 deletions SoftLayer/CLI/virt/migrate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
"""Manage Migrations of Virtual Guests"""
# :license: MIT, see LICENSE for more details.
import click

import SoftLayer
from SoftLayer.CLI import environment
from SoftLayer.CLI import formatting
from SoftLayer import utils


@click.command()
@click.option('--guest', '-g', type=click.INT, help="Guest ID to immediately migrate.")
@click.option('--all', '-a', 'migrate_all', is_flag=True, default=False,
help="Migrate ALL guests that require migration immediately.")
@click.option('--host', '-h', type=click.INT,
help="Dedicated Host ID to migrate to. Only works on guests that are already on a dedicated host.")
@environment.pass_env
def cli(env, guest, migrate_all, host):
"""Manage VSIs that require migration. Can migrate Dedicated Host VSIs as well."""

vsi = SoftLayer.VSManager(env.client)
pending_filter = {'virtualGuests': {'pendingMigrationFlag': {'operation': 1}}}
dedicated_filter = {'virtualGuests': {'dedicatedHost': {'id': {'operation': 'not null'}}}}
mask = """mask[
id, hostname, domain, datacenter, pendingMigrationFlag, powerState,
primaryIpAddress,primaryBackendIpAddress, dedicatedHost
]"""

# No options, just print out a list of guests that can be migrated
if not (guest or migrate_all):
require_migration = vsi.list_instances(filter=pending_filter, mask=mask)
require_table = formatting.Table(['id', 'hostname', 'domain', 'datacenter'], title="Require Migration")

for vsi_object in require_migration:
require_table.add_row([
vsi_object.get('id'),
vsi_object.get('hostname'),
vsi_object.get('domain'),
utils.lookup(vsi_object, 'datacenter', 'name')
])

if require_migration:
env.fout(require_table)
else:
click.secho("No guests require migration at this time", fg='green')

migrateable = vsi.list_instances(filter=dedicated_filter, mask=mask)
migrateable_table = formatting.Table(['id', 'hostname', 'domain', 'datacenter', 'Host Name', 'Host Id'],
title="Dedicated Guests")
for vsi_object in migrateable:
migrateable_table.add_row([
vsi_object.get('id'),
vsi_object.get('hostname'),
vsi_object.get('domain'),
utils.lookup(vsi_object, 'datacenter', 'name'),
utils.lookup(vsi_object, 'dedicatedHost', 'name'),
utils.lookup(vsi_object, 'dedicatedHost', 'id')
])
env.fout(migrateable_table)
# Migrate all guests with pendingMigrationFlag=True
elif migrate_all:
require_migration = vsi.list_instances(filter=pending_filter, mask="mask[id]")
if not require_migration:
click.secho("No guests require migration at this time", fg='green')
for vsi_object in require_migration:
migrate(vsi, vsi_object['id'])
# Just migrate based on the options
else:
migrate(vsi, guest, host)


def migrate(vsi_manager, vsi_id, host_id=None):
"""Handles actually migrating virtual guests and handling the exception"""

try:
if host_id:
vsi_manager.migrate_dedicated(vsi_id, host_id)
else:
vsi_manager.migrate(vsi_id)
click.secho("Started a migration on {}".format(vsi_id), fg='green')
except SoftLayer.exceptions.SoftLayerAPIError as ex:
click.secho("Failed to migrate {}. {}".format(vsi_id, str(ex)), fg='red')
3 changes: 3 additions & 0 deletions SoftLayer/fixtures/SoftLayer_Virtual_Guest.py
Original file line number Diff line number Diff line change
Expand Up @@ -767,3 +767,6 @@
}
}
]

migrate = True
migrateDedicatedHost = True
20 changes: 20 additions & 0 deletions SoftLayer/managers/vs.py
Original file line number Diff line number Diff line change
Expand Up @@ -1159,6 +1159,26 @@ def get_local_disks(self, instance_id):
mask = 'mask[diskImage]'
return self.guest.getBlockDevices(mask=mask, id=instance_id)

def migrate(self, instance_id):
"""Calls SoftLayer_Virtual_Guest::migrate

Only actually does anything if the virtual server requires a migration.
Will return an exception otherwise.

:param int instance_id: Id of the virtual server
"""
return self.guest.migrate(id=instance_id)

def migrate_dedicated(self, instance_id, host_id):
"""Calls SoftLayer_Virtual_Guest::migrate

Only actually does anything if the virtual server requires a migration.
Will return an exception otherwise.

:param int instance_id: Id of the virtual server
"""
return self.guest.migrateDedicatedHost(host_id, id=instance_id)

def get_hardware_guests(self):
"""Returns all virtualHost capable hardware objects and their guests.

Expand Down
9 changes: 9 additions & 0 deletions SoftLayer/testing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,15 @@ def assert_called_with(self, service, method, **props):

raise AssertionError('%s::%s was not called with given properties: %s' % (service, method, props))

def assert_not_called_with(self, service, method, **props):
"""Used to assert that API calls were NOT called with given properties.

Props are properties of the given transport.Request object.
"""

if self.calls(service, method, **props):
raise AssertionError('%s::%s was called with given properties: %s' % (service, method, props))

def assert_no_fail(self, result):
"""Fail when a failing click result has an error"""
if result.exception:
Expand Down
5 changes: 5 additions & 0 deletions docs/cli/vs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,11 @@ If no timezone is specified, IMS local time (CST) will be assumed, which might n
:prog: virtual credentials
:show-nested:

.. click:: SoftLayer.CLI.virt.migrate:cli
:prog: virtual migrate
:show-nested:

Manages the migration of virutal guests. Supports migrating virtual guests on Dedicated Hosts as well.

Reserved Capacity
-----------------
Expand Down
62 changes: 62 additions & 0 deletions tests/CLI/modules/vs/vs_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -802,3 +802,65 @@ def test_billing(self):
}
self.assert_no_fail(result)
self.assertEqual(json.loads(result.output), vir_billing)

def test_vs_migrate_list(self):
result = self.run_command(['vs', 'migrate'])
self.assert_no_fail(result)
self.assert_called_with('SoftLayer_Account', 'getVirtualGuests')
self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrate')
self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost')

def test_vs_migrate_list_empty(self):
mock = self.set_mock('SoftLayer_Account', 'getVirtualGuests')
mock.return_value = []
result = self.run_command(['vs', 'migrate'])
self.assert_no_fail(result)
self.assert_called_with('SoftLayer_Account', 'getVirtualGuests')
self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrate')
self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost')
self.assertIn("No guests require migration at this time", result.output)

def test_vs_migrate_guest(self):
result = self.run_command(['vs', 'migrate', '-g', '100'])
self.assert_no_fail(result)
self.assertIn('Started a migration on', result.output)
self.assert_not_called_with('SoftLayer_Account', 'getVirtualGuests')
self.assert_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=100)
self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost')

def test_vs_migrate_all(self):
result = self.run_command(['vs', 'migrate', '-a'])
self.assert_no_fail(result)
self.assertIn('Started a migration on', result.output)
self.assert_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=100)
self.assert_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=104)
self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost')

def test_vs_migrate_all_empty(self):
mock = self.set_mock('SoftLayer_Account', 'getVirtualGuests')
mock.return_value = []
result = self.run_command(['vs', 'migrate', '-a'])
self.assert_no_fail(result)
self.assertIn('No guests require migration at this time', result.output)
self.assert_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=100)
self.assert_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=104)
self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost')

def test_vs_migrate_dedicated(self):
result = self.run_command(['vs', 'migrate', '-g', '100', '-h', '999'])
self.assert_no_fail(result)
self.assertIn('Started a migration on', result.output)
self.assert_not_called_with('SoftLayer_Account', 'getVirtualGuests')
self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=100)
self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost', args=(999), identifier=100)

def test_vs_migrate_exception(self):
ex = SoftLayerAPIError('SoftLayer_Exception', 'PROBLEM')
mock = self.set_mock('SoftLayer_Virtual_Guest', 'migrate')
mock.side_effect = ex
result = self.run_command(['vs', 'migrate', '-g', '100'])
self.assert_no_fail(result)
self.assertIn('Failed to migrate', result.output)
self.assert_not_called_with('SoftLayer_Account', 'getVirtualGuests')
self.assert_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=100)
self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost', args=(999), identifier=100)
11 changes: 10 additions & 1 deletion tests/managers/vs/vs_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -1133,6 +1133,16 @@ def test_get_local_disks_swap(self):
}
], result)

def test_migrate(self):
result = self.vs.migrate(1234)
self.assertTrue(result)
self.assert_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=1234)

def test_migrate_dedicated(self):
result = self.vs.migrate_dedicated(1234, 5555)
self.assertTrue(result)
self.assert_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost', args=(5555,), identifier=1234)

def test_get_hardware_guests(self):
mock = self.set_mock('SoftLayer_Account', 'getHardware')
mock.return_value = [{
Expand Down Expand Up @@ -1163,5 +1173,4 @@ def test_get_hardware_guests(self):
}]}}]

result = self.vs.get_hardware_guests()

self.assertEqual("NSX-T Manager", result[0]['virtualHost']['guests'][0]['hostname'])