summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSudhakar Reddy <reddysud@amdocs.com>2018-08-13 21:13:56 +0530
committerSudhakar Reddy <Sudhakar.Reddy@amdocs.com>2018-08-20 14:19:54 +0530
commit6927c37a31ef5a874e923a4fe02feb1392a7dfb4 (patch)
tree28c1914b811a1bd3cac489ffc26890abd4ea15ae
parentc790c7e939a0bcd9b424fc986f40a29485686315 (diff)
Adoption of base framework code for azure plugin
This is the initial code which is created by referring to vmware plugin.The logging and containerization features are readily available. Change-Id: I3371d5f0671c2252edb1da7ea55f1f89ea27b3aa Issue-ID: MULTICLOUD-308 Signed-off-by: Sudhakar Reddy <Sudhakar.Reddy@amdocs.com>
-rw-r--r--.gitignore24
-rw-r--r--.gitreview4
-rw-r--r--License.txt37
-rw-r--r--azure/README.md12
-rw-r--r--azure/assembly.xml66
-rw-r--r--azure/azure/__init__.py0
-rw-r--r--azure/azure/api_v2/__init__.py0
-rw-r--r--azure/azure/api_v2/api_definition/__init__.py0
-rw-r--r--azure/azure/api_v2/api_definition/hosts.yaml72
-rw-r--r--azure/azure/api_v2/api_definition/images.yaml76
-rw-r--r--azure/azure/api_v2/api_definition/networks.yaml91
-rw-r--r--azure/azure/api_v2/api_definition/ports.yaml83
-rw-r--r--azure/azure/api_v2/api_definition/subnets.yaml88
-rw-r--r--azure/azure/api_v2/api_definition/utils.py31
-rw-r--r--azure/azure/api_v2/api_router/__init__.py0
-rw-r--r--azure/azure/api_v2/api_router/controller_builder.py224
-rw-r--r--azure/azure/api_v2/api_router/root.py34
-rw-r--r--azure/azure/api_v2/api_router/swagger_json.py25
-rw-r--r--azure/azure/api_v2/api_router/v0_controller.py49
-rw-r--r--azure/azure/api_v2/app.py35
-rw-r--r--azure/azure/api_v2/service.py54
-rw-r--r--azure/azure/event_listener/__init__.py0
-rw-r--r--azure/azure/event_listener/i18n.py39
-rw-r--r--azure/azure/event_listener/listener.conf15
-rw-r--r--azure/azure/event_listener/server.py126
-rw-r--r--azure/azure/middleware.py59
-rw-r--r--azure/azure/pub/__init__.py0
-rw-r--r--azure/azure/pub/config/__init__.py0
-rw-r--r--azure/azure/pub/config/config.py41
-rw-r--r--azure/azure/pub/config/log.yml26
-rw-r--r--azure/azure/pub/database/__init__.py0
-rw-r--r--azure/azure/pub/database/models.py23
-rw-r--r--azure/azure/pub/exceptions.py66
-rw-r--r--azure/azure/pub/msapi/__init__.py0
-rw-r--r--azure/azure/pub/msapi/extsys.py46
-rw-r--r--azure/azure/pub/utils/__init__.py0
-rw-r--r--azure/azure/pub/utils/enumutil.py15
-rw-r--r--azure/azure/pub/utils/fileutil.py50
-rw-r--r--azure/azure/pub/utils/idutil.py18
-rw-r--r--azure/azure/pub/utils/restcall.py782
-rw-r--r--azure/azure/pub/utils/syscomm.py111
-rw-r--r--azure/azure/pub/utils/timeutil.py17
-rw-r--r--azure/azure/pub/utils/values.py22
-rw-r--r--azure/azure/pub/vim/__init__.py0
-rw-r--r--azure/azure/pub/vim/const.py14
-rw-r--r--azure/azure/samples/__init__.py0
-rw-r--r--azure/azure/samples/tests.py31
-rw-r--r--azure/azure/samples/urls.py17
-rw-r--r--azure/azure/samples/views.py35
-rw-r--r--azure/azure/scripts/__init__.py0
-rw-r--r--azure/azure/scripts/api.py41
-rw-r--r--azure/azure/settings-cover.py22
-rw-r--r--azure/azure/settings.py98
-rw-r--r--azure/azure/swagger/__init__.py0
-rw-r--r--azure/azure/swagger/image_utils.py65
-rw-r--r--azure/azure/swagger/nova_utils.py119
-rw-r--r--azure/azure/swagger/tests.py31
-rw-r--r--azure/azure/swagger/urls.py37
-rw-r--r--azure/azure/swagger/utils.py37
-rw-r--r--azure/azure/swagger/views.py29
-rw-r--r--azure/azure/swagger/views/__init__.py0
-rw-r--r--azure/azure/swagger/views/multivim.swagger.json51
-rw-r--r--azure/azure/swagger/views/registry/__init__.py0
-rw-r--r--azure/azure/swagger/views/registry/views.py167
-rw-r--r--azure/azure/swagger/views/swagger_json.py42
-rw-r--r--azure/azure/swagger/volume_utils.py72
-rw-r--r--azure/azure/tests/__init__.py0
-rw-r--r--azure/azure/tests/test_aai_client.py378
-rw-r--r--azure/azure/tests/test_restcall.py101
-rw-r--r--azure/azure/urls.py18
-rw-r--r--azure/azure/wsgi.py20
-rw-r--r--azure/docker/Dockerfile29
-rw-r--r--azure/docker/build_image.sh50
-rw-r--r--azure/docker/docker-entrypoint.sh48
-rw-r--r--azure/docker/instance-config.sh48
-rw-r--r--azure/docker/instance-init.sh22
-rw-r--r--azure/docker/instance-run.sh22
-rw-r--r--azure/images/empty.txt0
-rw-r--r--azure/initialize.sh14
-rw-r--r--azure/logs/empty.txt0
-rw-r--r--azure/manage.py20
-rw-r--r--azure/pom.xml61
-rw-r--r--azure/requirements.txt39
-rw-r--r--azure/run.sh41
-rw-r--r--azure/setup.py23
-rw-r--r--azure/stop.sh15
-rw-r--r--azure/tox.ini28
-rw-r--r--azure/version.properties27
-rw-r--r--pom.xml99
-rwxr-xr-xsonar.sh83
-rw-r--r--version.properties27
91 files changed, 4582 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..64c410c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,24 @@
+.project
+.classpath
+.vscode
+.settings/
+.checkstyle
+target/
+logs/*.log
+*.pyc
+*.swp
+.idea/
+
+# Test related files
+azure/.coverage
+azure/.tox/
+azure/logs/*.log
+azure/test-reports/
+
+# build files
+
+azure/build
+azure/dist
+azure/1.25.0
+azure/*.egg-info
+azure/logs/*.log \ No newline at end of file
diff --git a/.gitreview b/.gitreview
new file mode 100644
index 0000000..a5cf8f2
--- /dev/null
+++ b/.gitreview
@@ -0,0 +1,4 @@
+[gerrit]
+host=gerrit.onap.org
+port=29418
+project=multicloud/azure.git
diff --git a/License.txt b/License.txt
new file mode 100644
index 0000000..0dd49ad
--- /dev/null
+++ b/License.txt
@@ -0,0 +1,37 @@
+/*
+* ============LICENSE_START==========================================
+* ===================================================================
+* Copyright (c) 2018 Amdocs
+* All rights reserved.
+* ===================================================================
+*
+* Unless otherwise specified, all software contained herein is licensed
+* under the Apache License, Version 2.0 (the “License”);
+* you may not use this software except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*
+*
+*
+* Unless otherwise specified, all documentation contained herein is licensed
+* under the Creative Commons License, Attribution 4.0 Intl. (the “License”);
+* you may not use this documentation except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* https://creativecommons.org/licenses/by/4.0/
+*
+* Unless required by applicable law or agreed to in writing, documentation
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*
+* ============LICENSE_END============================================
+*/ \ No newline at end of file
diff --git a/azure/README.md b/azure/README.md
new file mode 100644
index 0000000..cdca6b2
--- /dev/null
+++ b/azure/README.md
@@ -0,0 +1,12 @@
+## Multicloud Azure Plugin
+This plugin is a part of multicloud component which contains the capability
+to talk to Azure cloud based on the Service Principal credentials fetched
+from AAI.
+
+The initial version of this plugin will provide the API to register a Cloud
+region. The plugin will provide both versions(v0 and v1) of API for
+cloud registration.
+
+#### Provided APIs:
+
+**/api/multicloud-azure/v0/(?P<vimid>[0-9a-z-A-Z\-\_]+)/registry**
diff --git a/azure/assembly.xml b/azure/assembly.xml
new file mode 100644
index 0000000..61a4883
--- /dev/null
+++ b/azure/assembly.xml
@@ -0,0 +1,66 @@
+<!--
+ Copyright (c) 2018 Amdocs
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at:
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ -->
+<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
+ <id>azure</id>
+ <formats>
+ <format>zip</format>
+ </formats>
+ <fileSets>
+ <fileSet>
+ <directory>azure</directory>
+ <outputDirectory>/azure</outputDirectory>
+ <includes>
+ <include>**/*.py</include>
+ <include>**/*.json</include>
+ <include>**/*.xml</include>
+ <include>**/*.wsdl</include>
+ <include>**/*.xsd</include>
+ <include>**/*.bpel</include>
+ <include>**/*.yml</include>
+ <include>**/*.conf</include>
+ </includes>
+ </fileSet>
+ <fileSet>
+ <directory>logs</directory>
+ <outputDirectory>/logs</outputDirectory>
+ <includes>
+ <include>*.txt</include>
+ </includes>
+ </fileSet>
+ <fileSet>
+ <directory>docker</directory>
+ <outputDirectory>/docker</outputDirectory>
+ <includes>
+ <include>*.sh</include>
+ <include>Dockerfile</include>
+ </includes>
+ </fileSet>
+ <fileSet>
+ <directory>.</directory>
+ <outputDirectory>/</outputDirectory>
+ <includes>
+ <include>*.py</include>
+ <include>*.txt</include>
+ <include>*.sh</include>
+ <include>*.ini</include>
+ <include>*.md</include>
+ <include>*.yml</include>
+ </includes>
+ </fileSet>
+ </fileSets>
+ <baseDirectory>azure</baseDirectory>
+ <!--baseDirectory>multivimdriver-openstack/azure</baseDirectory-->
+</assembly>
diff --git a/azure/azure/__init__.py b/azure/azure/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/azure/azure/__init__.py
diff --git a/azure/azure/api_v2/__init__.py b/azure/azure/api_v2/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/azure/azure/api_v2/__init__.py
diff --git a/azure/azure/api_v2/api_definition/__init__.py b/azure/azure/api_v2/api_definition/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/azure/azure/api_v2/api_definition/__init__.py
diff --git a/azure/azure/api_v2/api_definition/hosts.yaml b/azure/azure/api_v2/api_definition/hosts.yaml
new file mode 100644
index 0000000..88eaa09
--- /dev/null
+++ b/azure/azure/api_v2/api_definition/hosts.yaml
@@ -0,0 +1,72 @@
+---
+ info:
+ version: "1.0.0"
+ title: "Multi Cloud Host"
+ description: "Definition of Host API"
+ termsOfService: "http://swagger.io/terms/"
+ schemes:
+ - "http"
+ produces:
+ - "application/json"
+ paths:
+ /{vimid}/{tenantid}/hosts/{hostid}:
+ parameters:
+ - type: string
+ name: vimid
+ - type: string
+ format: uuid
+ name: tenantid
+ - type: string
+ name: hostid
+ in: path
+ required: true
+ get:
+ produces:
+ - "application/json"
+ responses:
+ "200":
+ schema:
+ $ref: "#/definitions/host"
+ get_all:
+ produces:
+ - "application/json"
+ responses:
+ "200":
+ schema:
+ type: "array"
+ items:
+ $ref: "#/definitions/host"
+ vim_path: "/compute/os-hypervisors"
+ definitions:
+ host:
+ plural_vim_resource: "hypervisors"
+ vim_resource: "hypervisor"
+ plural: "hosts"
+ properties:
+ name:
+ type: string
+ required: true
+ source: hypervisor.hypervisor_hostname
+ id:
+ type: string
+ required: true
+ source: hypervisor.id
+ status:
+ type: string
+ source: hypervisor.status
+ state:
+ type: string
+ source: hypervisor.state
+ cpu:
+ type: integer
+ minimal: 1
+ source: hypervisor.vcpus
+ action: copy
+ disk_gb:
+ type: integer
+ minimal: 0
+ source: hypervisor.local_gb
+ memory_mb:
+ type: integer
+ minimal: 0
+ source: hypervisor.memory_mb
diff --git a/azure/azure/api_v2/api_definition/images.yaml b/azure/azure/api_v2/api_definition/images.yaml
new file mode 100644
index 0000000..723884c
--- /dev/null
+++ b/azure/azure/api_v2/api_definition/images.yaml
@@ -0,0 +1,76 @@
+---
+ info:
+ version: "1.0.0"
+ title: "Multi Cloud Image"
+ description: "Definition of Image API"
+ termsOfService: "http://swagger.io/terms/"
+ schemes:
+ - "http"
+ produces:
+ - "application/json"
+ paths:
+ /{vimid}/{tenantid}/images/{imageid}:
+ parameters:
+ - type: string
+ name: vimid
+ - type: string
+ format: uuid
+ name: tenantid
+ - type: string
+ name: imageid
+ in: path
+ required: true
+ get:
+ produces:
+ - "application/json"
+ responses:
+ "200":
+ schema:
+ $ref: "#/definitions/image"
+ get_all:
+ produces:
+ - "application/json"
+ responses:
+ "200":
+ schema:
+ type: "array"
+ items:
+ $ref: "#/definitions/image"
+ post:
+ produces:
+ - "application/json"
+ responses:
+ "200":
+ schema:
+ $ref: "#/definitions/image"
+ delete:
+ responses: "204"
+ vim_path: "/image/v2/images"
+ definitions:
+ image:
+ plural_vim_resource: "images"
+ vim_resource: "image"
+ plural: "images"
+ properties:
+ name:
+ type: string
+ required: true
+ source: image.name
+ id:
+ type: string
+ source: image.id
+ status:
+ type: string
+ source: image.status
+ imageType:
+ type: string
+ source: image.disk_format
+ containerFormat:
+ type: string
+ source: image.container_format
+ visibility:
+ type: string
+ source: image.visibility
+ size:
+ type: integer
+ source: image.size \ No newline at end of file
diff --git a/azure/azure/api_v2/api_definition/networks.yaml b/azure/azure/api_v2/api_definition/networks.yaml
new file mode 100644
index 0000000..c00808f
--- /dev/null
+++ b/azure/azure/api_v2/api_definition/networks.yaml
@@ -0,0 +1,91 @@
+---
+ info:
+ version: "1.0.0"
+ title: "Multi Cloud Network"
+ description: "Definition of Host API"
+ termsOfService: "http://swagger.io/terms/"
+ schemes:
+ - "http"
+ produces:
+ - "application/json"
+ paths:
+ /{vimid}/{tenantid}/networks/{networkid}:
+ parameters:
+ - type: string
+ name: vimid
+ - type: string
+ format: uuid
+ name: tenantid
+ - type: string
+ name: networkid
+ in: path
+ required: true
+ get:
+ produces:
+ - "application/json"
+ responses:
+ "200":
+ schema:
+ $ref: "#/definitions/network"
+ get_all:
+ produces:
+ - "application/json"
+ responses:
+ "200":
+ schema:
+ type: "array"
+ items:
+ $ref: "#/definitions/network"
+ post:
+ produces:
+ - "application/json"
+ responses:
+ "200":
+ schema:
+ $ref: "#/definitions/network"
+ delete:
+ responses: "204"
+ vim_path: "/network/v2.0/networks"
+ definitions:
+ network:
+ plural_vim_resource: "networks"
+ vim_resource: "network"
+ plural: "networks"
+ properties:
+ name:
+ type: string
+ required: true
+ source: network.name
+ id:
+ type: string
+ source: network.id
+ status:
+ type: string
+ source: network.status
+ segmentationId:
+ type: string
+ source: network.provider:segmentation_id
+ default: None
+ physicalNetwork:
+ type: string
+ source: network.provider:physical_network
+ default: None
+ networkType:
+ type: string
+ source: network.provider:network_type
+ default: None
+ tenantId:
+ type: string
+ source: network.tenant_id
+ shared:
+ type: boolean
+ source: network.shared
+ required: true
+ routerExternal:
+ type: boolean
+ source: network.router:external
+ required: true
+ vlanTransparent:
+ type: boolean
+ source: network.vlan_transparent
+ default: false
diff --git a/azure/azure/api_v2/api_definition/ports.yaml b/azure/azure/api_v2/api_definition/ports.yaml
new file mode 100644
index 0000000..e159593
--- /dev/null
+++ b/azure/azure/api_v2/api_definition/ports.yaml
@@ -0,0 +1,83 @@
+---
+ info:
+ version: "1.0.0"
+ title: "Multi Cloud Port"
+ description: "Definition of Port API"
+ termsOfService: "http://swagger.io/terms/"
+ schemes:
+ - "http"
+ produces:
+ - "application/json"
+ paths:
+ /{vimid}/{tenantid}/ports/{portid}:
+ parameters:
+ - type: string
+ name: vimid
+ - type: string
+ format: uuid
+ name: tenantid
+ - type: string
+ name: portid
+ in: path
+ required: true
+ get:
+ produces:
+ - "application/json"
+ responses:
+ "200":
+ schema:
+ $ref: "#/definitions/port"
+ get_all:
+ produces:
+ - "application/json"
+ responses:
+ "200":
+ schema:
+ type: "array"
+ items:
+ $ref: "#/definitions/port"
+ post:
+ produces:
+ - "application/json"
+ responses:
+ "200":
+ schema:
+ $ref: "#/definitions/port"
+ delete:
+ responses: "204"
+ vim_path: "/network/v2.0/ports"
+ definitions:
+ port:
+ plural_vim_resource: "ports"
+ vim_resource: "port"
+ plural: "port"
+ properties:
+ name:
+ type: string
+ required: true
+ source: port.name
+ id:
+ type: string
+ source: port.id
+ status:
+ type: string
+ source: port.status
+ networkId:
+ type: string
+ source: port.network_id
+ required: true
+ vnicType:
+ source: port.binding:vnic_type
+ securityGroups:
+ type: string
+ source: port.security_groups
+ tenantId:
+ type: string
+ source: port.tenant_id
+ macAddress:
+ type: string
+ source: port.mac_address
+ subnetId:
+ source: port.fixed_ips[0].subnet_id
+ ip:
+ source: port.fixed_ips[0].ip_address
diff --git a/azure/azure/api_v2/api_definition/subnets.yaml b/azure/azure/api_v2/api_definition/subnets.yaml
new file mode 100644
index 0000000..e48d570
--- /dev/null
+++ b/azure/azure/api_v2/api_definition/subnets.yaml
@@ -0,0 +1,88 @@
+---
+ info:
+ version: "1.0.0"
+ title: "Multi Cloud Subnet"
+ description: "Definition of Subnet API"
+ termsOfService: "http://swagger.io/terms/"
+ schemes:
+ - "http"
+ produces:
+ - "application/json"
+ paths:
+ /{vimid}/{tenantid}/subnets/{subnetid}:
+ parameters:
+ - type: string
+ name: vimid
+ - type: string
+ format: uuid
+ name: tenantid
+ - type: string
+ name: subnetid
+ in: path
+ required: true
+ get:
+ produces:
+ - "application/json"
+ responses:
+ "200":
+ schema:
+ $ref: "#/definitions/subnet"
+ get_all:
+ produces:
+ - "application/json"
+ responses:
+ "200":
+ schema:
+ type: "array"
+ items:
+ $ref: "#/definitions/subnet"
+ post:
+ produces:
+ - "application/json"
+ responses:
+ "200":
+ schema:
+ $ref: "#/definitions/subnet"
+ delete:
+ responses: "204"
+ vim_path: "/network/v2.0/subnets"
+ definitions:
+ subnet:
+ plural_vim_resource: "subnets"
+ vim_resource: "subnet"
+ plural: "subnets"
+ properties:
+ name:
+ type: string
+ required: true
+ source: subnet.name
+ id:
+ type: string
+ source: subnet.id
+ status:
+ type: string
+ source: subnet.status
+ networkId:
+ type: string
+ source: subnet.network_id
+ required: true
+ allocationPools:
+ source: subnet.allocation_pools
+ gatewayIp:
+ type: string
+ source: subnet.gateway_ip
+ default: None
+ tenantId:
+ type: string
+ source: subnet.tenant_id
+ enableDhcp:
+ type: boolean
+ source: subnet.enable_dhcp
+ ipVersion:
+ source: subnet.ip_version
+ dnsNameServers:
+ source: subnet.dns_nameservers
+ cidr:
+ source: subnet.cidr
+ hostRoutes:
+ source: subnet.host_routes
diff --git a/azure/azure/api_v2/api_definition/utils.py b/azure/azure/api_v2/api_definition/utils.py
new file mode 100644
index 0000000..3f5a8a1
--- /dev/null
+++ b/azure/azure/api_v2/api_definition/utils.py
@@ -0,0 +1,31 @@
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import pkg_resources
+import yaml
+
+
+def get_definition_list():
+ """ Get API Definition from YAML files. """
+
+ api_def = []
+ definition_dir = __name__[:__name__.rfind(".")]
+ for f in pkg_resources.resource_listdir(definition_dir, '.'):
+ if f.endswith(".yaml"):
+ with pkg_resources.resource_stream(definition_dir, f) as fd:
+ # TODO(xiaohhui): Should add exception handler to inform user
+ # of potential error.
+ api_def.append(yaml.safe_load(fd))
+
+ return api_def
diff --git a/azure/azure/api_v2/api_router/__init__.py b/azure/azure/api_v2/api_router/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/azure/azure/api_v2/api_router/__init__.py
diff --git a/azure/azure/api_v2/api_router/controller_builder.py b/azure/azure/api_v2/api_router/controller_builder.py
new file mode 100644
index 0000000..ec66268
--- /dev/null
+++ b/azure/azure/api_v2/api_router/controller_builder.py
@@ -0,0 +1,224 @@
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import json
+from keystoneauth1.identity import v2 as keystone_v2
+from keystoneauth1.identity import v3 as keystone_v3
+from keystoneauth1 import session
+import pecan
+from pecan import rest
+import re
+
+from azure.api_v2.api_definition import utils
+from azure.pub import exceptions
+from azure.pub.msapi import extsys
+
+
+OBJ_IN_ARRAY = "(\w+)\[(\d+)\]\.(\w+)"
+
+
+def _get_vim_auth_session(vim_id, tenant_id):
+ """ Get the auth session to the given backend VIM """
+
+ try:
+ vim = extsys.get_vim_by_id(vim_id)
+ except exceptions.VimDriverAzureException as e:
+ return pecan.abort(500, str(e))
+
+ params = {
+ "auth_url": vim["url"],
+ "username": vim["userName"],
+ "password": vim["password"],
+ }
+ params["tenant_id"] = tenant_id
+
+ if '/v2' in params["auth_url"]:
+ auth = keystone_v2.Password(**params)
+ else:
+ params["user_domain_name"] = vim["domain"]
+ params["project_domain_name"] = vim["domain"]
+
+ if 'tenant_id' in params:
+ params["project_id"] = params.pop("tenant_id")
+ if 'tenant_name' in params:
+ params["project_name"] = params.pop("tenant_name")
+ if '/v3' not in params["auth_url"]:
+ params["auth_url"] = params["auth_url"] + "/v3",
+ auth = keystone_v3.Password(**params)
+
+ return session.Session(auth=auth)
+
+
+def _convert_default_value(default):
+ return {"None": None, "true": True, "false": False}[default]
+
+
+def _property_exists(resource, attr, required=False):
+ if attr not in resource:
+ if required:
+ raise Exception("Required field %s is missed in VIM "
+ "resource %s", (attr, resource))
+ else:
+ return False
+
+ return True
+
+
+def _convert_vim_res_to_mc_res(vim_resource, res_properties):
+ mc_resource = {}
+ for key in res_properties:
+ vim_res, attr = res_properties[key]["source"].split('.', 1)
+ # action = res_properties[key].get("action", "copy")
+ if re.match(OBJ_IN_ARRAY, attr):
+ attr, index, sub_attr = re.match(OBJ_IN_ARRAY, attr).groups()
+ if _property_exists(vim_resource[vim_res], attr):
+ mc_resource[key] = (
+ vim_resource[vim_res][attr][int(index)][sub_attr])
+ else:
+ if _property_exists(vim_resource[vim_res], attr,
+ res_properties[key].get("required")):
+ mc_resource[key] = vim_resource[vim_res][attr]
+ else:
+ if "default" in res_properties[key]:
+ mc_resource[key] = _convert_default_value(
+ res_properties[key]["default"])
+
+ return mc_resource
+
+
+def _convert_mc_res_to_vim_res(mc_resource, res_properties):
+ vim_resource = {}
+ for key in res_properties:
+ vim_res, attr = res_properties[key]["source"].split('.', 1)
+ # action = res_properties[key].get("action", "copy")
+ if re.match(OBJ_IN_ARRAY, attr):
+ attr, index, sub_attr = re.match(OBJ_IN_ARRAY, attr).groups()
+ if _property_exists(mc_resource, key):
+ vim_resource[attr] = vim_resource.get(attr, [])
+ if vim_resource[attr]:
+ vim_resource[attr][0].update({sub_attr: mc_resource[key]})
+ else:
+ vim_resource[attr].append({sub_attr: mc_resource[key]})
+ else:
+ if _property_exists(mc_resource, key,
+ res_properties[key].get("required")):
+ vim_resource[attr] = mc_resource[key]
+
+ return vim_resource
+
+
+def _build_api_controller(api_meta):
+ # Assume that only one path
+ path, path_meta = api_meta['paths'].items()[0]
+ # url path is behind third slash. The first is vimid, the second is
+ # tenantid.
+ path = path.split("/")[3]
+ controller_name = path.upper() + "Controller"
+ delimiter = path_meta["vim_path"].find("/", 1)
+ service_type = path_meta["vim_path"][1:delimiter]
+ resource_url = path_meta["vim_path"][delimiter:]
+
+ # Assume there is only one resource.
+ name, resource_meta = api_meta['definitions'].items()[0]
+ resource_properties = resource_meta['properties']
+
+ controller_meta = {}
+ if "get" in path_meta:
+ # Add the get method to controller.
+ @pecan.expose("json")
+ def _get(self, vim_id, tenant_id, resource_id):
+ """ General GET """
+ session = _get_vim_auth_session(vim_id, tenant_id)
+ service = {'service_type': service_type,
+ 'interface': 'public'}
+ full_url = resource_url + "/%s" % resource_id
+ resp = session.get(full_url, endpoint_filter=service)
+ mc_res = _convert_vim_res_to_mc_res(resp.json(),
+ resource_properties)
+ mc_res.update({"vimName": vim_id,
+ "vimId": vim_id,
+ "tenantId": tenant_id,
+ "returnCode": 0})
+ return mc_res
+
+ controller_meta["get"] = _get
+
+ if "get_all" in path_meta:
+ # Add the get_all method to controller.
+ @pecan.expose("json")
+ def _get_all(self, vim_id, tenant_id):
+ """ General GET all """
+ session = _get_vim_auth_session(vim_id, tenant_id)
+ service = {'service_type': service_type,
+ 'interface': 'public'}
+ resp = session.get(resource_url, endpoint_filter=service)
+ vim_res = resp.json()[resource_meta['plural_vim_resource']]
+ mc_res = [_convert_vim_res_to_mc_res(
+ {resource_meta['vim_resource']: v},
+ resource_properties)
+ for v in vim_res]
+ return {"vimName": vim_id,
+ resource_meta['plural']: mc_res,
+ "tenantId": tenant_id,
+ "vimid": vim_id}
+
+ controller_meta["get_all"] = _get_all
+
+ if "post" in path_meta:
+ # Add the post method to controller.
+ @pecan.expose("json")
+ def _post(self, vim_id, tenant_id):
+ """ General POST """
+ session = _get_vim_auth_session(vim_id, tenant_id)
+ service = {'service_type': service_type,
+ 'interface': 'public'}
+ vim_res = _convert_mc_res_to_vim_res(pecan.request.json_body,
+ resource_properties)
+
+ req_body = json.JSONEncoder().encode(
+ {resource_meta['vim_resource']: vim_res})
+ resp = session.post(resource_url,
+ data=req_body,
+ endpoint_filter=service)
+ mc_res = _convert_vim_res_to_mc_res(resp.json(),
+ resource_properties)
+ mc_res.update({"vimName": vim_id,
+ "vimId": vim_id,
+ "tenantId": tenant_id,
+ "returnCode": 0})
+ return mc_res
+
+ controller_meta["post"] = _post
+
+ if "delete" in path_meta:
+ # Add the delete method to controller.
+ @pecan.expose("json")
+ def _delete(self, vim_id, tenant_id, resource_id):
+ """ General DELETE """
+ session = _get_vim_auth_session(vim_id, tenant_id)
+ service = {'service_type': service_type,
+ 'interface': 'public'}
+ full_url = resource_url + "/%s" % resource_id
+ session.delete(full_url, endpoint_filter=service)
+
+ controller_meta["delete"] = _delete
+
+ return path, type(controller_name, (rest.RestController,), controller_meta)
+
+
+def insert_dynamic_controller(root_controller):
+ api_defs = utils.get_definition_list()
+ for d in api_defs:
+ path, con_class = _build_api_controller(d)
+ setattr(root_controller, path, con_class())
diff --git a/azure/azure/api_v2/api_router/root.py b/azure/azure/api_v2/api_router/root.py
new file mode 100644
index 0000000..0b6f995
--- /dev/null
+++ b/azure/azure/api_v2/api_router/root.py
@@ -0,0 +1,34 @@
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import pecan
+from pecan import rest
+
+from azure.api_v2.api_router import v0_controller
+
+
+class AzureController(rest.RestController):
+ v0 = v0_controller.V0_Controller()
+
+
+class APIController(rest.RestController):
+ pass
+
+
+# Pecan workaround for the dash in path.
+pecan.route(APIController, "multicloud-azure", AzureController())
+
+
+class RootController(object):
+ api = APIController()
diff --git a/azure/azure/api_v2/api_router/swagger_json.py b/azure/azure/api_v2/api_router/swagger_json.py
new file mode 100644
index 0000000..8573a46
--- /dev/null
+++ b/azure/azure/api_v2/api_router/swagger_json.py
@@ -0,0 +1,25 @@
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import pecan
+from pecan import rest
+
+from azure.swagger import utils
+
+
+class SwaggerJson(rest.RestController):
+
+ @pecan.expose("json")
+ def get(self):
+ return utils.get_swagger_json_data()
diff --git a/azure/azure/api_v2/api_router/v0_controller.py b/azure/azure/api_v2/api_router/v0_controller.py
new file mode 100644
index 0000000..104597a
--- /dev/null
+++ b/azure/azure/api_v2/api_router/v0_controller.py
@@ -0,0 +1,49 @@
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import pecan
+from pecan import rest
+
+from azure.api_v2.api_router import controller_builder
+from azure.api_v2.api_router import swagger_json
+
+
+class V0_Controller(rest.RestController):
+
+ def get(self, vim_id, tenant_id):
+ """ Placeholder for sub controllers. """
+ pecan.abort(405)
+
+ def put(self, vim_id, tenant_id):
+ """ Placeholder for sub controllers. """
+ pecan.abort(405)
+
+ def post(self, vim_id, tenant_id):
+ """ Placeholder for sub controllers. """
+ pecan.abort(405)
+
+ def delete(self, vim_id, tenant_id):
+ """ Placeholder for sub controllers. """
+ pecan.abort(405)
+
+ def get_all(self, vim_id, tenant_id):
+ """ Placeholder for sub controllers. """
+ pecan.abort(405)
+
+
+pecan.route(V0_Controller, "swagger.json", swagger_json.SwaggerJson())
+
+
+# Insert API stem from yaml files.
+controller_builder.insert_dynamic_controller(V0_Controller)
diff --git a/azure/azure/api_v2/app.py b/azure/azure/api_v2/app.py
new file mode 100644
index 0000000..2c13403
--- /dev/null
+++ b/azure/azure/api_v2/app.py
@@ -0,0 +1,35 @@
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import pecan
+
+
+def setup_app(config=None):
+ app_conf = {
+ 'root': "azure.api_v2.api_router.root.RootController",
+ 'modules': ["azure.api_v2"],
+ 'debug': True,
+ # NOTE: By default, guess_content_type_from_ext is True, and Pecan will
+ # strip the file extension from url. For example, ../../swagger.json
+ # will look like ../../swagger to Pecan API router. This makes other
+ # url like ../../swagger.txt get the same API route. Set this to False
+ # to do strict url mapping.
+ 'guess_content_type_from_ext': False
+ }
+ app = pecan.make_app(
+ app_conf.pop('root'),
+ **app_conf
+ )
+
+ return app
diff --git a/azure/azure/api_v2/service.py b/azure/azure/api_v2/service.py
new file mode 100644
index 0000000..2b73c15
--- /dev/null
+++ b/azure/azure/api_v2/service.py
@@ -0,0 +1,54 @@
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_concurrency import processutils
+from oslo_config import cfg
+from oslo_service import service
+from oslo_service import wsgi
+
+from azure.api_v2 import app
+from azure.pub.config import config as mc_cfg
+
+
+CONF = cfg.CONF
+
+
+class WSGIService(service.ServiceBase):
+ """Provides ability to launch API from wsgi app."""
+
+ def __init__(self):
+ self.app = app.setup_app()
+
+ self.workers = processutils.get_worker_count()
+
+ self.server = wsgi.Server(
+ CONF,
+ "azure",
+ self.app,
+ host="0.0.0.0",
+ port=mc_cfg.API_SERVER_PORT,
+ use_ssl=False
+ )
+
+ def start(self):
+ self.server.start()
+
+ def stop(self):
+ self.server.stop()
+
+ def wait(self):
+ self.server.wait()
+
+ def reset(self):
+ self.server.reset()
diff --git a/azure/azure/event_listener/__init__.py b/azure/azure/event_listener/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/azure/azure/event_listener/__init__.py
diff --git a/azure/azure/event_listener/i18n.py b/azure/azure/event_listener/i18n.py
new file mode 100644
index 0000000..8fd9a6e
--- /dev/null
+++ b/azure/azure/event_listener/i18n.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+
+import oslo_i18n
+
+DOMAIN = "test_oslo"
+
+_translators = oslo_i18n.TranslatorFactory(domain=DOMAIN)
+
+# The primary translation function using the well-known name "_"
+_ = _translators.primary
+
+# The contextual translation function using the name "_C"
+_C = _translators.contextual_form
+
+# The plural translation function using the name "_P"
+_P = _translators.plural_form
+
+# Translators for log levels.
+#
+# The abbreviated names are meant to reflect the usual use of a short
+# name like '_'. The "L" is for "log" and the other letter comes from
+# the level.
+_LI = _translators.log_info
+_LW = _translators.log_warning
+_LE = _translators.log_error
+_LC = _translators.log_critical
diff --git a/azure/azure/event_listener/listener.conf b/azure/azure/event_listener/listener.conf
new file mode 100644
index 0000000..4376fe7
--- /dev/null
+++ b/azure/azure/event_listener/listener.conf
@@ -0,0 +1,15 @@
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+
+[Listener]
+rabbit_ip=10.154.9.172
+rabbit_passwd=6C2B96AsbinmFf1a9c6a \ No newline at end of file
diff --git a/azure/azure/event_listener/server.py b/azure/azure/event_listener/server.py
new file mode 100644
index 0000000..7f1f830
--- /dev/null
+++ b/azure/azure/event_listener/server.py
@@ -0,0 +1,126 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+
+from oslo_config import cfg
+from oslo_log import log as logging
+from i18n import _LI
+import oslo_messaging
+import ConfigParser
+import json
+import os
+import requests
+from azure.pub.config.config import MR_ADDR
+from azure.pub.config.config import MR_PORT
+
+
+LOG = logging.getLogger(__name__)
+
+
+def prepare():
+
+ product_name = "oslo_server"
+ logging.register_options(cfg.CONF)
+ logging.setup(cfg.CONF, product_name)
+
+
+'''
+below items must be added into vio nova.conf then restart nova services:
+notification_driver=messaging
+notification_topics= notifications_test
+notify_on_state_change=vm_and_task_state
+notify_on_any_change=True
+instance_usage_audit=True
+instance_usage_audit_period=hour
+'''
+
+
+def getConfig(section, key):
+
+ config = ConfigParser.ConfigParser()
+ path = os.path.split(os.path.realpath(__file__))[0] + '/listener.conf'
+ config.read(path)
+ return config.get(section, key)
+
+
+class NotificationEndPoint():
+
+ filter_rule = oslo_messaging.NotificationFilter(
+ publisher_id='^compute.*')
+
+ def info(self, ctxt, publisher_id, event_type, payload, metadata):
+
+ VM_EVENTS = {
+ 'compute.instance.unpause.start',
+ 'compute.instance.pause.start',
+ 'compute.instance.power_off.start',
+ 'compute.instance.reboot.start',
+ 'compute.instance.create.start'
+ }
+
+ status = payload.get('state_description')
+ if status != '' and event_type in VM_EVENTS:
+ url = 'http://%s:%s/events/test' % (MR_ADDR, MR_PORT)
+ headers = {'Content-type': 'application/json'}
+ requests.post(url, json.dumps(payload), headers=headers)
+
+ LOG.info(event_type)
+ self.action(payload)
+
+ def action(self, data):
+ LOG.info(_LI(json.dumps(data)))
+
+
+class Server(object):
+
+ def __init__(self):
+ self.topic = 'notifications_test'
+ self.server = None
+ prepare()
+
+
+class NotificationServer(Server):
+
+ def __init__(self):
+ super(NotificationServer, self).__init__()
+ # rabbit IP and password come from listener.conf
+ url = 'rabbit://test:%s@%s:5672/' % (
+ getConfig('Listener', 'rabbit_passwd'),
+ getConfig('Listener', 'rabbit_ip')
+ )
+ self.transport = oslo_messaging.get_notification_transport(
+ cfg.CONF,
+ url=url)
+ # The exchange must be the same as
+ # control_exchange in transport setting in client.
+ self.targets = [oslo_messaging.Target(
+ topic=self.topic,
+ exchange='nova')]
+ self.endpoints = [NotificationEndPoint()]
+
+ def start(self):
+ LOG.info(_LI("Start Notification server..."))
+ self.server = oslo_messaging.get_notification_listener(
+ self.transport,
+ self.targets,
+ self.endpoints,
+ executor='threading')
+ self.server.start()
+ self.server.wait()
+
+
+if __name__ == '__main__':
+
+ notification_server = NotificationServer()
+ notification_server.start()
diff --git a/azure/azure/middleware.py b/azure/azure/middleware.py
new file mode 100644
index 0000000..1edc44b
--- /dev/null
+++ b/azure/azure/middleware.py
@@ -0,0 +1,59 @@
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+
+
+import uuid
+from onaplogging.mdcContext import MDC
+from azure.pub.config.config import SERVICE_NAME
+from azure.pub.config.config import FORWARDED_FOR_FIELDS
+
+
+class LogContextMiddleware(object):
+
+ # the last IP behind multiple proxies, if no exist proxies
+ # get local host ip.
+ def _getLastIp(self, request):
+
+ ip = ""
+ try:
+ for field in FORWARDED_FOR_FIELDS:
+ if field in request.META:
+ if ',' in request.META[field]:
+ parts = request.META[field].split(',')
+ ip = parts[-1].strip().split(":")[0]
+ else:
+ ip = request.META[field].split(":")[0]
+
+ if ip == "":
+ ip = request.META.get("HTTP_HOST").split(":")[0]
+
+ except Exception:
+ pass
+
+ return ip
+
+ def process_request(self, request):
+
+ ReqeustID = request.META.get("HTTP_X_TRANSACTIONID", None)
+ if ReqeustID is None:
+ ReqeustID = str(uuid.uuid3(uuid.NAMESPACE_URL, SERVICE_NAME))
+ MDC.put("requestID", ReqeustID)
+ InovocationID = str(uuid.uuid4())
+ MDC.put("invocationID", InovocationID)
+ MDC.put("serviceName", SERVICE_NAME)
+ MDC.put("serviceIP", self._getLastIp(request))
+ return None
+
+ def process_response(self, request, response):
+
+ MDC.clear()
+ return response
diff --git a/azure/azure/pub/__init__.py b/azure/azure/pub/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/azure/azure/pub/__init__.py
diff --git a/azure/azure/pub/config/__init__.py b/azure/azure/pub/config/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/azure/azure/pub/config/__init__.py
diff --git a/azure/azure/pub/config/config.py b/azure/azure/pub/config/config.py
new file mode 100644
index 0000000..db09fd6
--- /dev/null
+++ b/azure/azure/pub/config/config.py
@@ -0,0 +1,41 @@
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+
+import os
+
+# [MSB]
+MSB_SERVICE_IP = "msb.onap.org"
+MSB_SERVICE_PORT = "10080"
+
+# [IMAGE LOCAL PATH]
+ROOT_PATH = os.path.dirname(os.path.dirname(
+ os.path.dirname(os.path.abspath(__file__))))
+
+# [A&AI]
+AAI_ADDR = "aai.api.simpledemo.openecomp.org"
+AAI_PORT = "8443"
+AAI_SERVICE_URL = 'https://%s:%s/aai' % (AAI_ADDR, AAI_PORT)
+AAI_SCHEMA_VERSION = "v13"
+AAI_USERNAME = "AAI"
+AAI_PASSWORD = "AAI"
+
+# [DMaaP]
+MR_ADDR = ""
+MR_PORT = ""
+
+# [MDC]
+SERVICE_NAME = "multicloud-azure"
+FORWARDED_FOR_FIELDS = ["HTTP_X_FORWARDED_FOR", "HTTP_X_FORWARDED_HOST",
+ "HTTP_X_FORWARDED_SERVER"]
+
+# [Local Config]
+API_SERVER_PORT = 9004
diff --git a/azure/azure/pub/config/log.yml b/azure/azure/pub/config/log.yml
new file mode 100644
index 0000000..7bbc427
--- /dev/null
+++ b/azure/azure/pub/config/log.yml
@@ -0,0 +1,26 @@
+version: 1
+disable_existing_loggers: False
+
+loggers:
+ azure:
+ handlers: [azure_handler]
+ level: "DEBUG"
+ propagate: False
+handlers:
+ azure_handler:
+ level: "DEBUG"
+ class: "logging.handlers.RotatingFileHandler"
+ filename: "/var/log/onap/multicloud/azure/azure.log"
+ formatter: "mdcFormat"
+ maxBytes: 52428800
+ backupCount: 10
+formatters:
+ standard:
+ format: "%(asctime)s|||||%(name)s||%(thread)||%(funcName)s||%(levelname)s||%(message)s"
+ mdcFormat:
+ format: "%(asctime)s|||||%(name)s||%(thread)s||%(funcName)s||%(levelname)s||%(message)s||||%(mdc)s \t"
+ mdcfmt: "{requestID} {invocationID} {serviceName} {serviceIP}"
+ datefmt: "%Y-%m-%d %H:%M:%S"
+ (): onaplogging.mdcformatter.MDCFormatter
+
+
diff --git a/azure/azure/pub/database/__init__.py b/azure/azure/pub/database/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/azure/azure/pub/database/__init__.py
diff --git a/azure/azure/pub/database/models.py b/azure/azure/pub/database/models.py
new file mode 100644
index 0000000..757430b
--- /dev/null
+++ b/azure/azure/pub/database/models.py
@@ -0,0 +1,23 @@
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+
+from django.db import models
+
+
+class VimInstModel(models.Model):
+ class Meta:
+ db_table = 'vim_inst_type_mapping'
+
+ vimid = models.CharField(
+ db_column='VIMID', primary_key=True, max_length=200)
+ vimtype = models.CharField(db_column="VIMTYPE", max_length=200)
+ viminst_url = models.CharField(db_column="VIMINSTURL", max_length=200)
diff --git a/azure/azure/pub/exceptions.py b/azure/azure/pub/exceptions.py
new file mode 100644
index 0000000..3f38f2c
--- /dev/null
+++ b/azure/azure/pub/exceptions.py
@@ -0,0 +1,66 @@
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+
+
+class ClientException(Exception):
+
+ message = "ClientException"
+
+ def __init__(self, message=None):
+ self.message = message or self.message
+ super(ClientException, self).__init__(self.message)
+
+
+class ServerException(Exception):
+
+ message = "ServerException"
+
+ def __init__(self, message=None, status_code="", content=""):
+ super(ServerException, self).__init__(message)
+ self.message = message or self.message
+ self.status_code = status_code
+ self.content = content
+
+
+class RetriableConnectionFailure(Exception):
+ pass
+
+
+class ConnectionError(ClientException):
+ message = "Cannot connect to API service."
+
+
+class ConnectTimeout(ConnectionError, RetriableConnectionFailure):
+ message = "Timed out connecting to service."
+
+
+class ConnectFailure(ConnectionError, RetriableConnectionFailure):
+ message = "Connection failure that may be retried."
+
+
+class SSLError(ConnectionError):
+ message = "An SSL error occurred."
+
+
+class UnknownConnectionError(ConnectionError):
+
+ def __init__(self, msg, original):
+ super(UnknownConnectionError, self).__init__(msg)
+ self.original = original
+
+
+class NotFoundError(ServerException):
+ message = "Cannot find value"
+
+
+class VimDriverAzureException(ServerException):
+ message = "Cannot find vim driver"
diff --git a/azure/azure/pub/msapi/__init__.py b/azure/azure/pub/msapi/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/azure/azure/pub/msapi/__init__.py
diff --git a/azure/azure/pub/msapi/extsys.py b/azure/azure/pub/msapi/extsys.py
new file mode 100644
index 0000000..f0b9dcc
--- /dev/null
+++ b/azure/azure/pub/msapi/extsys.py
@@ -0,0 +1,46 @@
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+
+import logging
+
+from azure.pub.utils.restcall import AAIClient
+
+logger = logging.getLogger(__name__)
+
+
+def split_vim_to_owner_region(vim_id):
+ split_vim = vim_id.split('_')
+ cloud_owner = split_vim[0]
+ cloud_region = "".join(split_vim[1:])
+ return cloud_owner, cloud_region
+
+
+def get_vim_by_id(vim_id):
+ cloud_owner, cloud_region = split_vim_to_owner_region(vim_id)
+ client = AAIClient(cloud_owner, cloud_region)
+ ret = client.get_vim(get_all=True)
+ esrInfo = ret['esr-system-info-list']['esr-system-info'][0]
+ data = {
+ 'type': ret['cloud-type'],
+ 'version': ret['cloud-region-version'],
+ 'vimId': vim_id,
+ 'name': vim_id,
+ 'userName': esrInfo['user-name'],
+ 'password': esrInfo['password'],
+ 'tenant': esrInfo['default-tenant'],
+ 'url': esrInfo['service-url'],
+ 'domain': esrInfo['cloud-domain'],
+ 'cacert': esrInfo.get('ssl-cacert', ""),
+ 'insecure': esrInfo.get('ssl-insecure', False)
+ }
+ ret.update(data)
+ return ret
diff --git a/azure/azure/pub/utils/__init__.py b/azure/azure/pub/utils/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/azure/azure/pub/utils/__init__.py
diff --git a/azure/azure/pub/utils/enumutil.py b/azure/azure/pub/utils/enumutil.py
new file mode 100644
index 0000000..eb7a22c
--- /dev/null
+++ b/azure/azure/pub/utils/enumutil.py
@@ -0,0 +1,15 @@
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+
+
+def enum(**enums):
+ return type('Enum', (), enums)
diff --git a/azure/azure/pub/utils/fileutil.py b/azure/azure/pub/utils/fileutil.py
new file mode 100644
index 0000000..1868300
--- /dev/null
+++ b/azure/azure/pub/utils/fileutil.py
@@ -0,0 +1,50 @@
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+import os
+import shutil
+import logging
+import traceback
+import urllib2
+
+logger = logging.getLogger(__name__)
+
+
+def make_dirs(path):
+ if not os.path.exists(path):
+ os.makedirs(path, 0777)
+
+
+def delete_dirs(path):
+ try:
+ if os.path.exists(path):
+ shutil.rmtree(path)
+ except Exception as e:
+ logger.error(traceback.format_exc())
+ logger.error("Failed to delete %s:%s", path, e.message)
+
+
+def download_file_from_http(url, local_dir, file_name):
+ local_file_name = os.path.join(local_dir, file_name)
+ is_download_ok = False
+ try:
+ make_dirs(local_dir)
+ r = urllib2.Request(url)
+ req = urllib2.urlopen(r)
+ save_file = open(local_file_name, 'wb')
+ save_file.write(req.read())
+ save_file.close()
+ req.close()
+ is_download_ok = True
+ except Exception:
+ logger.error(traceback.format_exc())
+ logger.error("Failed to download %s to %s.", url, local_file_name)
+ return is_download_ok, local_file_name
diff --git a/azure/azure/pub/utils/idutil.py b/azure/azure/pub/utils/idutil.py
new file mode 100644
index 0000000..be6e8a0
--- /dev/null
+++ b/azure/azure/pub/utils/idutil.py
@@ -0,0 +1,18 @@
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+from redisco import containers as cont
+
+
+def get_auto_id(id_type, id_group="auto_id_hash"):
+ auto_id_hash = cont.Hash(id_group)
+ auto_id_hash.hincrby(id_type, 1)
+ return auto_id_hash.hget(id_type)
diff --git a/azure/azure/pub/utils/restcall.py b/azure/azure/pub/utils/restcall.py
new file mode 100644
index 0000000..4b28098
--- /dev/null
+++ b/azure/azure/pub/utils/restcall.py
@@ -0,0 +1,782 @@
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+
+import sys
+import traceback
+import logging
+import urllib2
+import uuid
+import httplib2
+import json
+
+from azure.pub.config.config import AAI_SCHEMA_VERSION
+from azure.pub.config.config import AAI_SERVICE_URL
+from azure.pub.config.config import AAI_USERNAME
+from azure.pub.config.config import AAI_PASSWORD
+from azure.pub.config.config import MSB_SERVICE_IP, MSB_SERVICE_PORT
+
+from azure.pub.exceptions import VimDriverAzureException
+
+rest_no_auth, rest_oneway_auth, rest_bothway_auth = 0, 1, 2
+HTTP_200_OK, HTTP_201_CREATED = '200', '201'
+HTTP_204_NO_CONTENT, HTTP_202_ACCEPTED = '204', '202'
+status_ok_list = [HTTP_200_OK, HTTP_201_CREATED,
+ HTTP_204_NO_CONTENT, HTTP_202_ACCEPTED]
+HTTP_404_NOTFOUND, HTTP_403_FORBIDDEN = '404', '403'
+HTTP_401_UNAUTHORIZED, HTTP_400_BADREQUEST = '401', '400'
+
+logger = logging.getLogger(__name__)
+
+
+def call_req(base_url, user, passwd, auth_type, resource, method, content='',
+ headers=None):
+ callid = str(uuid.uuid1())
+# logger.debug("[%s]call_req('%s','%s','%s',%s,'%s','%s','%s')" % (
+# callid, base_url, user, passwd, auth_type, resource, method, content))
+ ret = None
+ resp_status = ''
+ resp = ""
+ full_url = ""
+
+ try:
+ full_url = combine_url(base_url, resource)
+ if headers is None:
+ headers = {}
+ headers['content-type'] = 'application/json'
+
+ if user:
+ headers['Authorization'] = 'Basic ' + \
+ ('%s:%s' % (user, passwd)).encode("base64")
+ ca_certs = None
+ for retry_times in range(3):
+ http = httplib2.Http(
+ ca_certs=ca_certs,
+ disable_ssl_certificate_validation=(
+ auth_type == rest_no_auth))
+ http.follow_all_redirects = True
+ try:
+ logger.debug("request=%s" % full_url)
+ resp, resp_content = http.request(
+ full_url, method=method.upper(), body=content,
+ headers=headers)
+ resp_status = resp['status']
+ resp_body = resp_content.decode('UTF-8')
+
+ if resp_status in status_ok_list:
+ ret = [0, resp_body, resp_status, resp]
+ else:
+ ret = [1, resp_body, resp_status, resp]
+ break
+ except Exception as ex:
+ if 'httplib.ResponseNotReady' in str(sys.exc_info()):
+ logger.error(traceback.format_exc())
+ ret = [1, "Unable to connect to %s" % full_url,
+ resp_status, resp]
+ continue
+ raise ex
+ except urllib2.URLError as err:
+ ret = [2, str(err), resp_status, resp]
+ except Exception as ex:
+ logger.error(traceback.format_exc())
+ logger.error("[%s]ret=%s" % (callid, str(sys.exc_info())))
+ res_info = str(sys.exc_info())
+ if 'httplib.ResponseNotReady' in res_info:
+ res_info = ("The URL[%s] request failed or is not responding." %
+ full_url)
+ ret = [3, res_info, resp_status, resp]
+# logger.debug("[%s]ret=%s" % (callid, str(ret)))
+ return ret
+
+
+def req_by_msb(resource, method, content=''):
+ base_url = "http://%s:%s/" % (MSB_SERVICE_IP, MSB_SERVICE_PORT)
+ return call_req(base_url, "", "", rest_no_auth, resource, method, content)
+
+
+def combine_url(base_url, resource):
+ full_url = None
+ if base_url.endswith('/') and resource.startswith('/'):
+ full_url = base_url[:-1] + resource
+ elif base_url.endswith('/') and not resource.startswith('/'):
+ full_url = base_url + resource
+ elif not base_url.endswith('/') and resource.startswith('/'):
+ full_url = base_url + resource
+ else:
+ full_url = base_url + '/' + resource
+ return full_url
+
+
+def get_res_from_aai(resource, content=''):
+ headers = {
+ 'X-FromAppId': 'MultiCloud',
+ 'X-TransactionId': '9001',
+ 'content-type': 'application/json',
+ 'accept': 'application/json'
+ }
+ base_url = "%s/%s" % (AAI_SERVICE_URL, AAI_SCHEMA_VERSION)
+ return call_req(base_url, AAI_USERNAME, AAI_PASSWORD, rest_no_auth,
+ resource, "GET", content, headers)
+
+
+class AAIClient(object):
+ def __init__(self, cloud_owner, cloud_region):
+ self.base_url = "%s/%s" % (AAI_SERVICE_URL, AAI_SCHEMA_VERSION)
+ self.username = AAI_USERNAME
+ self.password = AAI_PASSWORD
+ self.default_headers = {
+ 'X-FromAppId': 'multicloud-openstack-vmware',
+ 'X-TransactionId': '9004',
+ 'content-type': 'application/json',
+ 'accept': 'application/json'
+ }
+ self.cloud_owner = cloud_owner
+ self.cloud_region = cloud_region
+ self._vim_info = None
+
+ def get_vim(self, get_all=False):
+ resource = ("/cloud-infrastructure/cloud-regions/cloud-region"
+ "/%s/%s" % (self.cloud_owner, self.cloud_region))
+ if get_all:
+ resource = "%s?depth=all" % resource
+ resp = call_req(self.base_url, self.username, self.password,
+ rest_no_auth, resource, "GET",
+ headers=self.default_headers)
+ if resp[0] != 0:
+ raise VimDriverAzureException(
+ status_code=404,
+ content="Failed to query VIM with id (%s_%s) from extsys." % (
+ self.cloud_owner, self.cloud_region))
+ return json.loads(resp[1])
+
+ def delete_vim(self):
+ resp = self.get_vim(get_all=True)
+ logger.debug('Delete tenants')
+ self._del_tenants(resp)
+ logger.debug('Delete images')
+ self._del_images(resp)
+ logger.debug('Delete flavors')
+ self._del_flavors(resp)
+ logger.debug('Delete networks')
+ self._del_networks(resp)
+ logger.debug('Delete availability zones')
+ self._del_azs(resp)
+ logger.debug('Delete cloud region')
+ resource = ("/cloud-infrastructure/cloud-regions/cloud-region"
+ "/%s/%s?resource-version=%s" %
+ (self.cloud_owner, self.cloud_region,
+ resp['resource-version']))
+ resp = call_req(self.base_url, self.username, self.password,
+ rest_no_auth, resource, "DELETE",
+ headers=self.default_headers)
+ if resp[0] != 0:
+ raise VimDriverAzureException(
+ status_code=400,
+ content="Failed to delete cloud %s_%s: %s." % (
+ self.cloud_owner, self.cloud_region, resp[1]))
+
+ def update_vim(self, content):
+ # update identity url
+ self.update_identity_url()
+ # update tenants
+ self.add_tenants(content)
+ # update flavors
+ self.add_images(content)
+ # update images
+ self.add_flavors(content)
+ # update networks
+ self.add_networks(content)
+ # update pservers
+ self.add_pservers(content)
+
+ def update_identity_url(self):
+ vim = self.get_vim()
+ vim['identity-url'] = ("http://%s/api/multicloud/v0/%s_%s/identity/"
+ "v3" % (MSB_SERVICE_IP, self.cloud_owner,
+ self.cloud_region))
+ resource = ("/cloud-infrastructure/cloud-regions/cloud-region"
+ "/%s/%s" % (self.cloud_owner, self.cloud_region))
+ logger.debug("Updating identity url %s" % vim)
+ call_req(self.base_url, self.username, self.password,
+ rest_no_auth, resource, "PUT",
+ content=json.dumps(vim),
+ headers=self.default_headers)
+
+ def add_tenants(self, content):
+ for tenant in content['tenants']:
+ resource = ("/cloud-infrastructure/cloud-regions/cloud-region/"
+ "%s/%s/tenants/tenant/%s" % (
+ self.cloud_owner, self.cloud_region, tenant['id']))
+ body = {'tenant-name': tenant['name']}
+ logger.debug("Adding tenants to cloud region")
+ call_req(self.base_url, self.username, self.password,
+ rest_no_auth, resource, "PUT",
+ content=json.dumps(body),
+ headers=self.default_headers)
+
+ def add_flavors(self, content):
+ for flavor in content['flavors']:
+ resource = ("/cloud-infrastructure/cloud-regions/cloud-region/"
+ "%s/%s/flavors/flavor/%s" % (
+ self.cloud_owner, self.cloud_region, flavor['id']))
+ body = {
+ 'flavor-name': flavor['name'],
+ 'flavor-vcpus': flavor['vcpus'],
+ 'flavor-ram': flavor['ram'],
+ 'flavor-disk': flavor['disk'],
+ 'flavor-ephemeral': flavor['ephemeral'],
+ 'flavor-swap': flavor['swap'],
+ 'flavor-is-public': flavor['is_public'],
+ 'flavor-selflink': flavor['links'][0]['href'],
+ 'flavor-disabled': flavor['is_disabled']
+ }
+ # Handle extra specs
+ if flavor['name'].startswith("onap."):
+ hpa_capabilities = self._get_hpa_capabilities(
+ flavor)
+ body['hpa-capabilities'] = {
+ 'hpa-capability': hpa_capabilities}
+
+ logger.debug("Adding flavors to cloud region")
+ call_req(self.base_url, self.username, self.password,
+ rest_no_auth, resource, "PUT",
+ content=json.dumps(body),
+ headers=self.default_headers)
+
+ def add_images(self, content):
+ for image in content['images']:
+ resource = ("/cloud-infrastructure/cloud-regions/cloud-region/"
+ "%s/%s/images/image/%s" % (
+ self.cloud_owner, self.cloud_region, image['id']))
+ split_image_name = image['name'].split("-")
+ os_distro = split_image_name[0]
+ os_version = split_image_name[1] if \
+ len(split_image_name) > 1 else ""
+ body = {
+ 'image-name': image['name'],
+ # 'image-architecture': image[''],
+ 'image-os-distro': os_distro,
+ 'image-os-version': os_version,
+ # 'application': image[''],
+ # 'application-vendor': image[''],
+ # 'application-version': image[''],
+ # TODO replace this with image proxy endpoint
+ 'image-selflink': "",
+ }
+ logger.debug("Adding images to cloud region")
+ call_req(self.base_url, self.username, self.password,
+ rest_no_auth, resource, "PUT",
+ content=json.dumps(body),
+ headers=self.default_headers)
+
+ def add_networks(self, content):
+ for network in content['networks']:
+ resource = ("/cloud-infrastructure/cloud-regions/cloud-region/"
+ "%s/%s/oam-networks/oam-network/%s" % (
+ self.cloud_owner, self.cloud_region,
+ network['id']))
+ body = {
+ 'network-uuid': network['id'],
+ 'network-name': network['name'],
+ 'cvlan-tag': network['segmentationId'] or 0,
+ }
+ logger.debug("Adding networks to cloud region")
+ call_req(self.base_url, self.username, self.password,
+ rest_no_auth, resource, "PUT",
+ content=json.dumps(body),
+ headers=self.default_headers)
+
+ def add_pservers(self, content):
+ for hypervisor in content['hypervisors']:
+ resource = ("/cloud-infrastructure/pservers/pserver/%s" % (
+ hypervisor['name']))
+ body = {
+ # 'ptnii-equip-name'
+ 'number-of-cpus': hypervisor['vcpus'],
+ 'disk-in-gigabytes': hypervisor['local_disk_size'],
+ 'ram-in-megabytes': hypervisor['memory_size'],
+ # 'equip-type'
+ # 'equip-vendor'
+ # 'equip-model'
+ # 'fqdn'
+ # 'pserver-selflink'
+ 'ipv4-oam-address': hypervisor['host_ip'],
+ # 'serial-number'
+ # 'ipaddress-v4-loopback-0'
+ # 'ipaddress-v6-loopback-0'
+ # 'ipaddress-v4-aim'
+ # 'ipaddress-v6-aim'
+ # 'ipaddress-v6-oam'
+ # 'inv-status'
+ 'pserver-id': hypervisor['id'],
+ # 'internet-topology'
+ }
+ logger.debug("Adding pservers")
+ call_req(self.base_url, self.username, self.password,
+ rest_no_auth, resource, "PUT",
+ content=json.dumps(body),
+ headers=self.default_headers)
+ # update relationship
+ resource = ("/cloud-infrastructure/pservers/pserver/%s/"
+ "relationship-list/relationship" %
+ hypervisor['name'])
+ related_link = ("%s/cloud-infrastructure/cloud-regions/"
+ "cloud-region/%s/%s" % (
+ self.base_url, self.cloud_owner,
+ self.cloud_region))
+ body = {
+ 'related-to': 'cloud-region',
+ 'related-link': related_link,
+ 'relationship-data': [
+ {
+ 'relationship-key': 'cloud-region.cloud-owner',
+ 'relationship-value': self.cloud_owner
+ },
+ {
+ 'relationship-key': 'cloud-region.cloud-region-id',
+ 'relationship-value': self.cloud_region
+ }
+ ]
+ }
+ logger.debug("Connecting pservers and cloud region")
+ call_req(self.base_url, self.username, self.password,
+ rest_no_auth, resource, "PUT",
+ content=json.dumps(body),
+ headers=self.default_headers)
+
+ def _del_tenants(self, rsp):
+ tenants = rsp.get("tenants", [])
+ if not tenants:
+ return
+ for tenant in tenants["tenant"]:
+ resource = ("/cloud-infrastructure/cloud-regions/cloud-region/"
+ "%s/%s/tenants/tenant/%s?resource-version=%s" % (
+ self.cloud_owner, self.cloud_region,
+ tenant['tenant-id'], tenant['resource-version']))
+ resp = call_req(self.base_url, self.username, self.password,
+ rest_no_auth, resource, "DELETE",
+ headers=self.default_headers)
+ if resp[0] != 0:
+ raise VimDriverAzureException(
+ status_code=400,
+ content="Failed to delete tenant %s: %s." % (
+ tenant['tenant-id'], resp[1]))
+
+ def _del_hpa(self, flavor):
+ hpas = flavor.get("hpa-capabilities", {}).get("hpa-capability", [])
+ for hpa in hpas:
+ resource = (
+ "/cloud-infrastructure/cloud-regions/cloud-region/"
+ "%s/%s/flavors/flavor/%s/hpa-capabilities/hpa-capability/%s"
+ "?resource-version=%s" % (
+ self.cloud_owner, self.cloud_region,
+ flavor['flavor-id'], hpa['hpa-capability-id'],
+ hpa['resource-version']))
+ resp = call_req(self.base_url, self.username, self.password,
+ rest_no_auth, resource, "DELETE",
+ headers=self.default_headers)
+ if resp[0] != 0:
+ raise VimDriverAzureException(
+ status_code=400,
+ content="Failed to delete flavor %s on hpa %s: %s." % (
+ flavor['flavor-id'], hpa['hpa-capability-id'],
+ resp[1]))
+
+ def _del_flavors(self, rsp):
+ flavors = rsp.get("flavors", [])
+ if not flavors:
+ return
+ for flavor in flavors["flavor"]:
+ self._del_hpa(flavor)
+ resource = ("/cloud-infrastructure/cloud-regions/cloud-region/"
+ "%s/%s/flavors/flavor/%s?resource-version=%s" % (
+ self.cloud_owner, self.cloud_region,
+ flavor['flavor-id'], flavor['resource-version']))
+ resp = call_req(self.base_url, self.username, self.password,
+ rest_no_auth, resource, "DELETE",
+ headers=self.default_headers)
+ if resp[0] != 0:
+ raise VimDriverAzureException(
+ status_code=400,
+ content="Failed to delete flavor %s: %s." % (
+ flavor['flavor-id'], resp[1]))
+
+ def _del_images(self, rsp):
+ tenants = rsp.get("images", [])
+ if not tenants:
+ return
+ for tenant in tenants["image"]:
+ resource = ("/cloud-infrastructure/cloud-regions/cloud-region/"
+ "%s/%s/images/image/%s?resource-version=%s" % (
+ self.cloud_owner, self.cloud_region,
+ tenant['image-id'], tenant['resource-version']))
+ resp = call_req(self.base_url, self.username, self.password,
+ rest_no_auth, resource, "DELETE",
+ headers=self.default_headers)
+ if resp[0] != 0:
+ raise VimDriverAzureException(
+ status_code=400,
+ content="Failed to delete image %s: %s." % (
+ tenant['image-id'], resp[1]))
+
+ def _del_networks(self, rsp):
+ networks = rsp.get("oam-networks", [])
+ if not networks:
+ return
+ for network in networks["oam-network"]:
+ resource = ("/cloud-infrastructure/cloud-regions/cloud-region/"
+ "%s/%s/oam-networks/oam-network/%s?"
+ "resource-version=%s" % (
+ self.cloud_owner, self.cloud_region,
+ network['network-uuid'],
+ network['resource-version']))
+ resp = call_req(self.base_url, self.username, self.password,
+ rest_no_auth, resource, "DELETE",
+ headers=self.default_headers)
+ if resp[0] != 0:
+ raise VimDriverAzureException(
+ status_code=400,
+ content="Failed to delete network %s: %s." % (
+ network['network-uuid'], resp[1]))
+
+ def _del_azs(self, rsp):
+ azs = rsp.get("availability-zones", [])
+ if not azs:
+ return
+ for az in azs["availability-zone"]:
+ resource = ("/cloud-infrastructure/cloud-regions/cloud-region/"
+ "%s/%s/availability-zones/availability-zone/%s?"
+ "resource-version=%s" % (
+ self.cloud_owner, self.cloud_region,
+ az['availability-zone-name'],
+ az['resource-version']))
+ resp = call_req(self.base_url, self.username, self.password,
+ rest_no_auth, resource, "DELETE",
+ headers=self.default_headers)
+ if resp[0] != 0:
+ raise VimDriverAzureException(
+ status_code=400,
+ content="Failed to delete availability zone %s: %s." % (
+ az['availability-zone-name'], resp[1]))
+
+ def _get_hpa_capabilities(self, flavor):
+ hpa_caps = []
+
+ # Basic capabilties
+ caps_dict = self._get_hpa_basic_capabilities(flavor)
+ if len(caps_dict) > 0:
+ logger.debug("basic_capabilities_info: %s" % caps_dict)
+ hpa_caps.append(caps_dict)
+
+ # cpupining capabilities
+ caps_dict = self._get_cpupinning_capabilities(flavor['extra_specs'])
+ if len(caps_dict) > 0:
+ logger.debug("cpupining_capabilities_info: %s" % caps_dict)
+ hpa_caps.append(caps_dict)
+
+ # cputopology capabilities
+ caps_dict = self._get_cputopology_capabilities(flavor['extra_specs'])
+ if len(caps_dict) > 0:
+ logger.debug("cputopology_capabilities_info: %s" % caps_dict)
+ hpa_caps.append(caps_dict)
+
+ # hugepages capabilities
+ caps_dict = self._get_hugepages_capabilities(flavor['extra_specs'])
+ if len(caps_dict) > 0:
+ logger.debug("hugepages_capabilities_info: %s" % caps_dict)
+ hpa_caps.append(caps_dict)
+
+ # numa capabilities
+ caps_dict = self._get_numa_capabilities(flavor['extra_specs'])
+ if len(caps_dict) > 0:
+ logger.debug("numa_capabilities_info: %s" % caps_dict)
+ hpa_caps.append(caps_dict)
+
+ # storage capabilities
+ caps_dict = self._get_storage_capabilities(flavor)
+ if len(caps_dict) > 0:
+ logger.debug("storage_capabilities_info: %s" % caps_dict)
+ hpa_caps.append(caps_dict)
+
+ # CPU instruction set extension capabilities
+ caps_dict = self._get_instruction_set_capabilities(
+ flavor['extra_specs'])
+ if len(caps_dict) > 0:
+ logger.debug("instruction_set_capabilities_info: %s" % caps_dict)
+ hpa_caps.append(caps_dict)
+
+ # PCI passthrough capabilities
+ caps_dict = self._get_pci_passthrough_capabilities(
+ flavor['extra_specs'])
+ if len(caps_dict) > 0:
+ logger.debug("pci_passthrough_capabilities_info: %s" % caps_dict)
+ hpa_caps.append(caps_dict)
+
+ # ovsdpdk capabilities
+ caps_dict = self._get_ovsdpdk_capabilities()
+ if len(caps_dict) > 0:
+ logger.debug("ovsdpdk_capabilities_info: %s" % caps_dict)
+ hpa_caps.append(caps_dict)
+
+ return hpa_caps
+
+ def _get_hpa_basic_capabilities(self, flavor):
+ basic_capability = {}
+ feature_uuid = uuid.uuid4()
+
+ basic_capability['hpa-capability-id'] = str(feature_uuid)
+ basic_capability['hpa-feature'] = 'basicCapabilities'
+ basic_capability['architecture'] = 'generic'
+ basic_capability['hpa-version'] = 'v1'
+
+ basic_capability['hpa-feature-attributes'] = []
+ basic_capability['hpa-feature-attributes'].append({
+ 'hpa-attribute-key': 'numVirtualCpu',
+ 'hpa-attribute-value': json.dumps(
+ {'value': str(flavor['vcpus'])})})
+ basic_capability['hpa-feature-attributes'].append({
+ 'hpa-attribute-key': 'virtualMemSize',
+ 'hpa-attribute-value': json.dumps({'value': str(
+ flavor['ram']), 'unit': 'MB'})})
+
+ return basic_capability
+
+ def _get_cpupinning_capabilities(self, extra_specs):
+ cpupining_capability = {}
+ feature_uuid = uuid.uuid4()
+
+ if (extra_specs.get('hw:cpu_policy') or
+ extra_specs.get('hw:cpu_thread_policy')):
+ cpupining_capability['hpa-capability-id'] = str(feature_uuid)
+ cpupining_capability['hpa-feature'] = 'cpuPinning'
+ cpupining_capability['architecture'] = 'generic'
+ cpupining_capability['hpa-version'] = 'v1'
+
+ cpupining_capability['hpa-feature-attributes'] = []
+ if extra_specs.get('hw:cpu_thread_policy'):
+ cpupining_capability['hpa-feature-attributes'].append({
+ 'hpa-attribute-key': 'logicalCpuThreadPinningPolicy',
+ 'hpa-attribute-value': json.dumps({'value': str(
+ extra_specs['hw:cpu_thread_policy'])})})
+ if extra_specs.get('hw:cpu_policy'):
+ cpupining_capability['hpa-feature-attributes'].append({
+ 'hpa-attribute-key': 'logicalCpuPinningPolicy',
+ 'hpa-attribute-value': json.dumps({'value': str(
+ extra_specs['hw:cpu_policy'])})})
+
+ return cpupining_capability
+
+ def _get_cputopology_capabilities(self, extra_specs):
+ cputopology_capability = {}
+ feature_uuid = uuid.uuid4()
+
+ if (extra_specs.get('hw:cpu_sockets') or
+ extra_specs.get('hw:cpu_cores') or
+ extra_specs.get('hw:cpu_threads')):
+ cputopology_capability['hpa-capability-id'] = str(feature_uuid)
+ cputopology_capability['hpa-feature'] = 'cpuTopology'
+ cputopology_capability['architecture'] = 'generic'
+ cputopology_capability['hpa-version'] = 'v1'
+
+ cputopology_capability['hpa-feature-attributes'] = []
+ if extra_specs.get('hw:cpu_sockets'):
+ cputopology_capability['hpa-feature-attributes'].append({
+ 'hpa-attribute-key': 'numCpuSockets',
+ 'hpa-attribute-value': json.dumps({'value': str(
+ extra_specs['hw:cpu_sockets'])})})
+ if extra_specs.get('hw:cpu_cores'):
+ cputopology_capability['hpa-feature-attributes'].append({
+ 'hpa-attribute-key': 'numCpuCores',
+ 'hpa-attribute-value': json.dumps({'value': str(
+ extra_specs['hw:cpu_cores'])})})
+ if extra_specs.get('hw:cpu_threads'):
+ cputopology_capability['hpa-feature-attributes'].append({
+ 'hpa-attribute-key': 'numCpuThreads',
+ 'hpa-attribute-value': json.dumps({'value': str(
+ extra_specs['hw:cpu_threads'])})})
+
+ return cputopology_capability
+
+ def _get_hugepages_capabilities(self, extra_specs):
+ hugepages_capability = {}
+ feature_uuid = uuid.uuid4()
+
+ if extra_specs.get('hw:mem_page_size'):
+ hugepages_capability['hpa-capability-id'] = str(feature_uuid)
+ hugepages_capability['hpa-feature'] = 'hugePages'
+ hugepages_capability['architecture'] = 'generic'
+ hugepages_capability['hpa-version'] = 'v1'
+
+ hugepages_capability['hpa-feature-attributes'] = []
+ if extra_specs['hw:mem_page_size'] == 'large':
+ hugepages_capability['hpa-feature-attributes'].append({
+ 'hpa-attribute-key': 'memoryPageSize',
+ 'hpa-attribute-value': json.dumps(
+ {'value': '2', 'unit': 'MB'})})
+ elif extra_specs['hw:mem_page_size'] == 'small':
+ hugepages_capability['hpa-feature-attributes'].append({
+ 'hpa-attribute-key': 'memoryPageSize',
+ 'hpa-attribute-value': json.dumps(
+ {'value': '4', 'unit': 'KB'})})
+ elif extra_specs['hw:mem_page_size'] == 'any':
+ logger.info("Currently HPA feature memoryPageSize "
+ "did not support 'any' page!!")
+ else:
+ hugepages_capability['hpa-feature-attributes'].append({
+ 'hpa-attribute-key': 'memoryPageSize',
+ 'hpa-attribute-value': json.dumps({'value': str(
+ extra_specs['hw:mem_page_size']), 'unit': 'KB'})
+ })
+
+ return hugepages_capability
+
+ def _get_numa_capabilities(self, extra_specs):
+ numa_capability = {}
+ feature_uuid = uuid.uuid4()
+
+ if extra_specs.get('hw:numa_nodes'):
+ numa_capability['hpa-capability-id'] = str(feature_uuid)
+ numa_capability['hpa-feature'] = 'numa'
+ numa_capability['architecture'] = 'generic'
+ numa_capability['hpa-version'] = 'v1'
+
+ numa_capability['hpa-feature-attributes'] = []
+ numa_capability['hpa-feature-attributes'].append({
+ 'hpa-attribute-key': 'numaNodes',
+ 'hpa-attribute-value': json.dumps({'value': str(
+ extra_specs['hw:numa_nodes'])})
+ })
+
+ for num in range(0, int(extra_specs['hw:numa_nodes'])):
+ numa_cpu_node = "hw:numa_cpus.%s" % num
+ numa_mem_node = "hw:numa_mem.%s" % num
+ numacpu_key = "numaCpu-%s" % num
+ numamem_key = "numaMem-%s" % num
+
+ if (extra_specs.get(numa_cpu_node) and
+ extra_specs.get(numa_mem_node)):
+ numa_capability['hpa-feature-attributes'].append({
+ 'hpa-attribute-key': numacpu_key,
+ 'hpa-attribute-value': json.dumps({'value': str(
+ extra_specs[numa_cpu_node])})
+ })
+ numa_capability['hpa-feature-attributes'].append({
+ 'hpa-attribute-key': numamem_key,
+ 'hpa-attribute-value': json.dumps({'value': str(
+ extra_specs[numa_mem_node]), 'unit': 'MB'})
+ })
+
+ return numa_capability
+
+ def _get_storage_capabilities(self, flavor):
+ storage_capability = {}
+ feature_uuid = uuid.uuid4()
+
+ storage_capability['hpa-capability-id'] = str(feature_uuid)
+ storage_capability['hpa-feature'] = 'localStorage'
+ storage_capability['architecture'] = 'generic'
+ storage_capability['hpa-version'] = 'v1'
+
+ storage_capability['hpa-feature-attributes'] = []
+ storage_capability['hpa-feature-attributes'].append({
+ 'hpa-attribute-key': 'diskSize',
+ 'hpa-attribute-value': json.dumps({'value': str(
+ flavor['disk']), 'unit': 'GB'})
+ })
+ storage_capability['hpa-feature-attributes'].append({
+ 'hpa-attribute-key': 'swapMemSize',
+ 'hpa-attribute-value': json.dumps({'value': str(
+ flavor.get('swap', 0)), 'unit': 'MB'})
+ })
+ storage_capability['hpa-feature-attributes'].append({
+ 'hpa-attribute-key': 'ephemeralDiskSize',
+ 'hpa-attribute-value': json.dumps({'value': str(
+ flavor.get('OS-FLV-EXT-DATA:ephemeral', 0)), 'unit': 'GB'})
+ })
+ return storage_capability
+
+ def _get_instruction_set_capabilities(self, extra_specs):
+ instruction_capability = {}
+ feature_uuid = uuid.uuid4()
+
+ if extra_specs.get('hw:capabilities:cpu_info:features'):
+ instruction_capability['hpa-capability-id'] = str(feature_uuid)
+ instruction_capability['hpa-feature'] = 'instructionSetExtensions'
+ instruction_capability['architecture'] = 'Intel64'
+ instruction_capability['hpa-version'] = 'v1'
+
+ instruction_capability['hpa-feature-attributes'] = []
+ instruction_capability['hpa-feature-attributes'].append({
+ 'hpa-attribute-key': 'instructionSetExtensions',
+ 'hpa-attribute-value': json.dumps(
+ {'value': extra_specs[
+ 'hw:capabilities:cpu_info:features']})
+ })
+ return instruction_capability
+
+ def _get_pci_passthrough_capabilities(self, extra_specs):
+ instruction_capability = {}
+ feature_uuid = uuid.uuid4()
+
+ if extra_specs.get('pci_passthrough:alias'):
+ value1 = extra_specs['pci_passthrough:alias'].split(':')
+ value2 = value1[0].split('-')
+
+ instruction_capability['hpa-capability-id'] = str(feature_uuid)
+ instruction_capability['hpa-feature'] = 'pciePassthrough'
+ instruction_capability['architecture'] = str(value2[2])
+ instruction_capability['hpa-version'] = 'v1'
+
+ instruction_capability['hpa-feature-attributes'] = []
+ instruction_capability['hpa-feature-attributes'].append({
+ 'hpa-attribute-key': 'pciCount',
+ 'hpa-attribute-value': json.dumps({'value': value1[1]})
+ })
+ instruction_capability['hpa-feature-attributes'].append({
+ 'hpa-attribute-key': 'pciVendorId',
+ 'hpa-attribute-value': json.dumps({'value': value2[3]})
+ })
+ instruction_capability['hpa-feature-attributes'].append({
+ 'hpa-attribute-key': 'pciDeviceId',
+ 'hpa-attribute-value': json.dumps({'value': value2[4]})
+ })
+
+ return instruction_capability
+
+ def _get_ovsdpdk_capabilities(self):
+ ovsdpdk_capability = {}
+ feature_uuid = uuid.uuid4()
+
+ if not self._vim_info:
+ self._vim_info = self.get_vim(get_all=True)
+ cloud_extra_info_str = self._vim_info.get('cloud-extra-info')
+ if not isinstance(cloud_extra_info_str, dict):
+ try:
+ cloud_extra_info_str = json.loads(cloud_extra_info_str)
+ except Exception as ex:
+ logger.error("Can not convert cloud extra info %s %s" % (
+ str(ex), cloud_extra_info_str))
+ return {}
+ if cloud_extra_info_str:
+ cloud_dpdk_info = cloud_extra_info_str.get("ovsDpdk")
+ if cloud_dpdk_info:
+ ovsdpdk_capability['hpa-capability-id'] = str(feature_uuid)
+ ovsdpdk_capability['hpa-feature'] = 'ovsDpdk'
+ ovsdpdk_capability['architecture'] = 'Intel64'
+ ovsdpdk_capability['hpa-version'] = 'v1'
+
+ ovsdpdk_capability['hpa-feature-attributes'] = []
+ ovsdpdk_capability['hpa-feature-attributes'].append({
+ 'hpa-attribute-key': str(cloud_dpdk_info.get("libname")),
+ 'hpa-attribute-value': json.dumps(
+ {'value': cloud_dpdk_info.get("libversion")})
+ })
+ return ovsdpdk_capability
diff --git a/azure/azure/pub/utils/syscomm.py b/azure/azure/pub/utils/syscomm.py
new file mode 100644
index 0000000..f838956
--- /dev/null
+++ b/azure/azure/pub/utils/syscomm.py
@@ -0,0 +1,111 @@
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+
+import inspect
+import json
+from collections import defaultdict
+from rest_framework import status
+
+
+keystoneV2Json = \
+ {
+ "auth": {
+ "tenantName": "",
+ "passwordCredentials": {
+ "username": "",
+ "password": ""
+ }
+ }
+ }
+
+
+SUCCESS_STATE = [status.HTTP_200_OK, status.HTTP_201_CREATED,
+ status.HTTP_202_ACCEPTED]
+
+
+def fun_name():
+ return inspect.stack()[1][3]
+
+
+def jsonResponse(data, encoding='utf-8'):
+
+ content_type = "application/json"
+ try:
+ res = json.loads(data, encoding=encoding)
+ except Exception:
+ res = data
+ content_type = "text/plain"
+ return (res, content_type)
+
+
+class Catalogs(object):
+
+ def __init__(self):
+ self.ct = defaultdict(dict)
+
+ def storeEndpoint(self, vimid, endpoints):
+ if vimid in self.ct:
+ self.ct[vimid].update(endpoints)
+ else:
+ self.ct.setdefault(vimid, endpoints)
+
+ def getEndpointBy(self, vimid, serverType, interface='public'):
+
+ vim = self.ct.get(vimid)
+ return vim.get(serverType).get(interface, "") if vim else ""
+
+
+def verifyKeystoneV2(param):
+
+ return _walk_json(param, keystoneV2Json)
+
+
+# comapare two json by key
+def _walk_json(data, data2):
+ if isinstance(data, dict) and isinstance(data2, dict):
+ if set(data.keys()) != set(data2.keys()):
+ return False
+ else:
+ v1 = data.values()
+ v2 = data2.values()
+ v1.sort()
+ v2.sort()
+ if len(v1) != len(v2):
+ return False
+ for (i, j) in zip(v1, v2):
+ # continue compare key
+ if isinstance(i, dict) and isinstance(j, dict):
+ if not _walk_json(i, j):
+ return False
+ # ignore value
+ else:
+ continue
+
+ return True
+
+ return False
+
+
+def keystoneVersion(url, version="v3"):
+
+ tmp = url.split("/")
+ v = tmp[-1]
+ if v not in ["v2.0", "v3"]:
+ url += "/" + version
+ else:
+ tmp[-1] = version
+ url = "/".join(tmp)
+
+ return url
+
+
+catalog = Catalogs()
diff --git a/azure/azure/pub/utils/timeutil.py b/azure/azure/pub/utils/timeutil.py
new file mode 100644
index 0000000..d5ef329
--- /dev/null
+++ b/azure/azure/pub/utils/timeutil.py
@@ -0,0 +1,17 @@
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+
+import datetime
+
+
+def now_time(fmt="%Y-%m-%d %H:%M:%S"):
+ return datetime.datetime.now().strftime(fmt)
diff --git a/azure/azure/pub/utils/values.py b/azure/azure/pub/utils/values.py
new file mode 100644
index 0000000..61d7114
--- /dev/null
+++ b/azure/azure/pub/utils/values.py
@@ -0,0 +1,22 @@
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+
+
+def ignore_case_get(args, key, def_val=""):
+ if not key:
+ return def_val
+ if key in args:
+ return args[key]
+ for old_key in args:
+ if old_key.upper() == key.upper():
+ return args[old_key]
+ return def_val
diff --git a/azure/azure/pub/vim/__init__.py b/azure/azure/pub/vim/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/azure/azure/pub/vim/__init__.py
diff --git a/azure/azure/pub/vim/const.py b/azure/azure/pub/vim/const.py
new file mode 100644
index 0000000..dc5a3a4
--- /dev/null
+++ b/azure/azure/pub/vim/const.py
@@ -0,0 +1,14 @@
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+
+
+SAMPLE_KEY = "sample_value"
diff --git a/azure/azure/samples/__init__.py b/azure/azure/samples/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/azure/azure/samples/__init__.py
diff --git a/azure/azure/samples/tests.py b/azure/azure/samples/tests.py
new file mode 100644
index 0000000..e801e48
--- /dev/null
+++ b/azure/azure/samples/tests.py
@@ -0,0 +1,31 @@
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+
+import unittest
+import json
+from django.test import Client
+from rest_framework import status
+
+
+class SampleViewTest(unittest.TestCase):
+ def setUp(self):
+ self.client = Client()
+
+ def tearDown(self):
+ pass
+
+ def test_sample(self):
+ response = self.client.get("/samples/")
+ self.assertEqual(status.HTTP_200_OK,
+ response.status_code, response.content)
+ resp_data = json.loads(response.content)
+ self.assertEqual("active", resp_data["status"])
diff --git a/azure/azure/samples/urls.py b/azure/azure/samples/urls.py
new file mode 100644
index 0000000..39da376
--- /dev/null
+++ b/azure/azure/samples/urls.py
@@ -0,0 +1,17 @@
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+
+from django.conf.urls import url
+from azure.samples import views
+
+urlpatterns = [
+ url(r'^samples/$', views.SampleList.as_view()), ]
diff --git a/azure/azure/samples/views.py b/azure/azure/samples/views.py
new file mode 100644
index 0000000..6576450
--- /dev/null
+++ b/azure/azure/samples/views.py
@@ -0,0 +1,35 @@
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+
+import os
+import logging
+
+from rest_framework.views import APIView
+from rest_framework.response import Response
+
+logger = logging.getLogger(__name__)
+log_file = "/var/log/onap/multicloud/azure/azure.log"
+
+
+class SampleList(APIView):
+ """
+ List all samples.
+ """
+
+ def get(self, request, format=None):
+ logger.debug("get")
+ output = ""
+ if os.path.exists(log_file):
+ with open("/var/log/onap/multicloud/azure/azure.log", "r") as f:
+ lines = f.readlines()
+ output = lines[-1]
+ return Response({"status": "active", "logs": output})
diff --git a/azure/azure/scripts/__init__.py b/azure/azure/scripts/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/azure/azure/scripts/__init__.py
diff --git a/azure/azure/scripts/api.py b/azure/azure/scripts/api.py
new file mode 100644
index 0000000..792fd5d
--- /dev/null
+++ b/azure/azure/scripts/api.py
@@ -0,0 +1,41 @@
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+import eventlet
+eventlet.monkey_patch()
+
+import os # noqa
+from oslo_config import cfg # noqa
+from oslo_service import service # noqa
+import sys # noqa
+# FIXME: Since there is no explicitly setup process for the project. Hack the
+# python here.
+sys.path.append(os.path.abspath('.'))
+
+from azure.api_v2 import service as api_service # noqa
+
+
+def main():
+ try:
+ api_server = api_service.WSGIService()
+ launcher = service.launch(cfg.CONF,
+ api_server,
+ workers=api_server.workers)
+ launcher.wait()
+ except RuntimeError as excp:
+ sys.stderr.write("ERROR: %s\n" % excp)
+ sys.exit(1)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/azure/azure/settings-cover.py b/azure/azure/settings-cover.py
new file mode 100644
index 0000000..b4fecdd
--- /dev/null
+++ b/azure/azure/settings-cover.py
@@ -0,0 +1,22 @@
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+
+from azure.settings import * # noqa
+from azure.settings import INSTALLED_APPS
+
+INSTALLED_APPS.append('django_nose')
+
+TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
+
+NOSE_ARGS = [
+ '--with-coverage',
+ '--cover-package=azure',
+]
diff --git a/azure/azure/settings.py b/azure/azure/settings.py
new file mode 100644
index 0000000..ace6e8f
--- /dev/null
+++ b/azure/azure/settings.py
@@ -0,0 +1,98 @@
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+
+import os
+import sys
+from logging import config
+from onaplogging import monkey
+monkey.patch_all()
+
+# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = '3o-wney!99y)^h3v)0$j16l9=fdjxcb+a8g+q3tfbahcnu2b0o'
+
+# SECURITY WARNING: don't run with debug turned on in production!
+# DEBUG = True
+
+ALLOWED_HOSTS = ['*']
+
+# Application definition
+
+INSTALLED_APPS = [
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.messages',
+ 'django.contrib.staticfiles',
+ 'rest_framework',
+ 'azure.pub.database',
+]
+
+MIDDLEWARE_CLASSES = [
+ 'django.middleware.security.SecurityMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.common.CommonMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+ 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+ 'azure.middleware.LogContextMiddleware',
+]
+
+ROOT_URLCONF = 'azure.urls'
+
+WSGI_APPLICATION = 'azure.wsgi.application'
+
+REST_FRAMEWORK = {
+ 'DEFAULT_RENDERER_CLASSES': (
+ 'rest_framework.renderers.JSONRenderer',
+ ),
+
+ 'DEFAULT_PARSER_CLASSES': (
+ 'rest_framework.parsers.JSONParser',
+ 'rest_framework.parsers.MultiPartParser',
+ # 'rest_framework.parsers.FormParser',
+ # 'rest_framework.parsers.FileUploadParser',
+ )
+}
+
+
+TIME_ZONE = 'UTC'
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/1.6/howto/static-files/
+
+STATIC_URL = '/static/'
+
+
+LOGGING_CONFIG = None
+# yaml configuration of logging
+LOGGING_FILE = os.path.join(BASE_DIR, 'azure/pub/config/log.yml')
+config.yamlConfig(filepath=LOGGING_FILE, watchDog=True)
+
+
+if 'test' in sys.argv:
+ from azure.pub.config import config
+ REST_FRAMEWORK = {}
+ import platform
+
+ if platform.system() == 'Linux':
+ TEST_RUNNER = 'xmlrunner.extra.djangotestrunner.XMLTestRunner'
+ TEST_OUTPUT_VERBOSE = True
+ TEST_OUTPUT_DESCRIPTIONS = True
+ TEST_OUTPUT_DIR = 'test-reports'
diff --git a/azure/azure/swagger/__init__.py b/azure/azure/swagger/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/azure/azure/swagger/__init__.py
diff --git a/azure/azure/swagger/image_utils.py b/azure/azure/swagger/image_utils.py
new file mode 100644
index 0000000..a17565e
--- /dev/null
+++ b/azure/azure/swagger/image_utils.py
@@ -0,0 +1,65 @@
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+
+
+def image_formatter(image):
+
+ image = image.to_dict()
+ properties = {}
+ if image.get("vmware_adaptertype"):
+ properties['vmware_adaptertype'] = image.get("vmware_adaptertype")
+ if image.get("vmware_ostype"):
+ properties['vmware_ostype'] = image.get("vmware_ostype")
+
+ return {
+ 'id': image.get("id"),
+ 'name': image.get("name"),
+ 'imageType': image.get("disk_format"),
+ 'status': image.get("status"),
+ 'size': image.get("size"),
+ 'containerFormat': image.get("container_format"),
+ 'visibility': image.get("visibility"),
+ 'properties': properties
+ }
+
+
+def vim_formatter(vim_info, tenantid):
+
+ rsp = {}
+ rsp['vimId'] = vim_info.get('vimId')
+ rsp['vimName'] = vim_info.get('name')
+ rsp['tenantId'] = tenantid
+ return rsp
+
+
+def sdk_param_formatter(data):
+
+ param = {}
+ param['username'] = data.get('userName')
+ param['password'] = data.get('password')
+ param['auth_url'] = data.get('url')
+ param['project_id'] = data.get('tenant')
+ param['user_domain_name'] = 'default'
+ param['project_domain_name'] = 'default'
+ return param
+
+
+def req_body_formatter(body):
+
+ param = {}
+ param['name'] = body.get('name')
+ param['disk_format'] = body.get('imageType')
+ param['container_format'] = body.get('containerFormat')
+ param['visibility'] = body.get('visibility')
+ properties = body.get('properties', {})
+ param.update(properties)
+ return param
diff --git a/azure/azure/swagger/nova_utils.py b/azure/azure/swagger/nova_utils.py
new file mode 100644
index 0000000..86f10a2
--- /dev/null
+++ b/azure/azure/swagger/nova_utils.py
@@ -0,0 +1,119 @@
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+
+import six
+
+
+def server_formatter(server, interfaces=[]):
+ r = {
+ "id": server.id,
+ "name": server.name,
+ "tenantId": server.project_id,
+ "availabilityZone": server.availability_zone,
+ "flavorId": server.flavor_id or server.flavor['id'],
+ "volumeArray": [],
+ "metadata": [],
+ "securityGroups": [],
+ # TODO finish following attributes
+ "serverGroup": "",
+ "contextArray": [],
+ "userdata": server.user_data,
+ "nicArray": [],
+ "status": server.status
+ }
+ if interfaces:
+ r['nicArray'] = [{'portId': i.port_id} for i in interfaces]
+ elif server.networks:
+ r['nicArray'] = [{'portId': n['port']} for n in server.networks]
+ # TODO: wait sdk fix block_device_mapping
+ try:
+ if server.attached_volumes:
+ r["volumeArray"] = [{'volumeId': v['id']}
+ for v in server.attached_volumes]
+ elif server.block_device_mapping:
+ r["volumeArray"] = [{'volumeId': v['uuid']}
+ for v in server.block_device_mapping]
+ except ValueError:
+ r['volumeArray'] = [{'volumeId': ""}]
+ if server.image_id or server.image:
+ r['boot'] = {
+ 'type': 2,
+ 'imageId': server.image_id or server.image['id']
+ }
+ else:
+ r['boot'] = {
+ 'type': 1,
+ 'volumeId': r['volumeArray'][0]['volumeId']
+ }
+ if server.metadata:
+ r["metadata"] = [{'keyName': k, 'value': v}
+ for k, v in six.iteritems(server.metadata)]
+ if server.security_groups:
+ r["securityGroups"] = [i['name'] for i in server.security_groups]
+ return r
+
+
+def flavor_formatter(flavor, extra_specs):
+ r = {
+ "id": flavor.id,
+ "name": flavor.name,
+ "vcpu": flavor.vcpus,
+ "memory": flavor.ram,
+ "disk": flavor.disk,
+ "ephemeral": flavor.ephemeral,
+ "swap": flavor.swap,
+ "isPublic": flavor.is_public}
+ if extra_specs:
+ r["extraSpecs"] = extra_specs_formatter(extra_specs)
+ return r
+
+
+def extra_specs_formatter(extra_specs):
+ return [{"keyName": k, "value": v}
+ for k, v in six.iteritems(extra_specs.extra_specs)]
+
+
+def server_limits_formatter(limits):
+ return {
+ # nova
+ 'maxPersonality': limits.absolute.personality,
+ 'maxPersonalitySize': limits.absolute.personality_size,
+ 'maxServerGroupMembers': limits.absolute.server_group_members,
+ 'maxServerGroups': limits.absolute.server_groups,
+ 'maxImageMeta': limits.absolute.image_meta,
+ 'maxTotalCores': limits.absolute.total_cores,
+ 'maxTotalInstances': limits.absolute.instances,
+ 'maxTotalKeypairs': limits.absolute.keypairs,
+ 'maxTotalRAMSize': limits.absolute.total_ram,
+ 'security_group_rules': limits.absolute.security_group_rules,
+ 'security_group': limits.absolute.security_groups,
+
+ # cinder
+ # neutron
+ }
+
+
+def service_formatter(service):
+ return {
+ 'service': service.binary,
+ 'name': service.host,
+ 'zone': service.zone,
+ }
+
+
+def hypervisor_formatter(hypervisor):
+ return {
+ 'name': hypervisor.name,
+ 'cpu': hypervisor.vcpus,
+ 'disk_gb': hypervisor.local_disk_size,
+ 'memory_mb': hypervisor.memory_size,
+ }
diff --git a/azure/azure/swagger/tests.py b/azure/azure/swagger/tests.py
new file mode 100644
index 0000000..4631455
--- /dev/null
+++ b/azure/azure/swagger/tests.py
@@ -0,0 +1,31 @@
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+
+import unittest
+# import json
+from django.test import Client
+from rest_framework import status
+
+
+class SampleViewTest(unittest.TestCase):
+ def setUp(self):
+ self.client = Client()
+
+ def tearDown(self):
+ pass
+
+ def test_sample(self):
+ response = self.client.get("/api/multicloud-azure/v0/swagger.json")
+ self.assertEqual(status.HTTP_200_OK,
+ response.status_code, response.content)
+# resp_data = json.loads(response.content)
+# self.assertEqual({"status": "active"}, resp_data)
diff --git a/azure/azure/swagger/urls.py b/azure/azure/swagger/urls.py
new file mode 100644
index 0000000..f80fa26
--- /dev/null
+++ b/azure/azure/swagger/urls.py
@@ -0,0 +1,37 @@
+# Copyright (c) 2018 Amdocs
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+
+from django.conf.urls import url
+from rest_framework.urlpatterns import format_suffix_patterns
+
+from azure.swagger.views.swagger_json import SwaggerJsonView
+
+
+# Registry
+from azure.swagger.views.registry.views import Registry
+from azure.swagger.views.registry.views import UnRegistry
+
+
+urlpatterns = [
+ # swagger
+ url(r'^api/multicloud-azure/v0/swagger.json$', SwaggerJsonView.as_view()),
+
+ # Registry
+ url(r'^api/multicloud-azure/v0/(?P<vimid>[0-9a-z-A-Z\-\_]+)/registry$',
+ Registry.as_view()),
+ url(r'^api/multicloud-azure/v0/(?P<vimid>[0-9a-z-A-Z\-\_]+)$',
+ UnRegistry.as_view()),
+
+]
+
+urlpatterns = format_suffix_patterns(urlpatterns)
diff --git a/azure/azure/swagger/utils.py b/azure/azure/swagger/utils.py
new file mode 100644
index 0000000..cb7e4f0
--- /dev/null
+++ b/azure/azure/swagger/utils.py
@@ -0,0 +1,37 @@
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import json
+import os
+
+
+def get_swagger_json_data():
+ json_file = os.path.join(os.path.dirname(
+ __file__), 'multivim.flavor.swagger.json')
+ f = open(json_file)
+ json_data = json.JSONDecoder().decode(f.read())
+ f.close()
+ # json_file = os.path.join(os.path.dirname(
+ # __file__), 'multivim.image.swagger.json')
+ # f = open(json_file)
+ # json_data_temp = json.JSONDecoder().decode(f.read())
+ # f.close()
+ # json_data["paths"].update(json_data_temp["paths"])
+ # json_data["definitions"].update(json_data_temp["definitions"])
+
+ json_data["basePath"] = "/api/multicloud-azure/v0/"
+ json_data["info"]["title"] = "MultiVIM driver \
+ of Microsoft Azure Service NBI"
+
+ return json_data
diff --git a/azure/azure/swagger/views.py b/azure/azure/swagger/views.py
new file mode 100644
index 0000000..f0b7028
--- /dev/null
+++ b/azure/azure/swagger/views.py
@@ -0,0 +1,29 @@
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+
+import logging
+# import traceback
+
+# from rest_framework import status
+from rest_framework.response import Response
+from rest_framework.views import APIView
+
+# from azure.pub.exceptions import VimDriverAzureException
+from azure.swagger import utils
+
+logger = logging.getLogger(__name__)
+
+
+class SwaggerJsonView(APIView):
+ def get(self, request):
+
+ return Response(utils.get_swagger_json_data())
diff --git a/azure/azure/swagger/views/__init__.py b/azure/azure/swagger/views/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/azure/azure/swagger/views/__init__.py
diff --git a/azure/azure/swagger/views/multivim.swagger.json b/azure/azure/swagger/views/multivim.swagger.json
new file mode 100644
index 0000000..de3419f
--- /dev/null
+++ b/azure/azure/swagger/views/multivim.swagger.json
@@ -0,0 +1,51 @@
+{
+ "swagger": "2.0",
+ "info": {
+ "version": "1.0.0",
+ "title": "MultiVIM Service rest API"
+ },
+ "basePath": "/api/multicloud-azure/v0/",
+ "tags": [
+ {
+ "name": "MultiVIM Azure services"
+ }
+ ],
+ "paths": {
+ "/{vimid}/registry": {
+ "post": {
+ "tags": [
+ "vim registration"
+ ],
+ "summary": "vim registration API",
+ "description": "vim registration API",
+ "operationId": "vim_registration",
+ "consumes": [
+ "application/json"
+ ],
+ "produces": [
+ "application/json"
+ ],
+ "parameters": [
+ {
+ "name": "vimid",
+ "in": "path",
+ "description": "vim instance id",
+ "required": true,
+ "type": "string"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "successful operation"
+ },
+ "404": {
+ "description": "the vim id is wrong"
+ },
+ "500": {
+ "description": "error occured during the process"
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/azure/azure/swagger/views/registry/__init__.py b/azure/azure/swagger/views/registry/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/azure/azure/swagger/views/registry/__init__.py
diff --git a/azure/azure/swagger/views/registry/views.py b/azure/azure/swagger/views/registry/views.py
new file mode 100644
index 0000000..b5db805
--- /dev/null
+++ b/azure/azure/swagger/views/registry/views.py
@@ -0,0 +1,167 @@
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+
+
+import logging
+
+from rest_framework import status
+from rest_framework.response import Response
+from rest_framework.views import APIView
+
+from azure.pub.exceptions import VimDriverAzureException
+from azure.pub.msapi import extsys
+from azure.pub.utils.restcall import AAIClient
+
+
+logger = logging.getLogger(__name__)
+
+
+class Registry(APIView):
+ # def _get_tenants(self, auth_info):
+ # tenant_instance = OperateTenant.OperateTenant()
+ # try:
+ # projects = tenant_instance.get_projects(auth_info)
+ # except Exception as e:
+ # logger.exception("get tenants error %(e)s", {"e": e})
+ # raise e
+ #
+ # rsp = {"tenants": []}
+ # for project in projects:
+ # rsp['tenants'].append(project.to_dict())
+ # return rsp
+ #
+ # def _get_images(self, auth_info):
+ # image_instance = OperateImage.OperateImage(auth_info)
+ # try:
+ # images = image_instance.get_vim_images()
+ # except Exception as e:
+ # logger.exception("get images error %(e)s", {"e": e})
+ # raise e
+ #
+ # rsp = {"images": []}
+ # for image in images:
+ # rsp['images'].append(image.to_dict())
+ # return rsp
+ #
+ # def _get_flavors(self, auth_info):
+ # flavors_op = OperateFlavors.OperateFlavors()
+ # try:
+ # flavors = flavors_op.list_flavors(
+ # auth_info, auth_info['tenant'])
+ # except Exception as e:
+ # logger.exception("get flavors error %(e)s", {"e": e})
+ # raise e
+ #
+ # rsp = {"flavors": []}
+ # for flavor in flavors:
+ # flavor_info = flavor[0].to_dict()
+ # flavor_info['extra_specs'] = flavor[1].extra_specs
+ # rsp['flavors'].append(flavor_info)
+ # return rsp
+ #
+ # def _get_networks(self, auth_info):
+ # net_op = OperateNetwork.OperateNetwork()
+ # try:
+ # resp = net_op.list_networks(
+ # auth_info['vimId'], auth_info['tenant'])
+ # except Exception as e:
+ # logger.exception("get networks error %(e)s", {"e": e})
+ # raise e
+ #
+ # rsp = {'networks': resp['networks']}
+ # return rsp
+ #
+ # def _get_hypervisors(self, auth_info):
+ # hypervisor_op = OperateHypervisor.OperateHypervisor()
+ # try:
+ # hypervisors = hypervisor_op.list_hypervisors(auth_info)
+ # except Exception as e:
+ # logger.exception("get hypervisors error %(e)s", {"e": e})
+ # raise e
+ #
+ # rsp = {"hypervisors": []}
+ # for hypervisor in hypervisors:
+ # rsp['hypervisors'].append(hypervisor.to_dict())
+ # return rsp
+
+ def _find_tenant_id(self, name, tenants):
+ for tenant in tenants['tenants']:
+ if tenant['name'] == name:
+ return tenant['id']
+
+ def post(self, request, vimid):
+ try:
+ vim_info = extsys.get_vim_by_id(vimid)
+ except VimDriverAzureException as e:
+ return Response(data={'error': str(e)}, status=e.status_code)
+ data = {}
+ data['vimId'] = vim_info['vimId']
+ data['username'] = vim_info['userName']
+ data['userName'] = vim_info['userName']
+ data['password'] = vim_info['password']
+ data['url'] = vim_info['url']
+ data['project_name'] = vim_info['tenant']
+
+ rsp = {}
+ # get tenants
+ try:
+ logger.debug('Getting tenants')
+ tenants = self._get_tenants(data)
+ rsp.update(tenants)
+ data['tenant'] = self._find_tenant_id(
+ data['project_name'], tenants)
+ data['project_id'] = data['tenant']
+ # set default tenant
+ # get images
+ logger.debug('Getting images')
+ images = self._get_images(data)
+ rsp.update(images)
+ # get flavors
+ logger.debug('Getting flavors')
+ flavors = self._get_flavors(data)
+ rsp.update(flavors)
+ # get networks
+ logger.debug('Getting networks')
+ networks = self._get_networks(data)
+ rsp.update(networks)
+ # get hypervisors
+ logger.debug('Getting hypervisors')
+ hypervisors = self._get_hypervisors(data)
+ rsp.update(hypervisors)
+ # update A&AI
+ logger.debug('Put data into A&AI')
+ cloud_owner, cloud_region = extsys.split_vim_to_owner_region(
+ vimid)
+ aai_adapter = AAIClient(cloud_owner, cloud_region)
+ aai_adapter.update_vim(rsp)
+ except Exception as e:
+ if hasattr(e, "http_status"):
+ return Response(data={'error': str(e)}, status=e.http_status)
+ else:
+ return Response(data={'error': str(e)},
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+ return Response(data="", status=status.HTTP_200_OK)
+
+
+class UnRegistry(APIView):
+
+ def delete(self, request, vimid):
+ try:
+ cloud_owner, cloud_region = extsys.split_vim_to_owner_region(
+ vimid)
+ aai_adapter = AAIClient(cloud_owner, cloud_region)
+ aai_adapter.delete_vim()
+ except Exception as e:
+ return Response(data=e.message,
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+ return Response(data="", status=status.HTTP_204_NO_CONTENT)
diff --git a/azure/azure/swagger/views/swagger_json.py b/azure/azure/swagger/views/swagger_json.py
new file mode 100644
index 0000000..91c00dd
--- /dev/null
+++ b/azure/azure/swagger/views/swagger_json.py
@@ -0,0 +1,42 @@
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+
+
+import json
+import logging
+import os
+
+from rest_framework.response import Response
+from rest_framework.views import APIView
+
+
+logger = logging.getLogger(__name__)
+
+
+class SwaggerJsonView(APIView):
+ def get(self, request):
+ json_file = os.path.join(os.path.dirname(
+ __file__), 'multivim.swagger.json')
+ f = open(json_file)
+ json_data = json.JSONDecoder().decode(f.read())
+ f.close()
+ # json_file = os.path.join(os.path.dirname(
+ # __file__), 'multivim.image.swagger.json')
+ # f = open(json_file)
+ # json_data_temp = json.JSONDecoder().decode(f.read())
+ # f.close()
+ # json_data["paths"].update(json_data_temp["paths"])
+ # json_data["definitions"].update(json_data_temp["definitions"])
+ json_data["basePath"] = "/api/multicloud-azure/v0/"
+ json_data["info"]["title"] = "MultiVIM \
+ driver of Microsoft Azure Service NBI"
+ return Response(json_data)
diff --git a/azure/azure/swagger/volume_utils.py b/azure/azure/swagger/volume_utils.py
new file mode 100644
index 0000000..ae11285
--- /dev/null
+++ b/azure/azure/swagger/volume_utils.py
@@ -0,0 +1,72 @@
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+
+
+def volume_formatter(volume):
+
+ attachments = []
+ for attach in volume.attachments:
+ vim_attach = {
+ 'device': attach['device'],
+ 'volumeId': attach['volume_id'],
+ 'hostName': attach['host_name'],
+ 'Id': attach['attachment_id'],
+ 'serverId': attach['server_id']
+ }
+ attachments.append(vim_attach)
+
+ return {
+ 'id': volume.id,
+ 'name': volume.name,
+ 'createTime': volume.created_at,
+ 'status': volume.status,
+ 'type': volume.volume_type,
+ 'size': volume.size,
+ 'availabilityZone': volume.availability_zone,
+ 'attachments': attachments
+ }
+
+
+def vim_formatter(vim_info, tenantid):
+
+ rsp = {}
+ rsp['vimId'] = vim_info.get('vimId')
+ rsp['vimName'] = vim_info.get('name')
+ rsp['tenantId'] = tenantid
+ return rsp
+
+
+def sdk_param_formatter(data):
+
+ param = {}
+ param['username'] = data.get('userName')
+ param['password'] = data.get('password')
+ param['auth_url'] = data.get('url')
+ param['project_id'] = data.get('tenant')
+ param['user_domain_name'] = 'default'
+ param['project_domain_name'] = 'default'
+ return param
+
+
+def req_body_formatter(body):
+
+ param = {}
+ param['name'] = body.get('name')
+ param['size'] = body.get('volumeSize')
+
+ if body.get('volumeType'):
+ param['volume_type'] = body.get('volumeType')
+ if body.get('availabilityZone'):
+ param['availability_zone'] = body.get('availabilityZone')
+ if body.get('imageId'):
+ param['image_id'] = body.get('imageId')
+ return param
diff --git a/azure/azure/tests/__init__.py b/azure/azure/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/azure/azure/tests/__init__.py
diff --git a/azure/azure/tests/test_aai_client.py b/azure/azure/tests/test_aai_client.py
new file mode 100644
index 0000000..31ff37d
--- /dev/null
+++ b/azure/azure/tests/test_aai_client.py
@@ -0,0 +1,378 @@
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+
+import json
+import mock
+import unittest
+
+from azure.pub.exceptions import VimDriverAzureException
+from azure.pub.utils import restcall
+
+
+class TestAAIClient(unittest.TestCase):
+
+ def setUp(self):
+ self.view = restcall.AAIClient("vmware", "4.0")
+
+ @mock.patch.object(restcall, "call_req")
+ def test_get_vim(self, mock_call):
+ mock_call.return_value = [0, '{"cloudOwner": "vmware"}']
+ ret = self.view.get_vim(get_all=True)
+ expect_ret = {"cloudOwner": "vmware"}
+ self.assertEqual(expect_ret, ret)
+
+ @mock.patch.object(restcall.AAIClient, "get_vim")
+ @mock.patch.object(restcall, "call_req")
+ def test_update_identity_url(self, mock_call, mock_getvim):
+ mock_getvim.return_value = {}
+ self.view.update_identity_url()
+ mock_call.assert_called_once()
+
+ @mock.patch.object(restcall, "call_req")
+ def test_add_tenants(self, mock_call):
+ tenants = {"tenants": [{"name": "admin", "id": "admin-id"}]}
+ self.view.add_tenants(tenants)
+ mock_call.assert_called_once()
+
+ @mock.patch.object(restcall, "call_req")
+ def test_add_flavors(self, mock_call):
+ flavors = {
+ "flavors": [{
+ "name": "m1.small",
+ "id": "1",
+ "vcpus": 1,
+ "ram": 512,
+ "disk": 10,
+ "ephemeral": 0,
+ "swap": 0,
+ "is_public": True,
+ "links": [{"href": "http://fake-url"}],
+ "is_disabled": False
+ }]
+ }
+ self.view.add_flavors(flavors)
+ mock_call.assert_called_once()
+
+ @mock.patch.object(restcall, "call_req")
+ def test_add_flavors_with_hpa(self, mock_call):
+ flavors = {
+ "flavors": [{
+ "name": "onap.small",
+ "id": "1",
+ "vcpus": 1,
+ "ram": 512,
+ "disk": 10,
+ "ephemeral": 0,
+ "swap": 0,
+ "is_public": True,
+ "links": [{"href": "http://fake-url"}],
+ "is_disabled": False,
+ "extra_specs": {},
+ }]
+ }
+ self.view._get_ovsdpdk_capabilities = mock.MagicMock()
+ self.view._get_ovsdpdk_capabilities.return_value = {}
+ self.view.add_flavors(flavors)
+ mock_call.assert_called_once()
+
+ @mock.patch.object(restcall, "call_req")
+ def test_add_images(self, mock_call):
+ images = {
+ "images": [{
+ "name": "ubuntu-16.04",
+ "id": "image-id"
+ }]
+ }
+ self.view.add_images(images)
+ mock_call.assert_called_once()
+
+ @mock.patch.object(restcall, "call_req")
+ def test_add_networks(self, mock_call):
+ networks = {
+ "networks": [{
+ "name": "net-1",
+ "id": "net-id",
+ "segmentationId": 144
+ }]
+ }
+ self.view.add_networks(networks)
+ mock_call.assert_called_once()
+
+ @mock.patch.object(restcall, "call_req")
+ def test_add_pservers(self, mock_call):
+ pservers = {
+ "hypervisors": [{
+ "name": "compute-1",
+ "vcpus": 100,
+ "local_disk_size": 1000,
+ "memory_size": 10240,
+ "host_ip": "10.0.0.7",
+ "id": "compute-1-id"
+ }]
+ }
+ self.view.add_pservers(pservers)
+ self.assertEqual(mock_call.call_count, 2)
+
+ @mock.patch.object(restcall, "call_req")
+ def test_del_tenants(self, mock_call):
+ mock_call.return_value = [0]
+ rsp = {
+ "tenants": {
+ "tenant": [{
+ "tenant-id": "tenant-id",
+ "resource-version": "version-1"
+ }]
+ }
+ }
+ self.view._del_tenants(rsp)
+ mock_call.assert_called_once()
+
+ @mock.patch.object(restcall, "call_req")
+ def test_del_flavors(self, mock_call):
+ mock_call.return_value = [0]
+ rsp = {
+ "flavors": {
+ "flavor": [{
+ "flavor-id": "fake-id",
+ "resource-version": "fake-version"
+ }]
+ }
+ }
+ self.view._del_flavors(rsp)
+ mock_call.assert_called_once()
+
+ @mock.patch.object(restcall, "call_req")
+ def test_del_images(self, mock_call):
+ mock_call.return_value = [0]
+ rsp = {
+ "images": {
+ "image": [{
+ "image-id": "fake-id",
+ "resource-version": "fake-version"
+ }]
+ }
+ }
+ self.view._del_images(rsp)
+ mock_call.assert_called_once()
+
+ @mock.patch.object(restcall, "call_req")
+ def test_del_networks(self, mock_call):
+ mock_call.return_value = [0]
+ rsp = {
+ "oam-networks": {
+ "oam-network": [{
+ "network-uuid": "fake-id",
+ "resource-version": "fake-version"
+ }]
+ }
+ }
+ self.view._del_networks(rsp)
+ mock_call.assert_called_once()
+
+ @mock.patch.object(restcall, "call_req")
+ def test_del_azs(self, mock_call):
+ mock_call.return_value = [0]
+ rsp = {
+ "availability-zones": {
+ "availability-zone": [{
+ "availability-zone-name": "fake-name",
+ "resource-version": "fake-version"
+ }]
+ }
+ }
+ self.view._del_azs(rsp)
+ mock_call.assert_called_once()
+
+ @mock.patch.object(restcall, "call_req")
+ def test_del_hpa(self, mock_call):
+ mock_call.return_value = [0]
+ rsp = {
+ "flavor-id": "id1",
+ "hpa-capabilities": {
+ "hpa-capability": [{
+ "resource-version": "v1",
+ "hpa-capability-id": "id2"
+ }]
+ }
+ }
+ self.view._del_hpa(rsp)
+ mock_call.assert_called_once()
+
+ @mock.patch.object(restcall, "call_req")
+ def test_del_vim(self, mock_call):
+ resp = {
+ "resource-version": "1"
+ }
+ self.view.get_vim = mock.MagicMock()
+ self.view.get_vim.return_value = resp
+ mock_call.return_value = [0, "", "", ""]
+ self.view.delete_vim()
+ mock_call.assert_called_once()
+
+ @mock.patch.object(restcall, "call_req")
+ def test_del_vim_fail(self, mock_call):
+ resp = {
+ "resource-version": "1"
+ }
+ self.view.get_vim = mock.MagicMock()
+ self.view.get_vim.return_value = resp
+ mock_call.return_value = [1, "", "", ""]
+ self.assertRaises(VimDriverAzureException, self.view.delete_vim)
+
+ @mock.patch.object(restcall, "call_req")
+ def test_update_vim(self, mock_call):
+ resp = {
+ "resource-version": "1"
+ }
+ self.view.get_vim = mock.MagicMock()
+ self.view.get_vim.return_value = resp
+ content = {
+ "tenants": [],
+ "images": [],
+ "flavors": [],
+ "networks": [],
+ "hypervisors": []
+ }
+ self.view.update_vim(content)
+ mock_call.assert_called_once()
+
+ @mock.patch.object(restcall, "call_req")
+ def test_get_hpa(self, mock_call):
+ self.view._get_hpa_basic_capabilities = mock.MagicMock()
+ self.view._get_hpa_basic_capabilities.return_value = {"hpa": "basic"}
+ self.view._get_cpupinning_capabilities = mock.MagicMock()
+ self.view._get_cpupinning_capabilities.return_value = {"hpa": "basic"}
+ self.view._get_cputopology_capabilities = mock.MagicMock()
+ self.view._get_cputopology_capabilities.return_value = {"hpa": "basic"}
+ self.view._get_hugepages_capabilities = mock.MagicMock()
+ self.view._get_hugepages_capabilities.return_value = {"hpa": "basic"}
+ self.view._get_numa_capabilities = mock.MagicMock()
+ self.view._get_numa_capabilities.return_value = {"hpa": "basic"}
+ self.view._get_storage_capabilities = mock.MagicMock()
+ self.view._get_storage_capabilities.return_value = {"hpa": "basic"}
+ self.view._get_instruction_set_capabilities = mock.MagicMock()
+ self.view._get_instruction_set_capabilities.return_value = {
+ "hpa": "basic"}
+ self.view._get_pci_passthrough_capabilities = mock.MagicMock()
+ self.view._get_pci_passthrough_capabilities.return_value = {
+ "hpa": "basic"}
+ self.view._get_ovsdpdk_capabilities = mock.MagicMock()
+ self.view._get_ovsdpdk_capabilities.return_value = {"hpa": "basic"}
+ ret = self.view._get_hpa_capabilities({"extra_specs": {}})
+ self.assertEqual([{"hpa": "basic"}]*9, ret)
+
+ @mock.patch.object(restcall, "call_req")
+ def test_get_hpa_basic(self, mock_call):
+ flavor = {
+ "vcpus": 1,
+ "ram": 1024
+ }
+ ret = self.view._get_hpa_basic_capabilities(flavor)
+ self.assertEqual(len(ret["hpa-feature-attributes"]), 2)
+
+ @mock.patch.object(restcall, "call_req")
+ def test_get_hpa_cpupin(self, mock_call):
+ extra = {
+ "hw:cpu_policy": "cpu_policy",
+ "hw:cpu_thread_policy": "thread_policy"
+ }
+ ret = self.view._get_cpupinning_capabilities(extra)
+ self.assertEqual(len(ret["hpa-feature-attributes"]), 2)
+
+ @mock.patch.object(restcall, "call_req")
+ def test_get_hpa_cputopo(self, mock_call):
+ extra = {
+ "hw:cpu_sockets": 2,
+ "hw:cpu_cores": 2,
+ "hw:cpu_threads": 4
+ }
+ ret = self.view._get_cputopology_capabilities(extra)
+ self.assertEqual(len(ret["hpa-feature-attributes"]), 3)
+
+ @mock.patch.object(restcall, "call_req")
+ def test_get_hpa_hugepage_large(self, mock_call):
+ extra = {
+ "hw:mem_page_size": "large"
+ }
+ ret = self.view._get_hugepages_capabilities(extra)
+ self.assertIn(
+ "2", ret["hpa-feature-attributes"][0]["hpa-attribute-value"])
+
+ @mock.patch.object(restcall, "call_req")
+ def test_get_hpa_hugepage_small(self, mock_call):
+ extra = {
+ "hw:mem_page_size": "small"
+ }
+ ret = self.view._get_hugepages_capabilities(extra)
+ self.assertIn(
+ "4", ret["hpa-feature-attributes"][0]["hpa-attribute-value"])
+
+ @mock.patch.object(restcall, "call_req")
+ def test_get_hpa_hugepage_int(self, mock_call):
+ extra = {
+ "hw:mem_page_size": 8,
+ }
+ ret = self.view._get_hugepages_capabilities(extra)
+ self.assertIn(
+ "8", ret["hpa-feature-attributes"][0]["hpa-attribute-value"])
+
+ @mock.patch.object(restcall, "call_req")
+ def test_get_hpa_hugepage_any(self, mock_call):
+ extra = {
+ "hw:mem_page_size": "any",
+ }
+ ret = self.view._get_hugepages_capabilities(extra)
+ self.assertEqual(0, len(ret["hpa-feature-attributes"]))
+
+ @mock.patch.object(restcall, "call_req")
+ def test_get_hpa_numa(self, mock_call):
+ extra = {
+ "hw:numa_nodes": 1,
+ "hw:numa_cpus.0": 1,
+ "hw:numa_mem.0": 1024,
+ }
+ ret = self.view._get_numa_capabilities(extra)
+ self.assertEqual(3, len(ret["hpa-feature-attributes"]))
+
+ @mock.patch.object(restcall, "call_req")
+ def test_get_hpa_storage(self, mock_call):
+ extra = {
+ "disk": 10,
+ }
+ ret = self.view._get_storage_capabilities(extra)
+ self.assertEqual(3, len(ret["hpa-feature-attributes"]))
+
+ @mock.patch.object(restcall, "call_req")
+ def test_get_hpa_instru(self, mock_call):
+ extra = {
+ "hw:capabilities:cpu_info:features": "avx",
+ }
+ ret = self.view._get_instruction_set_capabilities(extra)
+ self.assertEqual(1, len(ret["hpa-feature-attributes"]))
+
+ @mock.patch.object(restcall, "call_req")
+ def test_get_hpa_pci(self, mock_call):
+ extra = {
+ "pci_passthrough:alias": "gpu-nvidia-x86-0011-0022:1",
+ }
+ ret = self.view._get_pci_passthrough_capabilities(extra)
+ self.assertEqual(3, len(ret["hpa-feature-attributes"]))
+
+ @mock.patch.object(restcall, "call_req")
+ def test_get_hpa_dpdk(self, mock_call):
+ self.view.get_vim = mock.MagicMock()
+ self.view.get_vim.return_value = {
+ "cloud-extra-info": json.dumps({'ovsDpdk': {
+ 'libname': 'generic', 'libversion': '17.04'}})
+ }
+ ret = self.view._get_ovsdpdk_capabilities()
+ self.assertEqual(1, len(ret["hpa-feature-attributes"]))
diff --git a/azure/azure/tests/test_restcall.py b/azure/azure/tests/test_restcall.py
new file mode 100644
index 0000000..bf45ccf
--- /dev/null
+++ b/azure/azure/tests/test_restcall.py
@@ -0,0 +1,101 @@
+# Copyright (c) 2018 Amdocs
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+
+import mock
+import unittest
+import urllib2
+
+from azure.pub.utils import restcall
+
+
+class TestRestCall(unittest.TestCase):
+
+ def test_combine_url(self):
+ url = ["http://a.com/test/", "http://a.com/test/",
+ "http://a.com/test", "http://a.com/test"]
+ res = ["/resource", "resource", "/resource", "resource"]
+ expected = "http://a.com/test/resource"
+ for i in range(len(url)):
+ self.assertEqual(expected, restcall.combine_url(url[i], res[i]))
+
+ @mock.patch.object(restcall, "call_req")
+ def test_get_res_from_aai(self, mock_call):
+ res = "cloud-regions"
+ content = ""
+ expect_url = "https://aai.api.simpledemo.openecomp.org:8443/aai/v13"
+ expect_user = "AAI"
+ expect_pass = "AAI"
+ expect_headers = {
+ 'X-FromAppId': 'MultiCloud',
+ 'X-TransactionId': '9001',
+ 'content-type': 'application/json',
+ 'accept': 'application/json'
+ }
+ restcall.get_res_from_aai(res, content=content)
+ mock_call.assert_called_once_with(
+ expect_url, expect_user, expect_pass, restcall.rest_no_auth,
+ res, "GET", content, expect_headers)
+
+ @mock.patch.object(restcall, "call_req")
+ def test_req_by_msb(self, mock_call):
+ res = "multicloud"
+ method = "GET"
+ content = "no content"
+ restcall.req_by_msb(res, method, content=content)
+ expect_url = "http://msb.onap.org:10080/"
+ mock_call.assert_called_once_with(
+ expect_url, "", "", restcall.rest_no_auth, res, method,
+ content)
+
+ @mock.patch("httplib2.Http.request")
+ def test_call_req_success(self, mock_req):
+ mock_resp = {
+ "status": "200"
+ }
+ resp_content = "hello"
+ mock_req.return_value = mock_resp, resp_content
+ expect_ret = [0, resp_content, "200", mock_resp]
+ ret = restcall.call_req("http://onap.org/", "user", "pass",
+ restcall.rest_no_auth, "vim", "GET")
+ self.assertEqual(expect_ret, ret)
+
+ @mock.patch("httplib2.Http.request")
+ def test_call_req_not_200(self, mock_req):
+ mock_resp = {
+ "status": "404"
+ }
+ resp_content = "hello"
+ mock_req.return_value = mock_resp, resp_content
+ expect_ret = [1, resp_content, "404", mock_resp]
+ ret = restcall.call_req("http://onap.org/", "user", "pass",
+ restcall.rest_no_auth, "vim", "GET")
+ self.assertEqual(expect_ret, ret)
+
+ @mock.patch("traceback.format_exc")
+ @mock.patch("sys.exc_info")
+ @mock.patch("httplib2.Http.request")
+ def test_call_req_response_not_ready(self, mock_req, mock_sys,
+ mock_traceback):
+ mock_sys.return_value = "httplib.ResponseNotReady"
+ mock_req.side_effect = [Exception("httplib.ResponseNotReady")] * 3
+ expect_ret = [1, "Unable to connect to http://onap.org/vim", "", ""]
+ ret = restcall.call_req("http://onap.org/", "user", "pass",
+ restcall.rest_no_auth, "vim", "GET")
+ self.assertEqual(expect_ret, ret)
+ self.assertEqual(3, mock_req.call_count)
+
+ @mock.patch("httplib2.Http.request")
+ def test_call_req_url_err(self, mock_req):
+ urlerr = urllib2.URLError("urlerror")
+ mock_req.side_effect = [urlerr]
+ expect_ret = [2, str(urlerr), "", ""]
+ ret = restcall.call_req("http://onap.org/", "user", "pass",
+ restcall.rest_no_auth, "vim", "GET")
+ self.assertEqual(expect_ret, ret)
diff --git a/azure/azure/urls.py b/azure/azure/urls.py
new file mode 100644
index 0000000..028f008
--- /dev/null
+++ b/azure/azure/urls.py
@@ -0,0 +1,18 @@
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+
+from django.conf.urls import include, url
+
+urlpatterns = [
+ url(r'^', include('azure.swagger.urls')),
+ url(r'^', include('azure.samples.urls')),
+]
diff --git a/azure/azure/wsgi.py b/azure/azure/wsgi.py
new file mode 100644
index 0000000..355b604
--- /dev/null
+++ b/azure/azure/wsgi.py
@@ -0,0 +1,20 @@
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "azure.settings")
+
+application = get_wsgi_application()
diff --git a/azure/docker/Dockerfile b/azure/docker/Dockerfile
new file mode 100644
index 0000000..1b232ae
--- /dev/null
+++ b/azure/docker/Dockerfile
@@ -0,0 +1,29 @@
+FROM python:2
+
+ENV MSB_ADDR "127.0.0.1"
+ENV MSB_PORT "80"
+ENV AAI_ADDR "aai.api.simpledemo.openecomp.org"
+ENV AAI_PORT "8443"
+ENV AAI_SCHEMA_VERSION "v13"
+ENV AAI_USERNAME "AAI"
+ENV AAI_PASSWORD "AAI"
+ENV MR_ADDR "127.0.0.1"
+ENV MR_PORT "3904"
+
+EXPOSE 9004
+
+RUN apt-get update && \
+ apt-get install -y unzip && \
+ apt-get install -y curl && \
+ apt-get install -y wget
+
+
+RUN cd /opt/ && \
+ wget -q -O multicloud-azure.zip 'https://nexus.onap.org/service/local/artifact/maven/redirect?r=snapshots&g=org.onap.multicloud.azure&a=multicloud-azure&v=LATEST&e=zip' && \
+ unzip multicloud-azure.zip && \
+ rm -rf multicloud-azure.zip && \
+ pip install -r azure/requirements.txt
+
+
+WORKDIR /opt
+ENTRYPOINT azure/docker/docker-entrypoint.sh
diff --git a/azure/docker/build_image.sh b/azure/docker/build_image.sh
new file mode 100644
index 0000000..24ba356
--- /dev/null
+++ b/azure/docker/build_image.sh
@@ -0,0 +1,50 @@
+#!/bin/bash
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+DIRNAME=`dirname $0`
+DOCKER_BUILD_DIR=`cd $DIRNAME/; pwd`
+echo "DOCKER_BUILD_DIR=${DOCKER_BUILD_DIR}"
+cd ${DOCKER_BUILD_DIR}
+
+BUILD_ARGS="--no-cache"
+ORG="onap"
+VERSION="1.2.0-SNAPSHOT"
+STAGING="1.2.0-STAGING"
+PROJECT="multicloud"
+IMAGE="azure"
+DOCKER_REPOSITORY="nexus3.onap.org:10003"
+IMAGE_NAME="${DOCKER_REPOSITORY}/${ORG}/${PROJECT}/${IMAGE}"
+
+if [ $HTTP_PROXY ]; then
+ BUILD_ARGS+=" --build-arg HTTP_PROXY=${HTTP_PROXY}"
+fi
+if [ $HTTPS_PROXY ]; then
+ BUILD_ARGS+=" --build-arg HTTPS_PROXY=${HTTPS_PROXY}"
+fi
+
+function build_image {
+ echo "Start build docker image: ${IMAGE_NAME}"
+ docker build ${BUILD_ARGS} -t ${IMAGE_NAME}:${VERSION} -t ${IMAGE_NAME}:latest -t ${IMAGE_NAME}:${STAGING} .
+}
+
+function push_image {
+ echo "Start push docker image: ${IMAGE_NAME}"
+ docker push ${IMAGE_NAME}:${VERSION}
+ docker push ${IMAGE_NAME}:latest
+ docker push ${IMAGE_NAME}:${STAGING}
+}
+
+build_image
+push_image
diff --git a/azure/docker/docker-entrypoint.sh b/azure/docker/docker-entrypoint.sh
new file mode 100644
index 0000000..5566aff
--- /dev/null
+++ b/azure/docker/docker-entrypoint.sh
@@ -0,0 +1,48 @@
+#!/bin/bash
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+if [ -z "$SERVICE_IP" ]; then
+ export SERVICE_IP=`hostname -i`
+fi
+echo
+echo Environment Variables:
+echo "SERVICE_IP=$SERVICE_IP"
+
+if [ -z "$MSB_ADDR" ]; then
+ echo "Missing required variable MSB_ADDR: Microservices Service Bus address <ip>:<port>"
+ exit 1
+fi
+echo "MSB_ADDR=$MSB_ADDR"
+echo
+
+
+echo
+
+# Configure service based on docker environment variables
+azure/docker/instance-config.sh
+
+
+# Perform one-time config
+if [ ! -e init.log ]; then
+
+ # microservice-specific one-time initialization
+ azure/docker/instance-init.sh
+
+ date > init.log
+fi
+
+# Start the microservice
+azure/docker/instance-run.sh
diff --git a/azure/docker/instance-config.sh b/azure/docker/instance-config.sh
new file mode 100644
index 0000000..6500d03
--- /dev/null
+++ b/azure/docker/instance-config.sh
@@ -0,0 +1,48 @@
+#!/bin/bash
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Configure MSB IP address
+MSB_IP=`echo $MSB_ADDR | cut -d: -f 1`
+MSB_PORT=`echo $MSB_PORT | cut -d: -f 2`
+sed -i "s|MSB_SERVICE_IP.*|MSB_SERVICE_IP = '$MSB_IP'|" azure/azure/pub/config/config.py
+sed -i "s|MSB_SERVICE_PORT.*|MSB_SERVICE_PORT = '$MSB_PORT'|" azure/azure/pub/config/config.py
+sed -i "s|DB_NAME.*|DB_NAME = 'inventory'|" azure/azure/pub/config/config.py
+sed -i "s|DB_USER.*|DB_USER = 'inventory'|" azure/azure/pub/config/config.py
+sed -i "s|DB_PASSWD.*|DB_PASSWD = 'inventory'|" azure/azure/pub/config/config.py
+sed -i "s|\"ip\": \".*\"|\"ip\": \"$SERVICE_IP\"|" azure/azure/pub/config/config.py
+
+# Configure MYSQL
+if [ -z "$MYSQL_ADDR" ]; then
+ export MYSQL_IP=`hostname -i`
+ export MYSQL_PORT=3306
+ export MYSQL_ADDR=$MYSQL_IP:$MYSQL_PORT
+else
+ MYSQL_IP=`echo $MYSQL_ADDR | cut -d: -f 1`
+ MYSQL_PORT=`echo $MYSQL_ADDR | cut -d: -f 2`
+fi
+echo "MYSQL_ADDR=$MYSQL_ADDR"
+sed -i "s|DB_IP.*|DB_IP = '$MYSQL_IP'|" azure/azure/pub/config/config.py
+sed -i "s|DB_PORT.*|DB_PORT = $MYSQL_PORT|" azure/azure/pub/config/config.py
+
+cat azure/azure/pub/config/config.py
+
+sed -i "s/sip=.*/sip=$SERVICE_IP/g" azure/run.sh
+sed -i "s/sip=.*/sip=$SERVICE_IP/g" azure/stop.sh
+
+# Create log directory
+logDir="/var/log/onap/multicloud/azure"
+if [ ! -x $logDir ]; then
+ mkdir -p $logDir
+fi
diff --git a/azure/docker/instance-init.sh b/azure/docker/instance-init.sh
new file mode 100644
index 0000000..cd2222c
--- /dev/null
+++ b/azure/docker/instance-init.sh
@@ -0,0 +1,22 @@
+#!/bin/bash -v
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Initialize DB schema
+#./bin/initDB.sh root rootpass 3306 127.0.0.1
+
+# Install python requirements
+cd /opt/azure
+./initialize.sh
+cd /opt
diff --git a/azure/docker/instance-run.sh b/azure/docker/instance-run.sh
new file mode 100644
index 0000000..90d3e9d
--- /dev/null
+++ b/azure/docker/instance-run.sh
@@ -0,0 +1,22 @@
+#!/bin/bash -v
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+cd ./azure
+./run.sh
+
+while [ ! -f /var/log/onap/multicloud/azure/azure.log ]; do
+ sleep 1
+done
+tail -F /var/log/onap/multicloud/azure/azure.log
diff --git a/azure/images/empty.txt b/azure/images/empty.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/azure/images/empty.txt
diff --git a/azure/initialize.sh b/azure/initialize.sh
new file mode 100644
index 0000000..6128a8b
--- /dev/null
+++ b/azure/initialize.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+
+pip install -r requirements.txt
diff --git a/azure/logs/empty.txt b/azure/logs/empty.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/azure/logs/empty.txt
diff --git a/azure/manage.py b/azure/manage.py
new file mode 100644
index 0000000..4a98417
--- /dev/null
+++ b/azure/manage.py
@@ -0,0 +1,20 @@
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+
+import os
+import sys
+
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "azure.settings")
+
+if __name__ == "__main__":
+ from django.core.management import execute_from_command_line
+ execute_from_command_line(sys.argv)
diff --git a/azure/pom.xml b/azure/pom.xml
new file mode 100644
index 0000000..42740aa
--- /dev/null
+++ b/azure/pom.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0"?>
+<!--
+ Copyright (c) 2018 Amdocs
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at:
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ -->
+<project
+ xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.onap.oparent</groupId>
+ <artifactId>oparent</artifactId>
+ <version>1.1.0</version>
+ <relativePath>../oparent</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.onap.multicloud.azure</groupId>
+ <artifactId>multicloud-azure</artifactId>
+ <version>1.2.0-SNAPSHOT</version>
+ <packaging>pom</packaging>
+ <name>multicloud-azure</name>
+ <description>multicloud azure</description>
+ <properties>
+ <encoding>UTF-8</encoding>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+ <nexusproxy>https://nexus.onap.org</nexusproxy>
+ </properties>
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <configuration>
+ <appendAssemblyId>false</appendAssemblyId>
+ <descriptors>
+ <descriptor>assembly.xml</descriptor>
+ </descriptors>
+ </configuration>
+ <executions>
+ <execution>
+ <id>make-assembly</id>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+
+ </build>
+</project>
diff --git a/azure/requirements.txt b/azure/requirements.txt
new file mode 100644
index 0000000..aa98578
--- /dev/null
+++ b/azure/requirements.txt
@@ -0,0 +1,39 @@
+# rest framework
+Django==1.9.6
+djangorestframework==3.3.3
+
+# redis cache
+redis==2.10.5
+
+# for access redis cache
+redisco==0.1.4
+django-redis-cache==0.13.1
+
+# for call rest api
+httplib2==0.9.2
+
+# for call openstack api
+# openstacksdk==0.9.15
+# os-client-config==1.29.0
+# python-cinderclient==3.5.0
+
+# for unit test
+django-nose>=1.4.0
+coverage==4.2
+mock==2.0.0
+unittest_xml_reporting==1.12.0
+
+# for onap logging
+onappylog>=1.0.6
+
+# for event
+oslo_messaging
+
+# for pecan framework
+uwsgi
+pecan>=1.2.1
+oslo.concurrency>=3.21.0
+oslo.config>=4.11.0
+oslo.service>=1.25.0
+eventlet>=0.20.0
+PyYAML>=3.1.0
diff --git a/azure/run.sh b/azure/run.sh
new file mode 100644
index 0000000..6883739
--- /dev/null
+++ b/azure/run.sh
@@ -0,0 +1,41 @@
+#!/bin/bash
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+sed -i "s/MSB_SERVICE_IP =.*/MSB_SERVICE_IP = \"${MSB_ADDR}\"/g" azure/pub/config/config.py
+sed -i "s/MSB_SERVICE_PORT =.*/MSB_SERVICE_PORT = \"${MSB_PORT}\"/g" azure/pub/config/config.py
+sed -i "s/AAI_ADDR =.*/AAI_ADDR = \"${AAI_ADDR}\"/g" azure/pub/config/config.py
+sed -i "s/AAI_PORT =.*/AAI_PORT = \"${AAI_PORT}\"/g" azure/pub/config/config.py
+sed -i "s/AAI_SCHEMA_VERSION =.*/AAI_SCHEMA_VERSION = \"${AAI_SCHEMA_VERSION}\"/g" azure/pub/config/config.py
+sed -i "s/AAI_USERNAME =.*/AAI_USERNAME = \"${AAI_USERNAME}\"/g" azure/pub/config/config.py
+sed -i "s/AAI_PASSWORD =.*/AAI_PASSWORD = \"${AAI_PASSWORD}\"/g" azure/pub/config/config.py
+sed -i "s/MR_ADDR =.*/MR_ADDR = \"${MR_ADDR}\"/g" azure/pub/config/config.py
+sed -i "s/MR_PORT =.*/MR_PORT = \"${MR_PORT}\"/g" azure/pub/config/config.py
+
+
+logDir="/var/log/onap/multicloud/azure"
+
+if [ "$WEB_FRAMEWORK" == "pecan" ]
+then
+ python multivimbroker/scripts/api.py
+else
+ # nohup python manage.py runserver 0.0.0.0:9008 2>&1 &
+ nohup uwsgi --http :9008 --module azure.wsgi --master --processes 4 &
+ nohup python -m azure.event_listener.server 2>&1 &
+
+ while [ ! -f $logDir/azure.log ]; do
+ sleep 1
+ done
+tail -F $logDir/azure.log
+fi
diff --git a/azure/setup.py b/azure/setup.py
new file mode 100644
index 0000000..c81e04f
--- /dev/null
+++ b/azure/setup.py
@@ -0,0 +1,23 @@
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import setuptools
+
+setuptools.setup(
+ name="azure",
+ version="1.0",
+ packages=setuptools.find_packages(),
+ include_package_data=True,
+ zip_safe=True
+)
diff --git a/azure/stop.sh b/azure/stop.sh
new file mode 100644
index 0000000..04e822a
--- /dev/null
+++ b/azure/stop.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+
+# ps auxww | grep 'manage.py runserver 0.0.0.0:9004' | awk '{print $2}' | xargs kill -9
+ps auxww |grep 'uwsgi --http :9004 --module azure.wsgi --master' |awk '{print $2}' |xargs kill -9
diff --git a/azure/tox.ini b/azure/tox.ini
new file mode 100644
index 0000000..d6559ab
--- /dev/null
+++ b/azure/tox.ini
@@ -0,0 +1,28 @@
+[tox]
+envlist = py27,pep8
+skipsdist = true
+
+[tox:jenkins]
+downloadcache = ~/cache/pip
+
+[testenv]
+deps = -r{toxinidir}/requirements.txt
+commands =
+ /usr/bin/find . -type f -name "*.py[c|o]" -delete
+ python manage.py test azure
+
+[testenv:pep8]
+deps=flake8
+commands=flake8
+
+[testenv:py27]
+commands =
+ {[testenv]commands}
+
+[testenv:cover]
+setenv=
+ DJANGO_SETTINGS_MODULE = azure.settings-cover
+commands =
+ coverage erase
+ {[testenv]commands}
+ coverage xml -i \ No newline at end of file
diff --git a/azure/version.properties b/azure/version.properties
new file mode 100644
index 0000000..831b951
--- /dev/null
+++ b/azure/version.properties
@@ -0,0 +1,27 @@
+#
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#
+
+# Versioning variables
+# Note that these variables cannot be structured (e.g. : version.release or version.snapshot etc... )
+# because they are used in Jenkins, whose plug-in doesn't support
+
+major=1
+minor=2
+patch=0
+
+base_version=${major}.${minor}.${patch}
+
+# Release must be completed with git revision # in Jenkins
+release_version=${base_version}
+snapshot_version=${base_version}-SNAPSHOT
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..cf86510
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,99 @@
+<?xml version="1.0"?>
+<!--
+ Copyright (c) 2018 Amdocs
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at:
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.onap.oparent</groupId>
+ <artifactId>oparent</artifactId>
+ <version>1.1.0</version>
+ <relativePath>../oparent</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.onap.multicloud.azure</groupId>
+ <artifactId>multicloud-azure</artifactId>
+ <version>1.2.0-SNAPSHOT</version>
+ <packaging>pom</packaging>
+ <name>multicloud-azure</name>
+ <description>multicloud azure</description>
+ <properties>
+ <encoding>UTF-8</encoding>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+ <nexusproxy>https://nexus.onap.org</nexusproxy>
+
+ <sonar.sourceEncoding>UTF-8</sonar.sourceEncoding>
+ <sonar.sources>.</sonar.sources>
+ <sonar.junit.reportsPath>xunit-results.xml</sonar.junit.reportsPath>
+ <sonar.python.coverage.reportPath>azure/coverage.xml</sonar.python.coverage.reportPath>
+ <sonar.language>py</sonar.language>
+ <sonar.pluginName>Python</sonar.pluginName>
+ <sonar.inclusions>**/*.py</sonar.inclusions>
+ <sonar.exclusions>**/tests/*,setup.py</sonar.exclusions>
+ </properties>
+ <build>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <version>1.2.1</version>
+ <configuration>
+ <executable>${session.executionRootDirectory}/sonar.sh</executable>
+ <environmentVariables>
+ <!-- make mvn properties as env for our script -->
+ <MVN_PROJECT_GROUPID>${project.groupId}</MVN_PROJECT_GROUPID>
+ <MVN_PROJECT_ARTIFACTID>${project.artifactId}</MVN_PROJECT_ARTIFACTID>
+ <MVN_PROJECT_VERSION>${project.version}</MVN_PROJECT_VERSION>
+ </environmentVariables>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <version>1.2.1</version>
+ <executions>
+ <execution>
+ <id>clean phase script</id>
+ <phase>clean</phase>
+ <goals>
+ <goal>exec</goal>
+ </goals>
+ <configuration>
+ <arguments>
+ <argument>__</argument>
+ <argument>clean</argument>
+ </arguments>
+ </configuration>
+ </execution>
+ <execution>
+ <id>test script</id>
+ <phase>test</phase>
+ <goals>
+ <goal>exec</goal>
+ </goals>
+ <configuration>
+ <arguments>
+ <argument>__</argument>
+ <argument>test</argument>
+ </arguments>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/sonar.sh b/sonar.sh
new file mode 100755
index 0000000..25cc44c
--- /dev/null
+++ b/sonar.sh
@@ -0,0 +1,83 @@
+#!/bin/bash
+# Copyright 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+set -e
+
+echo "running script: [$0] for module [$1] at stage [$2]"
+
+export SETTINGS_FILE=${SETTINGS_FILE:-$HOME/.m2/settings.xml}
+MVN_PROJECT_MODULEID="$1"
+MVN_PHASE="$2"
+
+
+FQDN="${MVN_PROJECT_GROUPID}.${MVN_PROJECT_ARTIFACTID}"
+if [ "$MVN_PROJECT_MODULEID" == "__" ]; then
+ MVN_PROJECT_MODULEID=""
+fi
+
+if [ -z "$WORKSPACE" ]; then
+ WORKSPACE=$(pwd)
+fi
+
+# mvn phase in life cycle
+MVN_PHASE="$2"
+
+
+echo "MVN_PROJECT_MODULEID is [$MVN_PROJECT_MODULEID]"
+echo "MVN_PHASE is [$MVN_PHASE]"
+echo "MVN_PROJECT_GROUPID is [$MVN_PROJECT_GROUPID]"
+echo "MVN_PROJECT_ARTIFACTID is [$MVN_PROJECT_ARTIFACTID]"
+echo "MVN_PROJECT_VERSION is [$MVN_PROJECT_VERSION]"
+
+run_tox_test()
+{
+ set -x
+ cd azure
+ CURDIR=$(pwd)
+ TOXINIS=$(find . -name "tox.ini")
+ cd ..
+ for TOXINI in "${TOXINIS[@]}"; do
+ DIR=$(echo "$TOXINI" | rev | cut -f2- -d'/' | rev)
+ cd "${CURDIR}/${DIR}"
+ rm -rf ./venv-tox ./.tox
+ virtualenv ./venv-tox
+ source ./venv-tox/bin/activate
+ pip install --upgrade pip
+ pip install --upgrade tox argparse
+ pip freeze
+ cd azure
+ tox -e cover
+ deactivate
+ cd ..
+ rm -rf ./venv-tox ./.tox
+ done
+}
+
+
+case $MVN_PHASE in
+clean)
+ echo "==> clean phase script"
+ rm -rf ./venv-*
+ ;;
+test)
+ echo "==> test phase script"
+ run_tox_test
+ ;;
+*)
+ echo "==> unprocessed phase"
+ ;;
+esac
+
diff --git a/version.properties b/version.properties
new file mode 100644
index 0000000..831b951
--- /dev/null
+++ b/version.properties
@@ -0,0 +1,27 @@
+#
+# Copyright (c) 2018 Amdocs
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#
+
+# Versioning variables
+# Note that these variables cannot be structured (e.g. : version.release or version.snapshot etc... )
+# because they are used in Jenkins, whose plug-in doesn't support
+
+major=1
+minor=2
+patch=0
+
+base_version=${major}.${minor}.${patch}
+
+# Release must be completed with git revision # in Jenkins
+release_version=${base_version}
+snapshot_version=${base_version}-SNAPSHOT