Skip to content

Commit f74e45e

Browse files
Merge pull request #1304 from allmightyspiff/issues874
Added Migrate command
2 parents 0ff9f33 + 47f236c commit f74e45e

File tree

9 files changed

+193
-2
lines changed

9 files changed

+193
-2
lines changed

SoftLayer/CLI/dedicatedhost/detail.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
@click.option('--guests', is_flag=True, help='Show guests on dedicated host')
2020
@environment.pass_env
2121
def cli(env, identifier, price=False, guests=False):
22-
"""Get details for a virtual server."""
22+
"""Get details for a dedicated host."""
2323
dhost = SoftLayer.DedicatedHostManager(env.client)
2424

2525
table = formatting.KeyValueTable(['name', 'value'])

SoftLayer/CLI/routes.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
('virtual:credentials', 'SoftLayer.CLI.virt.credentials:cli'),
4747
('virtual:capacity', 'SoftLayer.CLI.virt.capacity:cli'),
4848
('virtual:placementgroup', 'SoftLayer.CLI.virt.placementgroup:cli'),
49+
('virtual:migrate', 'SoftLayer.CLI.virt.migrate:cli'),
4950

5051
('dedicatedhost', 'SoftLayer.CLI.dedicatedhost'),
5152
('dedicatedhost:list', 'SoftLayer.CLI.dedicatedhost.list:cli'),

SoftLayer/CLI/virt/migrate.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
"""Manage Migrations of Virtual Guests"""
2+
# :license: MIT, see LICENSE for more details.
3+
import click
4+
5+
import SoftLayer
6+
from SoftLayer.CLI import environment
7+
from SoftLayer.CLI import formatting
8+
from SoftLayer import utils
9+
10+
11+
@click.command()
12+
@click.option('--guest', '-g', type=click.INT, help="Guest ID to immediately migrate.")
13+
@click.option('--all', '-a', 'migrate_all', is_flag=True, default=False,
14+
help="Migrate ALL guests that require migration immediately.")
15+
@click.option('--host', '-h', type=click.INT,
16+
help="Dedicated Host ID to migrate to. Only works on guests that are already on a dedicated host.")
17+
@environment.pass_env
18+
def cli(env, guest, migrate_all, host):
19+
"""Manage VSIs that require migration. Can migrate Dedicated Host VSIs as well."""
20+
21+
vsi = SoftLayer.VSManager(env.client)
22+
pending_filter = {'virtualGuests': {'pendingMigrationFlag': {'operation': 1}}}
23+
dedicated_filter = {'virtualGuests': {'dedicatedHost': {'id': {'operation': 'not null'}}}}
24+
mask = """mask[
25+
id, hostname, domain, datacenter, pendingMigrationFlag, powerState,
26+
primaryIpAddress,primaryBackendIpAddress, dedicatedHost
27+
]"""
28+
29+
# No options, just print out a list of guests that can be migrated
30+
if not (guest or migrate_all):
31+
require_migration = vsi.list_instances(filter=pending_filter, mask=mask)
32+
require_table = formatting.Table(['id', 'hostname', 'domain', 'datacenter'], title="Require Migration")
33+
34+
for vsi_object in require_migration:
35+
require_table.add_row([
36+
vsi_object.get('id'),
37+
vsi_object.get('hostname'),
38+
vsi_object.get('domain'),
39+
utils.lookup(vsi_object, 'datacenter', 'name')
40+
])
41+
42+
if require_migration:
43+
env.fout(require_table)
44+
else:
45+
click.secho("No guests require migration at this time", fg='green')
46+
47+
migrateable = vsi.list_instances(filter=dedicated_filter, mask=mask)
48+
migrateable_table = formatting.Table(['id', 'hostname', 'domain', 'datacenter', 'Host Name', 'Host Id'],
49+
title="Dedicated Guests")
50+
for vsi_object in migrateable:
51+
migrateable_table.add_row([
52+
vsi_object.get('id'),
53+
vsi_object.get('hostname'),
54+
vsi_object.get('domain'),
55+
utils.lookup(vsi_object, 'datacenter', 'name'),
56+
utils.lookup(vsi_object, 'dedicatedHost', 'name'),
57+
utils.lookup(vsi_object, 'dedicatedHost', 'id')
58+
])
59+
env.fout(migrateable_table)
60+
# Migrate all guests with pendingMigrationFlag=True
61+
elif migrate_all:
62+
require_migration = vsi.list_instances(filter=pending_filter, mask="mask[id]")
63+
if not require_migration:
64+
click.secho("No guests require migration at this time", fg='green')
65+
for vsi_object in require_migration:
66+
migrate(vsi, vsi_object['id'])
67+
# Just migrate based on the options
68+
else:
69+
migrate(vsi, guest, host)
70+
71+
72+
def migrate(vsi_manager, vsi_id, host_id=None):
73+
"""Handles actually migrating virtual guests and handling the exception"""
74+
75+
try:
76+
if host_id:
77+
vsi_manager.migrate_dedicated(vsi_id, host_id)
78+
else:
79+
vsi_manager.migrate(vsi_id)
80+
click.secho("Started a migration on {}".format(vsi_id), fg='green')
81+
except SoftLayer.exceptions.SoftLayerAPIError as ex:
82+
click.secho("Failed to migrate {}. {}".format(vsi_id, str(ex)), fg='red')

