ff5ca6a6e22ecc4d3154b81fa644c411df39d4c5
[multicloud/azure.git] / azure / multicloud_azure / pub / utils / restcall.py
1 # Copyright (c) 2018 Amdocs
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at:
6 #
7 #       http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
13 import sys
14 import traceback
15 import logging
16 import urllib2
17 import uuid
18 import httplib2
19 import json
20
21 from multicloud_azure.pub.config.config import AAI_SCHEMA_VERSION
22 from multicloud_azure.pub.config.config import AAI_SERVICE_URL
23 from multicloud_azure.pub.config.config import AAI_USERNAME
24 from multicloud_azure.pub.config.config import AAI_PASSWORD
25 from multicloud_azure.pub.config.config import MSB_SERVICE_IP, MSB_SERVICE_PORT
26
27 from multicloud_azure.pub.exceptions import VimDriverAzureException
28
29 rest_no_auth, rest_oneway_auth, rest_bothway_auth = 0, 1, 2
30 HTTP_200_OK, HTTP_201_CREATED = '200', '201'
31 HTTP_204_NO_CONTENT, HTTP_202_ACCEPTED = '204', '202'
32 status_ok_list = [HTTP_200_OK, HTTP_201_CREATED,
33                   HTTP_204_NO_CONTENT, HTTP_202_ACCEPTED]
34 HTTP_404_NOTFOUND, HTTP_403_FORBIDDEN = '404', '403'
35 HTTP_401_UNAUTHORIZED, HTTP_400_BADREQUEST = '401', '400'
36
37 logger = logging.getLogger(__name__)
38
39
40 def call_req(base_url, user, passwd, auth_type, resource, method, content='',
41              headers=None):
42     callid = str(uuid.uuid1())
43 #    logger.debug("[%s]call_req('%s','%s','%s',%s,'%s','%s','%s')" % (
44 #        callid, base_url, user, passwd, auth_type, resource, method, content))
45     ret = None
46     resp_status = ''
47     resp = ""
48     full_url = ""
49
50     try:
51         full_url = combine_url(base_url, resource)
52         if headers is None:
53             headers = {}
54             headers['content-type'] = 'application/json'
55
56         if user:
57             headers['Authorization'] = 'Basic ' + \
58                 ('%s:%s' % (user, passwd)).encode("base64")
59         ca_certs = None
60         for retry_times in range(3):
61             http = httplib2.Http(
62                 ca_certs=ca_certs,
63                 disable_ssl_certificate_validation=(
64                     auth_type == rest_no_auth))
65             http.follow_all_redirects = True
66             try:
67                 logger.debug("request=%s" % full_url)
68                 resp, resp_content = http.request(
69                     full_url, method=method.upper(), body=content,
70                     headers=headers)
71                 resp_status = resp['status']
72                 resp_body = resp_content.decode('UTF-8')
73
74                 if resp_status in status_ok_list:
75                     ret = [0, resp_body, resp_status, resp]
76                 else:
77                     ret = [1, resp_body, resp_status, resp]
78                 break
79             except Exception as ex:
80                 if 'httplib.ResponseNotReady' in str(sys.exc_info()):
81                     logger.error(traceback.format_exc())
82                     ret = [1, "Unable to connect to %s" % full_url,
83                            resp_status, resp]
84                     continue
85                 raise ex
86     except urllib2.URLError as err:
87         ret = [2, str(err), resp_status, resp]
88     except Exception as ex:
89         logger.error(traceback.format_exc())
90         logger.error("[%s]ret=%s" % (callid, str(sys.exc_info())))
91         res_info = str(sys.exc_info())
92         if 'httplib.ResponseNotReady' in res_info:
93             res_info = ("The URL[%s] request failed or is not responding." %
94                         full_url)
95         ret = [3, res_info, resp_status, resp]
96 #    logger.debug("[%s]ret=%s" % (callid, str(ret)))
97     return ret
98
99
100 def req_by_msb(resource, method, content=''):
101     base_url = "http://%s:%s/" % (MSB_SERVICE_IP, MSB_SERVICE_PORT)
102     return call_req(base_url, "", "", rest_no_auth, resource, method, content)
103
104
105 def combine_url(base_url, resource):
106     full_url = None
107     if base_url.endswith('/') and resource.startswith('/'):
108         full_url = base_url[:-1] + resource
109     elif base_url.endswith('/') and not resource.startswith('/'):
110         full_url = base_url + resource
111     elif not base_url.endswith('/') and resource.startswith('/'):
112         full_url = base_url + resource
113     else:
114         full_url = base_url + '/' + resource
115     return full_url
116
117
118 def get_res_from_aai(resource, content=''):
119     headers = {
120         'X-FromAppId': 'MultiCloud',
121         'X-TransactionId': '9001',
122         'content-type': 'application/json',
123         'accept': 'application/json'
124     }
125     base_url = "%s/%s" % (AAI_SERVICE_URL, AAI_SCHEMA_VERSION)
126     return call_req(base_url, AAI_USERNAME, AAI_PASSWORD, rest_no_auth,
127                     resource, "GET", content, headers)
128
129
130 class AAIClient(object):
131     def __init__(self, cloud_owner, cloud_region):
132         self.base_url = "%s/%s" % (AAI_SERVICE_URL, AAI_SCHEMA_VERSION)
133         self.username = AAI_USERNAME
134         self.password = AAI_PASSWORD
135         self.default_headers = {
136             'X-FromAppId': 'multicloud-azure',
137             'X-TransactionId': '9004',
138             'content-type': 'application/json',
139             'accept': 'application/json'
140         }
141         self.cloud_owner = cloud_owner
142         self.cloud_region = cloud_region
143         self._vim_info = None
144
145     def get_vim(self, get_all=False):
146         resource = ("/cloud-infrastructure/cloud-regions/cloud-region"
147                     "/%s/%s" % (self.cloud_owner, self.cloud_region))
148         if get_all:
149             resource = "%s?depth=all" % resource
150         resp = call_req(self.base_url, self.username, self.password,
151                         rest_no_auth, resource, "GET",
152                         headers=self.default_headers)
153         if resp[0] != 0:
154             raise VimDriverAzureException(
155                 status_code=404,
156                 content="Failed to query VIM with id (%s_%s) from extsys." % (
157                     self.cloud_owner, self.cloud_region))
158         return json.loads(resp[1])
159
160     def delete_vim(self):
161         resp = self.get_vim(get_all=True)
162         logger.debug('Delete cloud region')
163         resource = ("/cloud-infrastructure/cloud-regions/cloud-region"
164                     "/%s/%s?resource-version=%s" %
165                     (self.cloud_owner, self.cloud_region,
166                      resp['resource-version']))
167         resp = call_req(self.base_url, self.username, self.password,
168                         rest_no_auth, resource, "DELETE",
169                         headers=self.default_headers)
170         if resp[0] != 0:
171             raise VimDriverAzureException(
172                 status_code=400,
173                 content="Failed to delete cloud %s_%s: %s." % (
174                     self.cloud_owner, self.cloud_region, resp[1]))
175
176     def update_vim(self, content):
177         self.add_flavors(content)
178
179     def update_identity_url(self):
180         vim = self.get_vim()
181         vim['identity-url'] = ("http://%s/api/multicloud/v0/%s_%s/identity/"
182                                "v3" % (MSB_SERVICE_IP, self.cloud_owner,
183                                        self.cloud_region))
184         resource = ("/cloud-infrastructure/cloud-regions/cloud-region"
185                     "/%s/%s" % (self.cloud_owner, self.cloud_region))
186         logger.debug("Updating identity url %s" % vim)
187         call_req(self.base_url, self.username, self.password,
188                  rest_no_auth, resource, "PUT",
189                  content=json.dumps(vim),
190                  headers=self.default_headers)
191
192     def add_flavors(self, content):
193         for flavor in content['flavors']:
194             resource = ("/cloud-infrastructure/cloud-regions/cloud-region/"
195                         "%s/%s/flavors/flavor/%s" % (
196                             self.cloud_owner, self.cloud_region,
197                             flavor['name']))
198             body = {
199                 'flavor-name': flavor['name'],
200                 'flavor-vcpus': flavor['vcpus'],
201                 'flavor-ram': flavor['ram'],
202                 'flavor-disk': flavor['disk'],
203                 'flavor-selflink': ""
204             }
205             # Handle extra specs
206             if flavor['name'].startswith("onap."):
207                 hpa_capabilities = self._get_hpa_capabilities(
208                     flavor)
209                 body['hpa-capabilities'] = {
210                     'hpa-capability': hpa_capabilities}
211
212             logger.debug("Adding flavors to cloud region")
213             call_req(self.base_url, self.username, self.password,
214                      rest_no_auth, resource, "PUT",
215                      content=json.dumps(body),
216                      headers=self.default_headers)
217
218     def _get_hpa_capabilities(self, flavor):
219         hpa_caps = []
220
221         # Basic capabilties
222         caps_dict = self._get_hpa_basic_capabilities(flavor)
223         if len(caps_dict) > 0:
224             logger.debug("basic_capabilities_info: %s" % caps_dict)
225             hpa_caps.append(caps_dict)
226
227         # storage capabilities
228         caps_dict = self._get_storage_capabilities(flavor)
229         if len(caps_dict) > 0:
230             logger.debug("storage_capabilities_info: %s" % caps_dict)
231             hpa_caps.append(caps_dict)
232
233         # CPU instruction set extension capabilities
234         caps_dict = self._get_instruction_set_capabilities(
235             flavor['extra_specs'])
236         if len(caps_dict) > 0:
237             logger.debug("instruction_set_capabilities_info: %s" % caps_dict)
238             hpa_caps.append(caps_dict)
239
240         # ovsdpdk capabilities
241         caps_dict = self._get_ovsdpdk_capabilities()
242         if len(caps_dict) > 0:
243             logger.debug("ovsdpdk_capabilities_info: %s" % caps_dict)
244             hpa_caps.append(caps_dict)
245
246         return hpa_caps
247
248     def _get_hpa_basic_capabilities(self, flavor):
249         basic_capability = {}
250         feature_uuid = uuid.uuid4()
251
252         basic_capability['hpa-capability-id'] = str(feature_uuid)
253         basic_capability['hpa-feature'] = 'basicCapabilities'
254         basic_capability['architecture'] = 'generic'
255         basic_capability['hpa-version'] = 'v1'
256
257         basic_capability['hpa-feature-attributes'] = []
258         basic_capability['hpa-feature-attributes'].append({
259             'hpa-attribute-key': 'numVirtualCpu',
260             'hpa-attribute-value': json.dumps(
261                 {'value': str(flavor['vcpus'])})})
262         basic_capability['hpa-feature-attributes'].append({
263             'hpa-attribute-key': 'virtualMemSize',
264             'hpa-attribute-value': json.dumps({'value': str(
265                 flavor['ram']), 'unit': 'GB'})})
266
267         return basic_capability
268
269     def _get_storage_capabilities(self, flavor):
270         storage_capability = {}
271         feature_uuid = uuid.uuid4()
272
273         storage_capability['hpa-capability-id'] = str(feature_uuid)
274         storage_capability['hpa-feature'] = 'localStorage'
275         storage_capability['architecture'] = 'generic'
276         storage_capability['hpa-version'] = 'v1'
277
278         storage_capability['hpa-feature-attributes'] = []
279         storage_capability['hpa-feature-attributes'].append({
280             'hpa-attribute-key': 'diskSize',
281             'hpa-attribute-value': json.dumps({'value': str(
282                 flavor['disk']), 'unit': 'MB'})
283             })
284         storage_capability['hpa-feature-attributes'].append({
285             'hpa-attribute-key': 'swapMemSize',
286             'hpa-attribute-value': json.dumps({'value': str(
287                 flavor.get('swap', 0)), 'unit': 'MB'})
288             })
289         storage_capability['hpa-feature-attributes'].append({
290             'hpa-attribute-key': 'ephemeralDiskSize',
291             'hpa-attribute-value': json.dumps({'value': str(
292                 flavor.get('OS-FLV-EXT-DATA:ephemeral', 0)), 'unit': 'GB'})
293             })
294         return storage_capability
295
296     def _get_instruction_set_capabilities(self, extra_specs):
297         instruction_capability = {}
298         feature_uuid = uuid.uuid4()
299
300         if extra_specs.get('hw:capabilities:cpu_info:features'):
301             instruction_capability['hpa-capability-id'] = str(feature_uuid)
302             instruction_capability['hpa-feature'] = 'instructionSetExtensions'
303             instruction_capability['architecture'] = 'Intel64'
304             instruction_capability['hpa-version'] = 'v1'
305
306             instruction_capability['hpa-feature-attributes'] = []
307             instruction_capability['hpa-feature-attributes'].append({
308                 'hpa-attribute-key': 'instructionSetExtensions',
309                 'hpa-attribute-value': json.dumps(
310                     {'value': extra_specs[
311                         'hw:capabilities:cpu_info:features']})
312                 })
313         return instruction_capability
314
315     def _get_ovsdpdk_capabilities(self):
316         ovsdpdk_capability = {}
317         feature_uuid = uuid.uuid4()
318
319         if not self._vim_info:
320             self._vim_info = self.get_vim(get_all=True)
321         cloud_extra_info_str = self._vim_info.get('cloud-extra-info')
322         if not isinstance(cloud_extra_info_str, dict):
323             try:
324                 cloud_extra_info_str = json.loads(cloud_extra_info_str)
325             except Exception as ex:
326                 logger.error("Can not convert cloud extra info %s %s" % (
327                              str(ex), cloud_extra_info_str))
328                 return {}
329         if cloud_extra_info_str:
330             cloud_dpdk_info = cloud_extra_info_str.get("ovsDpdk")
331             if cloud_dpdk_info:
332                 ovsdpdk_capability['hpa-capability-id'] = str(feature_uuid)
333                 ovsdpdk_capability['hpa-feature'] = 'ovsDpdk'
334                 ovsdpdk_capability['architecture'] = 'Intel64'
335                 ovsdpdk_capability['hpa-version'] = 'v1'
336
337                 ovsdpdk_capability['hpa-feature-attributes'] = []
338                 ovsdpdk_capability['hpa-feature-attributes'].append({
339                     'hpa-attribute-key': str(cloud_dpdk_info.get("libname")),
340                     'hpa-attribute-value': json.dumps(
341                         {'value': cloud_dpdk_info.get("libversion")})
342                     })
343         return ovsdpdk_capability