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