summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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