Improve coverage of multicloud-azure plugin
[multicloud/azure.git] / azure / 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 azure.pub.config.config import AAI_SCHEMA_VERSION
22 from azure.pub.config.config import AAI_SERVICE_URL
23 from azure.pub.config.config import AAI_USERNAME
24 from azure.pub.config.config import AAI_PASSWORD
25 from azure.pub.config.config import MSB_SERVICE_IP, MSB_SERVICE_PORT
26
27 from 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-openstack-vmware',
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 tenants')
163         self._del_tenants(resp)
164         logger.debug('Delete images')
165         self._del_images(resp)
166         logger.debug('Delete flavors')
167         self._del_flavors(resp)
168         logger.debug('Delete networks')
169         self._del_networks(resp)
170         logger.debug('Delete availability zones')
171         self._del_azs(resp)
172         logger.debug('Delete cloud region')
173         resource = ("/cloud-infrastructure/cloud-regions/cloud-region"
174                     "/%s/%s?resource-version=%s" %
175                     (self.cloud_owner, self.cloud_region,
176                      resp['resource-version']))
177         resp = call_req(self.base_url, self.username, self.password,
178                         rest_no_auth, resource, "DELETE",
179                         headers=self.default_headers)
180         if resp[0] != 0:
181             raise VimDriverAzureException(
182                 status_code=400,
183                 content="Failed to delete cloud %s_%s: %s." % (
184                     self.cloud_owner, self.cloud_region, resp[1]))
185
186     def update_vim(self, content):
187         # update identity url
188         self.update_identity_url()
189         # update tenants
190         self.add_tenants(content)
191         # update flavors
192         self.add_images(content)
193         # update images
194         self.add_flavors(content)
195         # update networks
196         self.add_networks(content)
197         # update pservers
198         self.add_pservers(content)
199
200     def update_identity_url(self):
201         vim = self.get_vim()
202         vim['identity-url'] = ("http://%s/api/multicloud/v0/%s_%s/identity/"
203                                "v3" % (MSB_SERVICE_IP, self.cloud_owner,
204                                        self.cloud_region))
205         resource = ("/cloud-infrastructure/cloud-regions/cloud-region"
206                     "/%s/%s" % (self.cloud_owner, self.cloud_region))
207         logger.debug("Updating identity url %s" % vim)
208         call_req(self.base_url, self.username, self.password,
209                  rest_no_auth, resource, "PUT",
210                  content=json.dumps(vim),
211                  headers=self.default_headers)
212
213     def add_tenants(self, content):
214         for tenant in content['tenants']:
215             resource = ("/cloud-infrastructure/cloud-regions/cloud-region/"
216                         "%s/%s/tenants/tenant/%s" % (
217                             self.cloud_owner, self.cloud_region, tenant['id']))
218             body = {'tenant-name': tenant['name']}
219             logger.debug("Adding tenants to cloud region")
220             call_req(self.base_url, self.username, self.password,
221                      rest_no_auth, resource, "PUT",
222                      content=json.dumps(body),
223                      headers=self.default_headers)
224
225     def add_flavors(self, content):
226         for flavor in content['flavors']:
227             resource = ("/cloud-infrastructure/cloud-regions/cloud-region/"
228                         "%s/%s/flavors/flavor/%s" % (
229                             self.cloud_owner, self.cloud_region, flavor['id']))
230             body = {
231                 'flavor-name': flavor['name'],
232                 'flavor-vcpus': flavor['vcpus'],
233                 'flavor-ram': flavor['ram'],
234                 'flavor-disk': flavor['disk'],
235                 'flavor-ephemeral': flavor['ephemeral'],
236                 'flavor-swap': flavor['swap'],
237                 'flavor-is-public': flavor['is_public'],
238                 'flavor-selflink': flavor['links'][0]['href'],
239                 'flavor-disabled': flavor['is_disabled']
240             }
241             # Handle extra specs
242             if flavor['name'].startswith("onap."):
243                 hpa_capabilities = self._get_hpa_capabilities(
244                     flavor)
245                 body['hpa-capabilities'] = {
246                     'hpa-capability': hpa_capabilities}
247
248             logger.debug("Adding flavors to cloud region")
249             call_req(self.base_url, self.username, self.password,
250                      rest_no_auth, resource, "PUT",
251                      content=json.dumps(body),
252                      headers=self.default_headers)
253
254     def add_images(self, content):
255         for image in content['images']:
256             resource = ("/cloud-infrastructure/cloud-regions/cloud-region/"
257                         "%s/%s/images/image/%s" % (
258                             self.cloud_owner, self.cloud_region, image['id']))
259             split_image_name = image['name'].split("-")
260             os_distro = split_image_name[0]
261             os_version = split_image_name[1] if \
262                 len(split_image_name) > 1 else ""
263             body = {
264                 'image-name': image['name'],
265                 # 'image-architecture': image[''],
266                 'image-os-distro': os_distro,
267                 'image-os-version': os_version,
268                 # 'application': image[''],
269                 # 'application-vendor': image[''],
270                 # 'application-version': image[''],
271                 # TODO replace this with image proxy endpoint
272                 'image-selflink': "",
273             }
274             logger.debug("Adding images to cloud region")
275             call_req(self.base_url, self.username, self.password,
276                      rest_no_auth, resource, "PUT",
277                      content=json.dumps(body),
278                      headers=self.default_headers)
279
280     def add_networks(self, content):
281         for network in content['networks']:
282             resource = ("/cloud-infrastructure/cloud-regions/cloud-region/"
283                         "%s/%s/oam-networks/oam-network/%s" % (
284                             self.cloud_owner, self.cloud_region,
285                             network['id']))
286             body = {
287                 'network-uuid': network['id'],
288                 'network-name': network['name'],
289                 'cvlan-tag': network['segmentationId'] or 0,
290             }
291             logger.debug("Adding networks to cloud region")
292             call_req(self.base_url, self.username, self.password,
293                      rest_no_auth, resource, "PUT",
294                      content=json.dumps(body),
295                      headers=self.default_headers)
296
297     def add_pservers(self, content):
298         for hypervisor in content['hypervisors']:
299             resource = ("/cloud-infrastructure/pservers/pserver/%s" % (
300                 hypervisor['name']))
301             body = {
302                 # 'ptnii-equip-name'
303                 'number-of-cpus': hypervisor['vcpus'],
304                 'disk-in-gigabytes': hypervisor['local_disk_size'],
305                 'ram-in-megabytes': hypervisor['memory_size'],
306                 # 'equip-type'
307                 # 'equip-vendor'
308                 # 'equip-model'
309                 # 'fqdn'
310                 # 'pserver-selflink'
311                 'ipv4-oam-address': hypervisor['host_ip'],
312                 # 'serial-number'
313                 # 'ipaddress-v4-loopback-0'
314                 # 'ipaddress-v6-loopback-0'
315                 # 'ipaddress-v4-aim'
316                 # 'ipaddress-v6-aim'
317                 # 'ipaddress-v6-oam'
318                 # 'inv-status'
319                 'pserver-id': hypervisor['id'],
320                 # 'internet-topology'
321             }
322             logger.debug("Adding pservers")
323             call_req(self.base_url, self.username, self.password,
324                      rest_no_auth, resource, "PUT",
325                      content=json.dumps(body),
326                      headers=self.default_headers)
327             # update relationship
328             resource = ("/cloud-infrastructure/pservers/pserver/%s/"
329                         "relationship-list/relationship" %
330                         hypervisor['name'])
331             related_link = ("%s/cloud-infrastructure/cloud-regions/"
332                             "cloud-region/%s/%s" % (
333                                 self.base_url, self.cloud_owner,
334                                 self.cloud_region))
335             body = {
336                 'related-to': 'cloud-region',
337                 'related-link': related_link,
338                 'relationship-data': [
339                     {
340                         'relationship-key': 'cloud-region.cloud-owner',
341                         'relationship-value': self.cloud_owner
342                     },
343                     {
344                         'relationship-key': 'cloud-region.cloud-region-id',
345                         'relationship-value': self.cloud_region
346                     }
347                 ]
348             }
349             logger.debug("Connecting pservers and cloud region")
350             call_req(self.base_url, self.username, self.password,
351                      rest_no_auth, resource, "PUT",
352                      content=json.dumps(body),
353                      headers=self.default_headers)
354
355     def _del_tenants(self, rsp):
356         tenants = rsp.get("tenants", [])
357         if not tenants:
358             return
359         for tenant in tenants["tenant"]:
360             resource = ("/cloud-infrastructure/cloud-regions/cloud-region/"
361                         "%s/%s/tenants/tenant/%s?resource-version=%s" % (
362                             self.cloud_owner, self.cloud_region,
363                             tenant['tenant-id'], tenant['resource-version']))
364             resp = call_req(self.base_url, self.username, self.password,
365                             rest_no_auth, resource, "DELETE",
366                             headers=self.default_headers)
367             if resp[0] != 0:
368                 raise VimDriverAzureException(
369                     status_code=400,
370                     content="Failed to delete tenant %s: %s." % (
371                         tenant['tenant-id'], resp[1]))
372
373     def _del_hpa(self, flavor):
374         hpas = flavor.get("hpa-capabilities", {}).get("hpa-capability", [])
375         for hpa in hpas:
376             resource = (
377                 "/cloud-infrastructure/cloud-regions/cloud-region/"
378                 "%s/%s/flavors/flavor/%s/hpa-capabilities/hpa-capability/%s"
379                 "?resource-version=%s" % (
380                     self.cloud_owner, self.cloud_region,
381                     flavor['flavor-id'], hpa['hpa-capability-id'],
382                     hpa['resource-version']))
383             resp = call_req(self.base_url, self.username, self.password,
384                             rest_no_auth, resource, "DELETE",
385                             headers=self.default_headers)
386             if resp[0] != 0:
387                 raise VimDriverAzureException(
388                     status_code=400,
389                     content="Failed to delete flavor %s on hpa %s: %s." % (
390                         flavor['flavor-id'], hpa['hpa-capability-id'],
391                         resp[1]))
392
393     def _del_flavors(self, rsp):
394         flavors = rsp.get("flavors", [])
395         if not flavors:
396             return
397         for flavor in flavors["flavor"]:
398             self._del_hpa(flavor)
399             resource = ("/cloud-infrastructure/cloud-regions/cloud-region/"
400                         "%s/%s/flavors/flavor/%s?resource-version=%s" % (
401                             self.cloud_owner, self.cloud_region,
402                             flavor['flavor-id'], flavor['resource-version']))
403             resp = call_req(self.base_url, self.username, self.password,
404                             rest_no_auth, resource, "DELETE",
405                             headers=self.default_headers)
406             if resp[0] != 0:
407                 raise VimDriverAzureException(
408                     status_code=400,
409                     content="Failed to delete flavor %s: %s." % (
410                         flavor['flavor-id'], resp[1]))
411
412     def _del_images(self, rsp):
413         tenants = rsp.get("images", [])
414         if not tenants:
415             return
416         for tenant in tenants["image"]:
417             resource = ("/cloud-infrastructure/cloud-regions/cloud-region/"
418                         "%s/%s/images/image/%s?resource-version=%s" % (
419                             self.cloud_owner, self.cloud_region,
420                             tenant['image-id'], tenant['resource-version']))
421             resp = call_req(self.base_url, self.username, self.password,
422                             rest_no_auth, resource, "DELETE",
423                             headers=self.default_headers)
424             if resp[0] != 0:
425                 raise VimDriverAzureException(
426                     status_code=400,
427                     content="Failed to delete image %s: %s." % (
428                         tenant['image-id'], resp[1]))
429
430     def _del_networks(self, rsp):
431         networks = rsp.get("oam-networks", [])
432         if not networks:
433             return
434         for network in networks["oam-network"]:
435             resource = ("/cloud-infrastructure/cloud-regions/cloud-region/"
436                         "%s/%s/oam-networks/oam-network/%s?"
437                         "resource-version=%s" % (
438                             self.cloud_owner, self.cloud_region,
439                             network['network-uuid'],
440                             network['resource-version']))
441             resp = call_req(self.base_url, self.username, self.password,
442                             rest_no_auth, resource, "DELETE",
443                             headers=self.default_headers)
444             if resp[0] != 0:
445                 raise VimDriverAzureException(
446                     status_code=400,
447                     content="Failed to delete network %s: %s." % (
448                         network['network-uuid'], resp[1]))
449
450     def _del_azs(self, rsp):
451         azs = rsp.get("availability-zones", [])
452         if not azs:
453             return
454         for az in azs["availability-zone"]:
455             resource = ("/cloud-infrastructure/cloud-regions/cloud-region/"
456                         "%s/%s/availability-zones/availability-zone/%s?"
457                         "resource-version=%s" % (
458                             self.cloud_owner, self.cloud_region,
459                             az['availability-zone-name'],
460                             az['resource-version']))
461             resp = call_req(self.base_url, self.username, self.password,
462                             rest_no_auth, resource, "DELETE",
463                             headers=self.default_headers)
464             if resp[0] != 0:
465                 raise VimDriverAzureException(
466                     status_code=400,
467                     content="Failed to delete availability zone %s: %s." % (
468                         az['availability-zone-name'], resp[1]))
469
470     def _get_hpa_capabilities(self, flavor):
471         hpa_caps = []
472
473         # Basic capabilties
474         caps_dict = self._get_hpa_basic_capabilities(flavor)
475         if len(caps_dict) > 0:
476             logger.debug("basic_capabilities_info: %s" % caps_dict)
477             hpa_caps.append(caps_dict)
478
479         # cpupining capabilities
480         caps_dict = self._get_cpupinning_capabilities(flavor['extra_specs'])
481         if len(caps_dict) > 0:
482             logger.debug("cpupining_capabilities_info: %s" % caps_dict)
483             hpa_caps.append(caps_dict)
484
485         # cputopology capabilities
486         caps_dict = self._get_cputopology_capabilities(flavor['extra_specs'])
487         if len(caps_dict) > 0:
488             logger.debug("cputopology_capabilities_info: %s" % caps_dict)
489             hpa_caps.append(caps_dict)
490
491         # hugepages capabilities
492         caps_dict = self._get_hugepages_capabilities(flavor['extra_specs'])
493         if len(caps_dict) > 0:
494             logger.debug("hugepages_capabilities_info: %s" % caps_dict)
495             hpa_caps.append(caps_dict)
496
497         # numa capabilities
498         caps_dict = self._get_numa_capabilities(flavor['extra_specs'])
499         if len(caps_dict) > 0:
500             logger.debug("numa_capabilities_info: %s" % caps_dict)
501             hpa_caps.append(caps_dict)
502
503         # storage capabilities
504         caps_dict = self._get_storage_capabilities(flavor)
505         if len(caps_dict) > 0:
506             logger.debug("storage_capabilities_info: %s" % caps_dict)
507             hpa_caps.append(caps_dict)
508
509         # CPU instruction set extension capabilities
510         caps_dict = self._get_instruction_set_capabilities(
511             flavor['extra_specs'])
512         if len(caps_dict) > 0:
513             logger.debug("instruction_set_capabilities_info: %s" % caps_dict)
514             hpa_caps.append(caps_dict)
515
516         # PCI passthrough capabilities
517         caps_dict = self._get_pci_passthrough_capabilities(
518             flavor['extra_specs'])
519         if len(caps_dict) > 0:
520             logger.debug("pci_passthrough_capabilities_info: %s" % caps_dict)
521             hpa_caps.append(caps_dict)
522
523         # ovsdpdk capabilities
524         caps_dict = self._get_ovsdpdk_capabilities()
525         if len(caps_dict) > 0:
526             logger.debug("ovsdpdk_capabilities_info: %s" % caps_dict)
527             hpa_caps.append(caps_dict)
528
529         return hpa_caps
530
531     def _get_hpa_basic_capabilities(self, flavor):
532         basic_capability = {}
533         feature_uuid = uuid.uuid4()
534
535         basic_capability['hpa-capability-id'] = str(feature_uuid)
536         basic_capability['hpa-feature'] = 'basicCapabilities'
537         basic_capability['architecture'] = 'generic'
538         basic_capability['hpa-version'] = 'v1'
539
540         basic_capability['hpa-feature-attributes'] = []
541         basic_capability['hpa-feature-attributes'].append({
542             'hpa-attribute-key': 'numVirtualCpu',
543             'hpa-attribute-value': json.dumps(
544                 {'value': str(flavor['vcpus'])})})
545         basic_capability['hpa-feature-attributes'].append({
546             'hpa-attribute-key': 'virtualMemSize',
547             'hpa-attribute-value': json.dumps({'value': str(
548                 flavor['ram']), 'unit': 'MB'})})
549
550         return basic_capability
551
552     def _get_cpupinning_capabilities(self, extra_specs):
553         cpupining_capability = {}
554         feature_uuid = uuid.uuid4()
555
556         if (extra_specs.get('hw:cpu_policy') or
557                 extra_specs.get('hw:cpu_thread_policy')):
558             cpupining_capability['hpa-capability-id'] = str(feature_uuid)
559             cpupining_capability['hpa-feature'] = 'cpuPinning'
560             cpupining_capability['architecture'] = 'generic'
561             cpupining_capability['hpa-version'] = 'v1'
562
563             cpupining_capability['hpa-feature-attributes'] = []
564             if extra_specs.get('hw:cpu_thread_policy'):
565                 cpupining_capability['hpa-feature-attributes'].append({
566                     'hpa-attribute-key': 'logicalCpuThreadPinningPolicy',
567                     'hpa-attribute-value': json.dumps({'value': str(
568                         extra_specs['hw:cpu_thread_policy'])})})
569             if extra_specs.get('hw:cpu_policy'):
570                 cpupining_capability['hpa-feature-attributes'].append({
571                     'hpa-attribute-key': 'logicalCpuPinningPolicy',
572                     'hpa-attribute-value': json.dumps({'value': str(
573                         extra_specs['hw:cpu_policy'])})})
574
575         return cpupining_capability
576
577     def _get_cputopology_capabilities(self, extra_specs):
578         cputopology_capability = {}
579         feature_uuid = uuid.uuid4()
580
581         if (extra_specs.get('hw:cpu_sockets') or
582                 extra_specs.get('hw:cpu_cores') or
583                 extra_specs.get('hw:cpu_threads')):
584             cputopology_capability['hpa-capability-id'] = str(feature_uuid)
585             cputopology_capability['hpa-feature'] = 'cpuTopology'
586             cputopology_capability['architecture'] = 'generic'
587             cputopology_capability['hpa-version'] = 'v1'
588
589             cputopology_capability['hpa-feature-attributes'] = []
590             if extra_specs.get('hw:cpu_sockets'):
591                 cputopology_capability['hpa-feature-attributes'].append({
592                     'hpa-attribute-key': 'numCpuSockets',
593                     'hpa-attribute-value': json.dumps({'value': str(
594                         extra_specs['hw:cpu_sockets'])})})
595             if extra_specs.get('hw:cpu_cores'):
596                 cputopology_capability['hpa-feature-attributes'].append({
597                     'hpa-attribute-key': 'numCpuCores',
598                     'hpa-attribute-value': json.dumps({'value': str(
599                         extra_specs['hw:cpu_cores'])})})
600             if extra_specs.get('hw:cpu_threads'):
601                 cputopology_capability['hpa-feature-attributes'].append({
602                     'hpa-attribute-key': 'numCpuThreads',
603                     'hpa-attribute-value': json.dumps({'value': str(
604                         extra_specs['hw:cpu_threads'])})})
605
606         return cputopology_capability
607
608     def _get_hugepages_capabilities(self, extra_specs):
609         hugepages_capability = {}
610         feature_uuid = uuid.uuid4()
611
612         if extra_specs.get('hw:mem_page_size'):
613             hugepages_capability['hpa-capability-id'] = str(feature_uuid)
614             hugepages_capability['hpa-feature'] = 'hugePages'
615             hugepages_capability['architecture'] = 'generic'
616             hugepages_capability['hpa-version'] = 'v1'
617
618             hugepages_capability['hpa-feature-attributes'] = []
619             if extra_specs['hw:mem_page_size'] == 'large':
620                 hugepages_capability['hpa-feature-attributes'].append({
621                     'hpa-attribute-key': 'memoryPageSize',
622                     'hpa-attribute-value': json.dumps(
623                         {'value': '2', 'unit': 'MB'})})
624             elif extra_specs['hw:mem_page_size'] == 'small':
625                 hugepages_capability['hpa-feature-attributes'].append({
626                     'hpa-attribute-key': 'memoryPageSize',
627                     'hpa-attribute-value': json.dumps(
628                         {'value': '4', 'unit': 'KB'})})
629             elif extra_specs['hw:mem_page_size'] == 'any':
630                 logger.info("Currently HPA feature memoryPageSize "
631                             "did not support 'any' page!!")
632             else:
633                 hugepages_capability['hpa-feature-attributes'].append({
634                     'hpa-attribute-key': 'memoryPageSize',
635                     'hpa-attribute-value': json.dumps({'value': str(
636                         extra_specs['hw:mem_page_size']), 'unit': 'KB'})
637                     })
638
639         return hugepages_capability
640
641     def _get_numa_capabilities(self, extra_specs):
642         numa_capability = {}
643         feature_uuid = uuid.uuid4()
644
645         if extra_specs.get('hw:numa_nodes'):
646             numa_capability['hpa-capability-id'] = str(feature_uuid)
647             numa_capability['hpa-feature'] = 'numa'
648             numa_capability['architecture'] = 'generic'
649             numa_capability['hpa-version'] = 'v1'
650
651             numa_capability['hpa-feature-attributes'] = []
652             numa_capability['hpa-feature-attributes'].append({
653                 'hpa-attribute-key': 'numaNodes',
654                 'hpa-attribute-value': json.dumps({'value': str(
655                     extra_specs['hw:numa_nodes'])})
656                 })
657
658             for num in range(0, int(extra_specs['hw:numa_nodes'])):
659                 numa_cpu_node = "hw:numa_cpus.%s" % num
660                 numa_mem_node = "hw:numa_mem.%s" % num
661                 numacpu_key = "numaCpu-%s" % num
662                 numamem_key = "numaMem-%s" % num
663
664                 if (extra_specs.get(numa_cpu_node) and
665                         extra_specs.get(numa_mem_node)):
666                     numa_capability['hpa-feature-attributes'].append({
667                         'hpa-attribute-key': numacpu_key,
668                         'hpa-attribute-value': json.dumps({'value': str(
669                             extra_specs[numa_cpu_node])})
670                         })
671                     numa_capability['hpa-feature-attributes'].append({
672                         'hpa-attribute-key': numamem_key,
673                         'hpa-attribute-value': json.dumps({'value': str(
674                             extra_specs[numa_mem_node]), 'unit': 'MB'})
675                         })
676
677         return numa_capability
678
679     def _get_storage_capabilities(self, flavor):
680         storage_capability = {}
681         feature_uuid = uuid.uuid4()
682
683         storage_capability['hpa-capability-id'] = str(feature_uuid)
684         storage_capability['hpa-feature'] = 'localStorage'
685         storage_capability['architecture'] = 'generic'
686         storage_capability['hpa-version'] = 'v1'
687
688         storage_capability['hpa-feature-attributes'] = []
689         storage_capability['hpa-feature-attributes'].append({
690             'hpa-attribute-key': 'diskSize',
691             'hpa-attribute-value': json.dumps({'value': str(
692                 flavor['disk']), 'unit': 'GB'})
693             })
694         storage_capability['hpa-feature-attributes'].append({
695             'hpa-attribute-key': 'swapMemSize',
696             'hpa-attribute-value': json.dumps({'value': str(
697                 flavor.get('swap', 0)), 'unit': 'MB'})
698             })
699         storage_capability['hpa-feature-attributes'].append({
700             'hpa-attribute-key': 'ephemeralDiskSize',
701             'hpa-attribute-value': json.dumps({'value': str(
702                 flavor.get('OS-FLV-EXT-DATA:ephemeral', 0)), 'unit': 'GB'})
703             })
704         return storage_capability
705
706     def _get_instruction_set_capabilities(self, extra_specs):
707         instruction_capability = {}
708         feature_uuid = uuid.uuid4()
709
710         if extra_specs.get('hw:capabilities:cpu_info:features'):
711             instruction_capability['hpa-capability-id'] = str(feature_uuid)
712             instruction_capability['hpa-feature'] = 'instructionSetExtensions'
713             instruction_capability['architecture'] = 'Intel64'
714             instruction_capability['hpa-version'] = 'v1'
715
716             instruction_capability['hpa-feature-attributes'] = []
717             instruction_capability['hpa-feature-attributes'].append({
718                 'hpa-attribute-key': 'instructionSetExtensions',
719                 'hpa-attribute-value': json.dumps(
720                     {'value': extra_specs[
721                         'hw:capabilities:cpu_info:features']})
722                 })
723         return instruction_capability
724
725     def _get_pci_passthrough_capabilities(self, extra_specs):
726         instruction_capability = {}
727         feature_uuid = uuid.uuid4()
728
729         if extra_specs.get('pci_passthrough:alias'):
730             value1 = extra_specs['pci_passthrough:alias'].split(':')
731             value2 = value1[0].split('-')
732
733             instruction_capability['hpa-capability-id'] = str(feature_uuid)
734             instruction_capability['hpa-feature'] = 'pciePassthrough'
735             instruction_capability['architecture'] = str(value2[2])
736             instruction_capability['hpa-version'] = 'v1'
737
738             instruction_capability['hpa-feature-attributes'] = []
739             instruction_capability['hpa-feature-attributes'].append({
740                 'hpa-attribute-key': 'pciCount',
741                 'hpa-attribute-value': json.dumps({'value': value1[1]})
742                 })
743             instruction_capability['hpa-feature-attributes'].append({
744                 'hpa-attribute-key': 'pciVendorId',
745                 'hpa-attribute-value': json.dumps({'value': value2[3]})
746                 })
747             instruction_capability['hpa-feature-attributes'].append({
748                 'hpa-attribute-key': 'pciDeviceId',
749                 'hpa-attribute-value': json.dumps({'value': value2[4]})
750                 })
751
752         return instruction_capability
753
754     def _get_ovsdpdk_capabilities(self):
755         ovsdpdk_capability = {}
756         feature_uuid = uuid.uuid4()
757
758         if not self._vim_info:
759             self._vim_info = self.get_vim(get_all=True)
760         cloud_extra_info_str = self._vim_info.get('cloud-extra-info')
761         if not isinstance(cloud_extra_info_str, dict):
762             try:
763                 cloud_extra_info_str = json.loads(cloud_extra_info_str)
764             except Exception as ex:
765                 logger.error("Can not convert cloud extra info %s %s" % (
766                              str(ex), cloud_extra_info_str))
767                 return {}
768         if cloud_extra_info_str:
769             cloud_dpdk_info = cloud_extra_info_str.get("ovsDpdk")
770             if cloud_dpdk_info:
771                 ovsdpdk_capability['hpa-capability-id'] = str(feature_uuid)
772                 ovsdpdk_capability['hpa-feature'] = 'ovsDpdk'
773                 ovsdpdk_capability['architecture'] = 'Intel64'
774                 ovsdpdk_capability['hpa-version'] = 'v1'
775
776                 ovsdpdk_capability['hpa-feature-attributes'] = []
777                 ovsdpdk_capability['hpa-feature-attributes'].append({
778                     'hpa-attribute-key': str(cloud_dpdk_info.get("libname")),
779                     'hpa-attribute-value': json.dumps(
780                         {'value': cloud_dpdk_info.get("libversion")})
781                     })
782         return ovsdpdk_capability