SoftLayer/fixtures/SoftLayer_Virtual_Guest.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -767,3 +767,6 @@
767767
}
768768
}
769769
]
770+
771+
migrate = True
772+
migrateDedicatedHost = True

SoftLayer/managers/vs.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1159,6 +1159,26 @@ def get_local_disks(self, instance_id):
11591159
mask = 'mask[diskImage]'
11601160
return self.guest.getBlockDevices(mask=mask, id=instance_id)
11611161

1162+
def migrate(self, instance_id):
1163+
"""Calls SoftLayer_Virtual_Guest::migrate
1164+
1165+
Only actually does anything if the virtual server requires a migration.
1166+
Will return an exception otherwise.
1167+
1168+
:param int instance_id: Id of the virtual server
1169+
"""
1170+
return self.guest.migrate(id=instance_id)
1171+
1172+
def migrate_dedicated(self, instance_id, host_id):
1173+
"""Calls SoftLayer_Virtual_Guest::migrate
1174+
1175+
Only actually does anything if the virtual server requires a migration.
1176+
Will return an exception otherwise.
1177+
1178+
:param int instance_id: Id of the virtual server
1179+
"""
1180+
return self.guest.migrateDedicatedHost(host_id, id=instance_id)
1181+
11621182
def get_hardware_guests(self):
11631183
"""Returns all virtualHost capable hardware objects and their guests.
11641184

SoftLayer/testing/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,15 @@ def assert_called_with(self, service, method, **props):
142142

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

145+
def assert_not_called_with(self, service, method, **props):
146+
"""Used to assert that API calls were NOT called with given properties.
147+
148+
Props are properties of the given transport.Request object.
149+
"""
150+
151+
if self.calls(service, method, **props):
152+
raise AssertionError('%s::%s was called with given properties: %s' % (service, method, props))
153+
145154
def assert_no_fail(self, result):
146155
"""Fail when a failing click result has an error"""
147156
if result.exception:

docs/cli/vs.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,11 @@ If no timezone is specified, IMS local time (CST) will be assumed, which might n
264264
:prog: virtual credentials
265265
:show-nested:
266266

267+
.. click:: SoftLayer.CLI.virt.migrate:cli
268+
:prog: virtual migrate
269+
:show-nested:
270+
271+
Manages the migration of virutal guests. Supports migrating virtual guests on Dedicated Hosts as well.
267272

268273
Reserved Capacity
269274
-----------------

tests/CLI/modules/vs/vs_tests.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -802,3 +802,65 @@ def test_billing(self):
802802
}
803803
self.assert_no_fail(result)
804804
self.assertEqual(json.loads(result.output), vir_billing)
805+
806+
def test_vs_migrate_list(self):
807+
result = self.run_command(['vs', 'migrate'])
808+
self.assert_no_fail(result)
809+
self.assert_called_with('SoftLayer_Account', 'getVirtualGuests')
810+
self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrate')
811+
self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost')
812+
813+
def test_vs_migrate_list_empty(self):
814+
mock = self.set_mock('SoftLayer_Account', 'getVirtualGuests')
815+
mock.return_value = []
816+
result = self.run_command(['vs', 'migrate'])
817+
self.assert_no_fail(result)
818+
self.assert_called_with('SoftLayer_Account', 'getVirtualGuests')
819+
self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrate')
820+
self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost')
821+
self.assertIn("No guests require migration at this time", result.output)
822+
823+
def test_vs_migrate_guest(self):
824+
result = self.run_command(['vs', 'migrate', '-g', '100'])
825+
self.assert_no_fail(result)
826+
self.assertIn('Started a migration on', result.output)
827+
self.assert_not_called_with('SoftLayer_Account', 'getVirtualGuests')
828+
self.assert_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=100)
829+
self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost')
830+
831+
def test_vs_migrate_all(self):
832+
result = self.run_command(['vs', 'migrate', '-a'])
833+
self.assert_no_fail(result)
834+
self.assertIn('Started a migration on', result.output)
835+
self.assert_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=100)
836+
self.assert_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=104)
837+
self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost')
838+
839+
def test_vs_migrate_all_empty(self):
840+
mock = self.set_mock('SoftLayer_Account', 'getVirtualGuests')
841+
mock.return_value = []
842+
result = self.run_command(['vs', 'migrate', '-a'])
843+
self.assert_no_fail(result)
844+
self.assertIn('No guests require migration at this time', result.output)
845+
self.assert_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=100)
846+
self.assert_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=104)
847+
self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost')
848+
849+
def test_vs_migrate_dedicated(self):
850+
result = self.run_command(['vs', 'migrate', '-g', '100', '-h', '999'])
851+
self.assert_no_fail(result)
852+
self.assertIn('Started a migration on', result.output)
853+
self.assert_not_called_with('SoftLayer_Account', 'getVirtualGuests')
854+
self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=100)
855+
self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost', args=(999), identifier=100)
856+
857+
def test_vs_migrate_exception(self):
858+
ex = SoftLayerAPIError('SoftLayer_Exception', 'PROBLEM')
859+
mock = self.set_mock('SoftLayer_Virtual_Guest', 'migrate')
860+
mock.side_effect = ex
861+
result = self.run_command(['vs', 'migrate', '-g', '100'])
862+
self.assert_no_fail(result)
863+
self.assertIn('Failed to migrate', result.output)
864+
self.assert_not_called_with('SoftLayer_Account', 'getVirtualGuests')
865+
self.assert_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=100)
866+
self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost', args=(999), identifier=100)

tests/managers/vs/vs_tests.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1133,6 +1133,16 @@ def test_get_local_disks_swap(self):
11331133
}
11341134
], result)
11351135

1136+
def test_migrate(self):
1137+
result = self.vs.migrate(1234)
1138+
self.assertTrue(result)
1139+
self.assert_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=1234)
1140+
1141+
def test_migrate_dedicated(self):
1142+
result = self.vs.migrate_dedicated(1234, 5555)
1143+
self.assertTrue(result)
1144+
self.assert_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost', args=(5555,), identifier=1234)
1145+
11361146
def test_get_hardware_guests(self):
11371147
mock = self.set_mock('SoftLayer_Account', 'getHardware')
11381148
mock.return_value = [{
@@ -1163,5 +1173,4 @@ def test_get_hardware_guests(self):
11631173
}]}}]
11641174

11651175
result = self.vs.get_hardware_guests()
1166-
11671176
self.assertEqual("NSX-T Manager", result[0]['virtualHost']['guests'][0]['hostname'])

0 commit comments

Comments
 (0)