class SoftLayer(object): def __init__(self, config, client=None): self.config = config if client is None: client = Client(auth=self.config['auth'], endpoint_url=self.config['endpoint_url']) self.client = client self.ssh = SshKeyManager(client) self.instances = CCIManager(client) @classmethod def get_config(cls): provider_conf = client_conf.get_client_settings() if 'SL_SSH_KEY' in os.environ: provider_conf['ssh_key'] = os.environ['SL_SSH_KEY'] if not ('auth' in provider_conf and 'endpoint_url' in provider_conf): raise ConfigError("Missing digital ocean api credentials") return provider_conf def get_ssh_keys(self): keys = map(SSHKey, self.ssh.list_keys()) if 'ssh_key' in self.config: keys = [k for k in keys if k.name == self.config['ssh_key']] log.debug("Using SoftLayer ssh keys: %s" % ", ".join(k.name for k in keys)) return keys def get_instances(self): return map(Instance, self.instances.list_instances()) def get_instance(self, instance_id): return Instance(self.instances.get_instance(instance_id)) def launch_instance(self, params): return Instance(self.instances.create_instance(**params)) def terminate_instance(self, instance_id): self.instances.cancel_instance(instance_id) def wait_on(self, instance): # Wait up to 5 minutes, in 30 sec increments result = self._wait_on_instance(instance, 30, 10) if not result: raise ProviderError("Could not provision instance before timeout") return result def _wait_on_instance(self, instance, limit, delay=10): # Redo cci.wait to give user feedback in verbose mode. for count, new_instance in enumerate(itertools.repeat(instance.id)): instance = self.get_instance(new_instance) if not instance.get('activeTransaction', {}).get('id') and \ instance.get('provisionDate'): return True if count >= limit: return False if count and count % 3 == 0: log.debug("Waiting for instance:%s ip:%s waited:%ds" % (instance.name, instance.ip_address, count * delay)) time.sleep(delay)
def on_get(self, req, resp, tenant_id, server_id): client = req.env["sl_client"] cci = CCIManager(client) instance = cci.get_instance(server_id, mask=get_virtual_guest_mask()) results = get_server_details_dict(self.app, req, instance) resp.body = {"server": results}
def on_get(self, req, resp, tenant_id, server_id): client = req.env['sl_client'] cci = CCIManager(client) instance = cci.get_instance(server_id, mask=get_virtual_guest_mask()) results = get_server_details_dict(self.app, req, instance) resp.body = {'server': results}
def _update_with_like_args(self, args): """ Update arguments with options taken from a currently running CCI. :param CCIManager args: A CCIManager :param dict args: CLI arguments """ if args['--like']: cci = CCIManager(self.client) cci_id = resolve_id(cci.resolve_ids, args.pop('--like'), 'CCI') like_details = cci.get_instance(cci_id) like_args = { '--hostname': like_details['hostname'], '--domain': like_details['domain'], '--cpu': like_details['maxCpu'], '--memory': like_details['maxMemory'], '--hourly': like_details['hourlyBillingFlag'], '--monthly': not like_details['hourlyBillingFlag'], '--datacenter': like_details['datacenter']['name'], '--network': like_details['networkComponents'][0]['maxSpeed'], '--user-data': like_details['userData'] or None, '--postinstall': like_details.get('postInstallScriptUri'), '--dedicated': like_details['dedicatedAccountHostOnlyFlag'], '--private': like_details['privateNetworkOnlyFlag'], } # Handle mutually exclusive options like_image = lookup(like_details, 'blockDeviceTemplateGroup', 'globalIdentifier') like_os = lookup(like_details, 'operatingSystem', 'softwareLicense', 'softwareDescription', 'referenceCode') if like_image and not args.get('--os'): like_args['--image'] = like_image elif like_os and not args.get('--image'): like_args['--os'] = like_os if args.get('--hourly'): like_args['--monthly'] = False if args.get('--monthly'): like_args['--hourly'] = False # Merge like CCI options with the options passed in for key, value in like_args.items(): if args.get(key) in [None, False]: args[key] = value
def on_put(self, req, resp, tenant_id, server_id): client = req.env["sl_client"] cci = CCIManager(client) body = json.loads(req.stream.read().decode()) if "name" in lookup(body, "server"): if lookup(body, "server", "name").strip() == "": return bad_request(resp, message="Server name is blank") cci.edit(server_id, hostname=lookup(body, "server", "name")) instance = cci.get_instance(server_id, mask=get_virtual_guest_mask()) results = get_server_details_dict(self.app, req, instance) resp.body = {"server": results}
def on_put(self, req, resp, tenant_id, server_id): client = req.env['sl_client'] cci = CCIManager(client) body = json.loads(req.stream.read().decode()) if 'name' in lookup(body, 'server'): if lookup(body, 'server', 'name').strip() == '': return bad_request(resp, message='Server name is blank') cci.edit(server_id, hostname=lookup(body, 'server', 'name')) instance = cci.get_instance(server_id, mask=get_virtual_guest_mask()) results = get_server_details_dict(self.app, req, instance) resp.body = {'server': results}
def on_get(self, req, resp, tenant_id, server_id, network_label): network_label = network_label.lower() network_mask = None if network_label == 'public': network_mask = 'primaryIpAddress' elif network_label == 'private': network_mask = 'primaryBackendIpAddress' else: return not_found(resp, message='Network does not exist') client = req.env['sl_client'] cci = CCIManager(client) instance = cci.get_instance(server_id, mask='id, ' + network_mask) resp.body = { network_label: [ {'version': 4, 'addr': instance[network_mask]}, ] }
def on_get(self, req, resp, tenant_id, server_id): client = req.env['sl_client'] cci = CCIManager(client) instance = cci.get_instance( server_id, mask='id, primaryIpAddress, primaryBackendIpAddress') addresses = {} if instance.get('primaryIpAddress'): addresses['public'] = [{ 'version': 4, 'addr': instance['primaryIpAddress'], }] if instance.get('primaryBackendIpAddress'): addresses['private'] = [{ 'version': 4, 'addr': instance['primaryBackendIpAddress'], }] resp.body = {'addresses': addresses}
def on_get(self, req, resp, tenant_id, server_id, network_label): network_label = network_label.lower() network_mask = None if network_label == 'public': network_mask = 'primaryIpAddress' elif network_label == 'private': network_mask = 'primaryBackendIpAddress' else: return not_found(resp, message='Network does not exist') client = req.env['sl_client'] cci = CCIManager(client) instance = cci.get_instance(server_id, mask='id, ' + network_mask) resp.body = { network_label: [ { 'version': 4, 'addr': instance[network_mask] }, ] }
def dns_sync(self, args): """ Sync DNS records to match the FQDN of the CCI """ dns = DNSManager(self.client) cci = CCIManager(self.client) cci_id = resolve_id(cci.resolve_ids, args.get('<identifier>'), 'CCI') instance = cci.get_instance(cci_id) zone_id = resolve_id(dns.resolve_ids, instance['domain'], name='zone') def sync_a_record(): """ Sync A record """ records = dns.get_records( zone_id, host=instance['hostname'], ) if not records: # don't have a record, lets add one to the base zone dns.create_record( zone['id'], instance['hostname'], 'a', instance['primaryIpAddress'], ttl=args['--ttl']) else: recs = [x for x in records if x['type'].lower() == 'a'] if len(recs) != 1: raise CLIAbort("Aborting A record sync, found %d " "A record exists!" % len(recs)) rec = recs[0] rec['data'] = instance['primaryIpAddress'] rec['ttl'] = args['--ttl'] dns.edit_record(rec) def sync_ptr_record(): """ Sync PTR record """ host_rec = instance['primaryIpAddress'].split('.')[-1] ptr_domains = self.client['Virtual_Guest'].\ getReverseDomainRecords(id=instance['id'])[0] edit_ptr = None for ptr in ptr_domains['resourceRecords']: if ptr['host'] == host_rec: ptr['ttl'] = args['--ttl'] edit_ptr = ptr break if edit_ptr: edit_ptr['data'] = instance['fullyQualifiedDomainName'] dns.edit_record(edit_ptr) else: dns.create_record( ptr_domains['id'], host_rec, 'ptr', instance['fullyQualifiedDomainName'], ttl=args['--ttl']) if not instance['primaryIpAddress']: raise CLIAbort('No primary IP address associated with this CCI') try: zone = dns.get_zone(zone_id) except DNSZoneNotFound: raise CLIAbort("Unable to create A record, " "no zone found matching: %s" % instance['domain']) go_for_it = args['--really'] or confirm( "Attempt to update DNS records for %s" % instance['fullyQualifiedDomainName']) if not go_for_it: raise CLIAbort("Aborting DNS sync") both = False if not args['--ptr'] and not args['-a']: both = True if both or args['-a']: sync_a_record() if both or args['--ptr']: sync_ptr_record()
def execute(self, args): cci = CCIManager(self.client) table = KeyValueTable(['Name', 'Value']) table.align['Name'] = 'r' table.align['Value'] = 'l' cci_id = resolve_id(cci.resolve_ids, args.get('<identifier>'), 'CCI') result = cci.get_instance(cci_id) result = NestedDict(result) table.add_row(['id', result['id']]) table.add_row(['hostname', result['fullyQualifiedDomainName']]) table.add_row(['status', FormattedItem( result['status']['keyName'] or blank(), result['status']['name'] or blank() )]) table.add_row(['active_transaction', active_txn(result)]) table.add_row(['state', FormattedItem( lookup(result, 'powerState', 'keyName'), lookup(result, 'powerState', 'name'), )]) table.add_row(['datacenter', result['datacenter']['name'] or blank()]) operating_system = lookup(result, 'operatingSystem', 'softwareLicense', 'softwareDescription') or {} table.add_row([ 'os', FormattedItem( operating_system.get('version') or blank(), operating_system.get('name') or blank() )]) table.add_row(['os_version', operating_system.get('version') or blank()]) table.add_row(['cores', result['maxCpu']]) table.add_row(['memory', mb_to_gb(result['maxMemory'])]) table.add_row(['public_ip', result['primaryIpAddress'] or blank()]) table.add_row(['private_ip', result['primaryBackendIpAddress'] or blank()]) table.add_row(['private_only', result['privateNetworkOnlyFlag']]) table.add_row(['private_cpu', result['dedicatedAccountHostOnlyFlag']]) table.add_row(['created', result['createDate']]) table.add_row(['modified', result['modifyDate']]) vlan_table = Table(['type', 'number', 'id']) for vlan in result['networkVlans']: vlan_table.add_row([ vlan['networkSpace'], vlan['vlanNumber'], vlan['id']]) table.add_row(['vlans', vlan_table]) if result.get('notes'): table.add_row(['notes', result['notes']]) if args.get('--price'): table.add_row(['price rate', result['billingItem']['recurringFee']]) if args.get('--passwords'): pass_table = Table(['username', 'password']) for item in result['operatingSystem']['passwords']: pass_table.add_row([item['username'], item['password']]) table.add_row(['users', pass_table]) tag_row = [] for tag in result['tagReferences']: tag_row.append(tag['tag']['name']) if tag_row: table.add_row(['tags', listing(tag_row, separator=',')]) if not result['privateNetworkOnlyFlag']: ptr_domains = self.client['Virtual_Guest'].\ getReverseDomainRecords(id=cci_id) for ptr_domain in ptr_domains: for ptr in ptr_domain['resourceRecords']: table.add_row(['ptr', ptr['data']]) return table
def on_post(self, req, resp, tenant_id, instance_id): body = json.loads(req.stream.read().decode()) if len(body) == 0: return bad_request(resp, message="Malformed request body") vg_client = req.env['sl_client']['Virtual_Guest'] cci = CCIManager(req.env['sl_client']) try: instance_id = int(instance_id) except ValueError: return not_found(resp, "Invalid instance ID specified.") instance = cci.get_instance(instance_id) if 'pause' in body or 'suspend' in body: try: vg_client.pause(id=instance_id) except SoftLayerAPIError as e: if 'Unable to pause instance' in e.faultString: return duplicate(resp, e.faultString) raise resp.status = 202 return elif 'unpause' in body or 'resume' in body: vg_client.resume(id=instance_id) resp.status = 202 return elif 'reboot' in body: if body['reboot'].get('type') == 'SOFT': vg_client.rebootSoft(id=instance_id) elif body['reboot'].get('type') == 'HARD': vg_client.rebootHard(id=instance_id) else: vg_client.rebootDefault(id=instance_id) resp.status = 202 return elif 'os-stop' in body: vg_client.powerOff(id=instance_id) resp.status = 202 return elif 'os-start' in body: vg_client.powerOn(id=instance_id) resp.status = 202 return elif 'createImage' in body: image_name = body['createImage']['name'] disks = [] for disk in filter(lambda x: x['device'] == '0', instance['blockDevices']): disks.append(disk) try: vg_client.createArchiveTransaction( image_name, disks, "Auto-created by OpenStack compatibility layer", id=instance_id, ) # Workaround for not having an image guid until the image is # fully created. TODO: Fix this cci.wait_for_transaction(instance_id, 300) _filter = { 'privateBlockDeviceTemplateGroups': { 'name': {'operation': image_name}, 'createDate': { 'operation': 'orderBy', 'options': [{'name': 'sort', 'value': ['DESC']}], } }} acct = req.env['sl_client']['Account'] matching_image = acct.getPrivateBlockDeviceTemplateGroups( mask='id, globalIdentifier', filter=_filter, limit=1) image_guid = matching_image.get('globalIdentifier') url = self.app.get_endpoint_url('image', req, 'v2_image', image_guid=image_guid) resp.status = 202 resp.set_header('location', url) except SoftLayerAPIError as e: compute_fault(resp, e.faultString) return elif 'os-getConsoleOutput' in body: resp.status = 501 return elif 'resize' in body: flavor_id = int(body['resize'].get('flavorRef')) if flavor_id not in FLAVORS: return bad_request(resp, message="Invalid flavor id in the " "request body") flavor = FLAVORS[flavor_id] cci.upgrade(instance_id, cpus=flavor['cpus'], memory=flavor['ram'] / 1024) resp.status = 202 return elif 'confirmResize' in body: resp.status = 204 return return bad_request(resp, message="There is no such action: %s" % list(body.keys()), code=400)
def on_post(self, req, resp, tenant_id, instance_id): body = json.loads(req.stream.read().decode()) if len(body) == 0: return bad_request(resp, message="Malformed request body") vg_client = req.env['sl_client']['Virtual_Guest'] cci = CCIManager(req.env['sl_client']) try: instance_id = int(instance_id) except ValueError: return not_found(resp, "Invalid instance ID specified.") instance = cci.get_instance(instance_id) if 'pause' in body or 'suspend' in body: try: vg_client.pause(id=instance_id) except SoftLayerAPIError as e: if 'Unable to pause instance' in e.faultString: return duplicate(resp, e.faultString) raise resp.status = 202 return elif 'unpause' in body or 'resume' in body: vg_client.resume(id=instance_id) resp.status = 202 return elif 'reboot' in body: if body['reboot'].get('type') == 'SOFT': vg_client.rebootSoft(id=instance_id) elif body['reboot'].get('type') == 'HARD': vg_client.rebootHard(id=instance_id) else: vg_client.rebootDefault(id=instance_id) resp.status = 202 return elif 'os-stop' in body: vg_client.powerOff(id=instance_id) resp.status = 202 return elif 'os-start' in body: vg_client.powerOn(id=instance_id) resp.status = 202 return elif 'createImage' in body: image_name = body['createImage']['name'] disks = [] for disk in filter(lambda x: x['device'] == '0', instance['blockDevices']): disks.append(disk) try: vg_client.createArchiveTransaction( image_name, disks, "Auto-created by OpenStack compatibility layer", id=instance_id, ) # Workaround for not having an image guid until the image is # fully created. TODO: Fix this cci.wait_for_transaction(instance_id, 300) _filter = { 'privateBlockDeviceTemplateGroups': { 'name': { 'operation': image_name }, 'createDate': { 'operation': 'orderBy', 'options': [{ 'name': 'sort', 'value': ['DESC'] }], } } } acct = req.env['sl_client']['Account'] matching_image = acct.getPrivateBlockDeviceTemplateGroups( mask='id, globalIdentifier', filter=_filter, limit=1) image_guid = matching_image.get('globalIdentifier') url = self.app.get_endpoint_url('image', req, 'v2_image', image_guid=image_guid) resp.status = 202 resp.set_header('location', url) except SoftLayerAPIError as e: compute_fault(resp, e.faultString) return elif 'os-getConsoleOutput' in body: resp.status = 501 return return bad_request(resp, message="There is no such action: %s" % list(body.keys()), code=400)
class CCITests(unittest.TestCase): def setUp(self): self.client = FixtureClient() self.cci = CCIManager(self.client) def test_list_instances(self): mcall = call(mask=ANY, filter={}) service = self.client['Account'] list_expected_ids = [100, 104] hourly_expected_ids = [104] monthly_expected_ids = [100] results = self.cci.list_instances(hourly=True, monthly=True) service.getVirtualGuests.assert_has_calls(mcall) for result in results: self.assertIn(result['id'], list_expected_ids) result = self.cci.list_instances(hourly=False, monthly=False) service.getVirtualGuests.assert_has_calls(mcall) for result in results: self.assertIn(result['id'], list_expected_ids) results = self.cci.list_instances(hourly=False, monthly=True) service.getMonthlyVirtualGuests.assert_has_calls(mcall) for result in results: self.assertIn(result['id'], monthly_expected_ids) results = self.cci.list_instances(hourly=True, monthly=False) service.getHourlyVirtualGuests.assert_has_calls(mcall) for result in results: self.assertIn(result['id'], hourly_expected_ids) def test_list_instances_with_filters(self): self.cci.list_instances( hourly=True, monthly=True, tags=['tag1', 'tag2'], cpus=2, memory=1024, hostname='hostname', domain='example.com', local_disk=True, datacenter='dal05', nic_speed=100, public_ip='1.2.3.4', private_ip='4.3.2.1', ) service = self.client['Account'] service.getVirtualGuests.assert_has_calls(call( filter={ 'virtualGuests': { 'datacenter': { 'name': {'operation': '_= dal05'}}, 'domain': {'operation': '_= example.com'}, 'tagReferences': { 'tag': {'name': { 'operation': 'in', 'options': [{ 'name': 'data', 'value': ['tag1', 'tag2']}]}}}, 'maxCpu': {'operation': 2}, 'localDiskFlag': {'operation': True}, 'maxMemory': {'operation': 1024}, 'hostname': {'operation': '_= hostname'}, 'networkComponents': {'maxSpeed': {'operation': 100}}, 'primaryIpAddress': {'operation': '_= 1.2.3.4'}, 'primaryBackendIpAddress': {'operation': '_= 4.3.2.1'} }}, mask=ANY, )) def test_resolve_ids_ip(self): service = self.client['Account'] _id = self.cci._get_ids_from_ip('172.16.240.2') self.assertEqual(_id, [100, 104]) _id = self.cci._get_ids_from_ip('nope') self.assertEqual(_id, []) # Now simulate a private IP test service.getVirtualGuests.side_effect = [[], [{'id': 99}]] _id = self.cci._get_ids_from_ip('10.0.1.87') self.assertEqual(_id, [99]) def test_resolve_ids_hostname(self): _id = self.cci._get_ids_from_hostname('cci-test1') self.assertEqual(_id, [100, 104]) def test_get_instance(self): result = self.cci.get_instance(100) self.client['Virtual_Guest'].getObject.assert_called_once_with( id=100, mask=ANY) self.assertEqual(Virtual_Guest.getObject, result) def test_get_create_options(self): results = self.cci.get_create_options() self.assertEqual(Virtual_Guest.getCreateObjectOptions, results) def test_cancel_instance(self): self.cci.cancel_instance(1) self.client['Virtual_Guest'].deleteObject.assert_called_once_with(id=1) def test_reload_instance(self): post_uri = 'http://test.sftlyr.ws/test.sh' self.cci.reload_instance(1, post_uri=post_uri, ssh_keys=[1701]) service = self.client['Virtual_Guest'] f = service.reloadOperatingSystem f.assert_called_once_with('FORCE', {'customProvisionScriptUri': post_uri, 'sshKeyIds': [1701]}, id=1) @patch('SoftLayer.managers.cci.CCIManager._generate_create_dict') def test_create_verify(self, create_dict): create_dict.return_value = {'test': 1, 'verify': 1} self.cci.verify_create_instance(test=1, verify=1) create_dict.assert_called_once_with(test=1, verify=1) f = self.client['Virtual_Guest'].generateOrderTemplate f.assert_called_once_with({'test': 1, 'verify': 1}) @patch('SoftLayer.managers.cci.CCIManager._generate_create_dict') def test_create_instance(self, create_dict): create_dict.return_value = {'test': 1, 'verify': 1} self.cci.create_instance(test=1, verify=1) create_dict.assert_called_once_with(test=1, verify=1) self.client['Virtual_Guest'].createObject.assert_called_once_with( {'test': 1, 'verify': 1}) def test_generate_os_and_image(self): self.assertRaises( ValueError, self.cci._generate_create_dict, cpus=1, memory=1, hostname='test', domain='example.com', os_code=1, image_id=1, ) def test_generate_missing(self): self.assertRaises(ValueError, self.cci._generate_create_dict) self.assertRaises(ValueError, self.cci._generate_create_dict, cpus=1) def test_generate_basic(self): data = self.cci._generate_create_dict( cpus=1, memory=1, hostname='test', domain='example.com', os_code="STRING", ) assert_data = { 'startCpus': 1, 'maxMemory': 1, 'hostname': 'test', 'domain': 'example.com', 'localDiskFlag': True, 'operatingSystemReferenceCode': "STRING", 'hourlyBillingFlag': True, } self.assertEqual(data, assert_data) def test_generate_monthly(self): data = self.cci._generate_create_dict( cpus=1, memory=1, hostname='test', domain='example.com', os_code="STRING", hourly=False, ) assert_data = { 'hourlyBillingFlag': False, 'startCpus': 1, 'maxMemory': 1, 'hostname': 'test', 'domain': 'example.com', 'localDiskFlag': True, 'operatingSystemReferenceCode': "STRING", } self.assertEqual(data, assert_data) def test_generate_image_id(self): data = self.cci._generate_create_dict( cpus=1, memory=1, hostname='test', domain='example.com', image_id="45", ) assert_data = { 'startCpus': 1, 'maxMemory': 1, 'hostname': 'test', 'domain': 'example.com', 'localDiskFlag': True, 'blockDeviceTemplateGroup': {"globalIdentifier": "45"}, 'hourlyBillingFlag': True, } self.assertEqual(data, assert_data) def test_generate_dedicated(self): data = self.cci._generate_create_dict( cpus=1, memory=1, hostname='test', domain='example.com', os_code="STRING", dedicated=True, ) assert_data = { 'startCpus': 1, 'maxMemory': 1, 'hostname': 'test', 'domain': 'example.com', 'localDiskFlag': True, 'operatingSystemReferenceCode': "STRING", 'hourlyBillingFlag': True, 'dedicatedAccountHostOnlyFlag': True, } self.assertEqual(data, assert_data) def test_generate_datacenter(self): data = self.cci._generate_create_dict( cpus=1, memory=1, hostname='test', domain='example.com', os_code="STRING", datacenter="sng01", ) assert_data = { 'startCpus': 1, 'maxMemory': 1, 'hostname': 'test', 'domain': 'example.com', 'localDiskFlag': True, 'operatingSystemReferenceCode': "STRING", 'hourlyBillingFlag': True, 'datacenter': {"name": 'sng01'}, } self.assertEqual(data, assert_data) def test_generate_public_vlan(self): data = self.cci._generate_create_dict( cpus=1, memory=1, hostname='test', domain='example.com', os_code="STRING", public_vlan=1, ) assert_data = { 'startCpus': 1, 'maxMemory': 1, 'hostname': 'test', 'domain': 'example.com', 'localDiskFlag': True, 'operatingSystemReferenceCode': "STRING", 'hourlyBillingFlag': True, 'primaryNetworkComponent': {"networkVlan": {"id": 1}}, } self.assertEqual(data, assert_data) def test_generate_private_vlan(self): data = self.cci._generate_create_dict( cpus=1, memory=1, hostname='test', domain='example.com', os_code="STRING", private_vlan=1, ) assert_data = { 'startCpus': 1, 'maxMemory': 1, 'hostname': 'test', 'domain': 'example.com', 'localDiskFlag': True, 'operatingSystemReferenceCode': "STRING", 'hourlyBillingFlag': True, 'primaryBackendNetworkComponent': {"networkVlan": {"id": 1}}, } self.assertEqual(data, assert_data) def test_generate_userdata(self): data = self.cci._generate_create_dict( cpus=1, memory=1, hostname='test', domain='example.com', os_code="STRING", userdata="ICANHAZCCI", ) assert_data = { 'startCpus': 1, 'maxMemory': 1, 'hostname': 'test', 'domain': 'example.com', 'localDiskFlag': True, 'operatingSystemReferenceCode': "STRING", 'hourlyBillingFlag': True, 'userData': [{'value': "ICANHAZCCI"}], } self.assertEqual(data, assert_data) def test_generate_network(self): data = self.cci._generate_create_dict( cpus=1, memory=1, hostname='test', domain='example.com', os_code="STRING", nic_speed=9001, ) assert_data = { 'startCpus': 1, 'maxMemory': 1, 'hostname': 'test', 'domain': 'example.com', 'localDiskFlag': True, 'operatingSystemReferenceCode': "STRING", 'hourlyBillingFlag': True, 'networkComponents': [{'maxSpeed': 9001}], } self.assertEqual(data, assert_data) def test_generate_private_network_only(self): data = self.cci._generate_create_dict( cpus=1, memory=1, hostname='test', domain='example.com', os_code="STRING", nic_speed=9001, private=True ) assert_data = { 'startCpus': 1, 'maxMemory': 1, 'hostname': 'test', 'domain': 'example.com', 'localDiskFlag': True, 'operatingSystemReferenceCode': "STRING", 'privateNetworkOnlyFlag': True, 'hourlyBillingFlag': True, 'networkComponents': [{'maxSpeed': 9001}], } self.assertEqual(data, assert_data) def test_generate_post_uri(self): data = self.cci._generate_create_dict( cpus=1, memory=1, hostname='test', domain='example.com', os_code="STRING", post_uri='https://example.com/boostrap.sh', ) assert_data = { 'startCpus': 1, 'maxMemory': 1, 'hostname': 'test', 'domain': 'example.com', 'localDiskFlag': True, 'operatingSystemReferenceCode': "STRING", 'hourlyBillingFlag': True, 'postInstallScriptUri': 'https://example.com/boostrap.sh', } self.assertEqual(data, assert_data) def test_generate_sshkey(self): data = self.cci._generate_create_dict( cpus=1, memory=1, hostname='test', domain='example.com', os_code="STRING", ssh_keys=[543], ) assert_data = { 'startCpus': 1, 'maxMemory': 1, 'hostname': 'test', 'domain': 'example.com', 'localDiskFlag': True, 'operatingSystemReferenceCode': "STRING", 'hourlyBillingFlag': True, 'sshKeys': [{'id': 543}], } self.assertEqual(data, assert_data) def test_generate_no_disks(self): data = self.cci._generate_create_dict( cpus=1, memory=1, hostname='test', domain='example.com', os_code="STRING" ) self.assertEqual(data.get('blockDevices'), None) def test_generate_single_disk(self): data = self.cci._generate_create_dict( cpus=1, memory=1, hostname='test', domain='example.com', os_code="STRING", disks=[50] ) assert_data = { 'blockDevices': [ {"device": "0", "diskImage": {"capacity": 50}}] } self.assertTrue(data.get('blockDevices')) self.assertEqual(data['blockDevices'], assert_data['blockDevices']) def test_generate_multi_disk(self): data = self.cci._generate_create_dict( cpus=1, memory=1, hostname='test', domain='example.com', os_code="STRING", disks=[50, 70, 100] ) assert_data = { 'blockDevices': [ {"device": "0", "diskImage": {"capacity": 50}}, {"device": "2", "diskImage": {"capacity": 70}}, {"device": "3", "diskImage": {"capacity": 100}}] } self.assertTrue(data.get('blockDevices')) self.assertEqual(data['blockDevices'], assert_data['blockDevices']) def test_change_port_speed_public(self): cci_id = 1 speed = 100 self.cci.change_port_speed(cci_id, True, speed) service = self.client['Virtual_Guest'] f = service.setPublicNetworkInterfaceSpeed f.assert_called_once_with(speed, id=cci_id) def test_change_port_speed_private(self): cci_id = 2 speed = 10 self.cci.change_port_speed(cci_id, False, speed) service = self.client['Virtual_Guest'] f = service.setPrivateNetworkInterfaceSpeed f.assert_called_once_with(speed, id=cci_id) def test_edit(self): # Test editing user data service = self.client['Virtual_Guest'] self.cci.edit(100, userdata='my data') service.setUserMetadata.assert_called_once_with(['my data'], id=100) # Now test a blank edit self.assertTrue(self.cci.edit, 100) # Finally, test a full edit args = { 'hostname': 'new-host', 'domain': 'new.sftlyr.ws', 'notes': 'random notes', } self.cci.edit(100, **args) service.editObject.assert_called_once_with(args, id=100)
class SoftLayer(object): def __init__(self, config, client=None): self.config = config if client is None: client = Client( auth=self.config['auth'], endpoint_url=self.config['endpoint_url']) self.client = client self.ssh = SshKeyManager(client) self.instances = CCIManager(client) @classmethod def get_config(cls): provider_conf = client_conf.get_client_settings() if 'SL_SSH_KEY' in os.environ: provider_conf['ssh_key'] = os.environ['SL_SSH_KEY'] if not ('auth' in provider_conf and 'endpoint_url' in provider_conf): raise ConfigError("Missing digital ocean api credentials") return provider_conf def get_ssh_keys(self): keys = map(SSHKey, self.ssh.list_keys()) if 'ssh_key' in self.config: keys = [k for k in keys if k.name == self.config['ssh_key']] log.debug( "Using SoftLayer ssh keys: %s" % ", ".join(k.name for k in keys)) return keys def get_instances(self): return map(Instance, self.instances.list_instances()) def get_instance(self, instance_id): return Instance(self.instances.get_instance(instance_id)) def launch_instance(self, params): return Instance(self.instances.create_instance(**params)) def terminate_instance(self, instance_id): self.instances.cancel_instance(instance_id) def wait_on(self, instance): # Wait up to 5 minutes, in 30 sec increments result = self._wait_on_instance(instance, 30, 10) if not result: raise ProviderError("Could not provision instance before timeout") return result def _wait_on_instance(self, instance, limit, delay=10): # Redo cci.wait to give user feedback in verbose mode. for count, new_instance in enumerate(itertools.repeat(instance.id)): instance = self.get_instance(new_instance) if not instance.get('activeTransaction', {}).get('id') and \ instance.get('provisionDate'): return True if count >= limit: return False if count and count % 3 == 0: log.debug("Waiting for instance:%s ip:%s waited:%ds" % ( instance.name, instance.ip_address, count*delay)) time.sleep(delay)
def on_post(self, req, resp, tenant_id, instance_id): body = json.loads(req.stream.read().decode()) if len(body) == 0: return bad_request(resp, message="Malformed request body") vg_client = req.env["sl_client"]["Virtual_Guest"] cci = CCIManager(req.env["sl_client"]) try: instance_id = int(instance_id) except ValueError: return not_found(resp, "Invalid instance ID specified.") instance = cci.get_instance(instance_id) if "pause" in body or "suspend" in body: try: vg_client.pause(id=instance_id) except SoftLayerAPIError as e: if "Unable to pause instance" in e.faultString: return duplicate(resp, e.faultString) raise resp.status = 202 return elif "unpause" in body or "resume" in body: vg_client.resume(id=instance_id) resp.status = 202 return elif "reboot" in body: if body["reboot"].get("type") == "SOFT": vg_client.rebootSoft(id=instance_id) elif body["reboot"].get("type") == "HARD": vg_client.rebootHard(id=instance_id) else: vg_client.rebootDefault(id=instance_id) resp.status = 202 return elif "os-stop" in body: vg_client.powerOff(id=instance_id) resp.status = 202 return elif "os-start" in body: vg_client.powerOn(id=instance_id) resp.status = 202 return elif "createImage" in body: image_name = body["createImage"]["name"] disks = [] for disk in filter(lambda x: x["device"] == "0", instance["blockDevices"]): disks.append(disk) try: vg_client.createArchiveTransaction( image_name, disks, "Auto-created by OpenStack compatibility layer", id=instance_id ) # Workaround for not having an image guid until the image is # fully created. TODO: Fix this cci.wait_for_transaction(instance_id, 300) _filter = { "privateBlockDeviceTemplateGroups": { "name": {"operation": image_name}, "createDate": {"operation": "orderBy", "options": [{"name": "sort", "value": ["DESC"]}]}, } } acct = req.env["sl_client"]["Account"] matching_image = acct.getPrivateBlockDeviceTemplateGroups( mask="id, globalIdentifier", filter=_filter, limit=1 ) image_guid = matching_image.get("globalIdentifier") url = self.app.get_endpoint_url("image", req, "v2_image", image_guid=image_guid) resp.status = 202 resp.set_header("location", url) except SoftLayerAPIError as e: compute_fault(resp, e.faultString) return elif "os-getConsoleOutput" in body: resp.status = 501 return return bad_request(resp, message="There is no such action: %s" % list(body.keys()), code=400)