Skip to content

Commit 583935b

Browse files
committed
Fetch IBM instance profiles
Create catalog (`ibm/vms.csv`) file from the IBM instance profiles fetched by the API. Unfortunately, it seems that currently, it is not possible to fetch pricing. There is the pricing API but it doesn't work for the `kind:instance.profile`. It works for the `kind:service` as described in the IBM documentation (https://cloud.ibm.com/docs/account?topic=account-getting-pricing-api).
1 parent a480f34 commit 583935b

File tree

1 file changed

+176
-0
lines changed

1 file changed

+176
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
"""A script that generates the Lambda Cloud catalog.
2+
3+
Usage:
4+
python fetch_ibm.py [-h] [--api-key API_KEY]
5+
[--api-key-path API_KEY_PATH]
6+
7+
If neither --api-key nor --api-key-path are provided, this script will parse
8+
`~/.ibm/credentials.yaml` to look for IBM API key (`iam_api_key`).
9+
"""
10+
import argparse
11+
import csv
12+
from datetime import datetime
13+
import json
14+
import os
15+
from typing import Dict, List, Optional, Tuple
16+
17+
import requests
18+
import yaml
19+
20+
TOKEN_ENDPOINT = 'https://iam.cloud.ibm.com/identity/token'
21+
REGIONS_ENDPOINT = f'https://us-south.iaas.cloud.ibm.com/v1/regions?version={datetime.today().strftime("%Y-%m-%d")}&generation=2' # pylint: disable=line-too-long
22+
ENDPOINT = 'https://cloud.lambdalabs.com/api/v1/instance-types'
23+
DEFAULT_IBM_CREDENTIALS_PATH = os.path.expanduser('~/.ibm/credentials.yaml')
24+
25+
26+
def _fetch_token(api_key: Optional[str] = None,
27+
api_key_path: Optional[str] = None,
28+
ibm_token: Optional[str] = None) -> str:
29+
if ibm_token is None:
30+
if api_key is None:
31+
if api_key_path is None:
32+
api_key_path = DEFAULT_IBM_CREDENTIALS_PATH
33+
with open(api_key_path, mode='r', encoding='utf-8') as f:
34+
ibm_cred_yaml = yaml.safe_load(f)
35+
api_key = ibm_cred_yaml['iam_api_key']
36+
37+
headers = {
38+
'Accept': 'application/json',
39+
}
40+
data = {
41+
'grant_type': 'urn:ibm:params:oauth:grant-type:apikey',
42+
'apikey': api_key,
43+
}
44+
response = requests.post(url=TOKEN_ENDPOINT, data=data, headers=headers)
45+
return response.json()['access_token']
46+
else:
47+
return ibm_token
48+
49+
50+
def _fetch_regions(ibm_token: str) -> List[str]:
51+
headers = {
52+
'Authorization': f'Bearer {ibm_token}',
53+
'Accept': 'application/json',
54+
}
55+
response = requests.get(url=REGIONS_ENDPOINT, headers=headers)
56+
regions_json = response.json()
57+
58+
regions = [r['name'] for r in regions_json['regions']]
59+
60+
print(f'regions: {regions}')
61+
return regions
62+
63+
64+
def _fetch_instance_profiles(regions: List[str],
65+
ibm_token: str) -> Dict[str, Tuple[List, List]]:
66+
"""Fetch instance profiles by region (map):
67+
{
68+
"region_name": (
69+
[list of available zones in the region],
70+
[list of available instance profiles in the region]
71+
)
72+
}
73+
"""
74+
d = datetime.today().strftime('%Y-%m-%d')
75+
76+
result = {}
77+
headers = {
78+
'Authorization': f'Bearer {ibm_token}',
79+
'Accept': 'application/json',
80+
}
81+
for r in regions:
82+
az_endpoint = f'https://{r}.iaas.cloud.ibm.com/v1/regions/{r}/zones?version={d}&generation=2' # pylint: disable=line-too-long
83+
az_response = requests.get(url=az_endpoint, headers=headers)
84+
az_response_json = az_response.json()
85+
zones = [a['name'] for a in az_response_json['zones']]
86+
print(f'Fetching instance profiles for region {r}, zones {zones}')
87+
88+
instances_endpoint = f'https://{r}.iaas.cloud.ibm.com/v1/instance/profiles?version={d}&generation=2' # pylint: disable=line-too-long
89+
instance_response = requests.get(url=instances_endpoint,
90+
headers=headers)
91+
instance_response_json = instance_response.json()
92+
instance_profiles = instance_response_json['profiles']
93+
result[r] = (zones, instance_profiles)
94+
95+
return result
96+
97+
98+
def create_catalog(region_profile: Dict[str, Tuple[List, List]],
99+
output_path: str) -> None:
100+
print(f'Create catalog file {output_path} based on the fetched profiles')
101+
with open(output_path, mode='w', encoding='utf-8') as f:
102+
writer = csv.writer(f, delimiter=',', quotechar='"')
103+
writer.writerow([
104+
'InstanceType', 'AcceleratorName', 'AcceleratorCount', 'vCPUs',
105+
'MemoryGiB', 'GpuInfo', 'Price', 'SpotPrice', 'Region',
106+
'AvailabilityZone'
107+
])
108+
109+
for region, (zones, profiles) in region_profile.items():
110+
print(f' adding region {region} instances')
111+
for profile in profiles:
112+
vm = profile['name']
113+
gpu: Optional[str] = None
114+
gpu_cnt: Optional[int] = None
115+
gpu_manufacturer: Optional[str] = None
116+
gpu_memory: Optional[int] = None
117+
if 'gpu_model' in profile:
118+
gpu = profile['gpu_model']['values'][0]
119+
if 'gpu_count' in profile:
120+
gpu_cnt = int(profile['gpu_count']['value'])
121+
if 'vcpu_count' in profile:
122+
vcpus = int(profile['vcpu_count']['value'])
123+
if 'memory' in profile:
124+
mem = int(profile['memory']['value'])
125+
if 'gpu_memory' in profile:
126+
gpu_memory = int(profile['gpu_memory']['value'])
127+
if 'gpu_manufacturer' in profile:
128+
gpu_manufacturer = profile['gpu_manufacturer']['values'][0]
129+
# TODO: How to fetch prices?
130+
# The pricing API doesn't return prices for instance.profile. # pylint: disable=line-too-long
131+
# https://cloud.ibm.com/docs/account?topic=account-getting-pricing-api # pylint: disable=line-too-long
132+
# https://globalcatalog.cloud.ibm.com/api/v1?q=kind:instance.profile # pylint: disable=line-too-long
133+
# https://globalcatalog.cloud.ibm.com/api/v1/gx2-16x128x1v100/plan # pylint: disable=line-too-long
134+
price = 0.0
135+
gpuinfo: Optional[str] = None
136+
gpu_memory_mb: Optional[int] = None
137+
gpu_memory_total_mb: Optional[int] = None
138+
if gpu_memory is not None:
139+
gpu_memory_mb = gpu_memory * 1024
140+
if gpu_cnt is not None:
141+
gpu_memory_total_mb = gpu_memory_mb * gpu_cnt
142+
# gpuinfo: Optional[str] = None
143+
if gpu is not None:
144+
gpuinfo_dict = {
145+
'Gpus': [{
146+
'Name': gpu,
147+
'Manufacturer': gpu_manufacturer,
148+
'Count': gpu_cnt,
149+
'MemoryInfo': {
150+
'SizeInMiB': gpu_memory_mb
151+
},
152+
}],
153+
'TotalGpuMemoryInMiB': gpu_memory_total_mb
154+
}
155+
gpuinfo = json.dumps(gpuinfo_dict).replace('"', "'") # pylint: disable=inconsistent-quotes,invalid-string-quote
156+
157+
for zone in zones:
158+
writer.writerow([
159+
vm, gpu, gpu_cnt, vcpus, mem, gpuinfo, price, '',
160+
region, zone
161+
])
162+
163+
164+
if __name__ == '__main__':
165+
parser = argparse.ArgumentParser()
166+
parser.add_argument('--api-key', help='IBM API key.')
167+
parser.add_argument('--api-key-path',
168+
help='path of file containing IBM Credentials.')
169+
args = parser.parse_args()
170+
os.makedirs('ibm', exist_ok=True)
171+
call_token = _fetch_token()
172+
call_regions = _fetch_regions(call_token)
173+
region_profiles_map = _fetch_instance_profiles(regions=call_regions,
174+
ibm_token=call_token)
175+
create_catalog(region_profiles_map, 'ibm/vms.csv')
176+
print('IBM Cloud catalog saved to ibm/vms.csv')

0 commit comments

Comments
 (0)