Adoption of base framework code for azure plugin 45/60345/9
authorSudhakar Reddy <reddysud@amdocs.com>
Mon, 13 Aug 2018 15:43:56 +0000 (21:13 +0530)
committerSudhakar Reddy <Sudhakar.Reddy@amdocs.com>
Mon, 20 Aug 2018 08:49:54 +0000 (14:19 +0530)
This is the initial code which is created by referring to vmware
plugin.The logging and containerization features are readily available.

Change-Id: I3371d5f0671c2252edb1da7ea55f1f89ea27b3aa
Issue-ID: MULTICLOUD-308
Signed-off-by: Sudhakar Reddy <Sudhakar.Reddy@amdocs.com>
91 files changed:
.gitignore [new file with mode: 0644]
.gitreview [new file with mode: 0644]
License.txt [new file with mode: 0644]
azure/README.md [new file with mode: 0644]
azure/assembly.xml [new file with mode: 0644]
azure/azure/__init__.py [new file with mode: 0644]
azure/azure/api_v2/__init__.py [new file with mode: 0644]
azure/azure/api_v2/api_definition/__init__.py [new file with mode: 0644]
azure/azure/api_v2/api_definition/hosts.yaml [new file with mode: 0644]
azure/azure/api_v2/api_definition/images.yaml [new file with mode: 0644]
azure/azure/api_v2/api_definition/networks.yaml [new file with mode: 0644]
azure/azure/api_v2/api_definition/ports.yaml [new file with mode: 0644]
azure/azure/api_v2/api_definition/subnets.yaml [new file with mode: 0644]
azure/azure/api_v2/api_definition/utils.py [new file with mode: 0644]
azure/azure/api_v2/api_router/__init__.py [new file with mode: 0644]
azure/azure/api_v2/api_router/controller_builder.py [new file with mode: 0644]
azure/azure/api_v2/api_router/root.py [new file with mode: 0644]
azure/azure/api_v2/api_router/swagger_json.py [new file with mode: 0644]
azure/azure/api_v2/api_router/v0_controller.py [new file with mode: 0644]
azure/azure/api_v2/app.py [new file with mode: 0644]
azure/azure/api_v2/service.py [new file with mode: 0644]
azure/azure/event_listener/__init__.py [new file with mode: 0644]
azure/azure/event_listener/i18n.py [new file with mode: 0644]
azure/azure/event_listener/listener.conf [new file with mode: 0644]
azure/azure/event_listener/server.py [new file with mode: 0644]
azure/azure/middleware.py [new file with mode: 0644]
azure/azure/pub/__init__.py [new file with mode: 0644]
azure/azure/pub/config/__init__.py [new file with mode: 0644]
azure/azure/pub/config/config.py [new file with mode: 0644]
azure/azure/pub/config/log.yml [new file with mode: 0644]
azure/azure/pub/database/__init__.py [new file with mode: 0644]
azure/azure/pub/database/models.py [new file with mode: 0644]
azure/azure/pub/exceptions.py [new file with mode: 0644]
azure/azure/pub/msapi/__init__.py [new file with mode: 0644]
azure/azure/pub/msapi/extsys.py [new file with mode: 0644]
azure/azure/pub/utils/__init__.py [new file with mode: 0644]
azure/azure/pub/utils/enumutil.py [new file with mode: 0644]
azure/azure/pub/utils/fileutil.py [new file with mode: 0644]
azure/azure/pub/utils/idutil.py [new file with mode: 0644]
azure/azure/pub/utils/restcall.py [new file with mode: 0644]
azure/azure/pub/utils/syscomm.py [new file with mode: 0644]
azure/azure/pub/utils/timeutil.py [new file with mode: 0644]
azure/azure/pub/utils/values.py [new file with mode: 0644]
azure/azure/pub/vim/__init__.py [new file with mode: 0644]
azure/azure/pub/vim/const.py [new file with mode: 0644]
azure/azure/samples/__init__.py [new file with mode: 0644]
azure/azure/samples/tests.py [new file with mode: 0644]
azure/azure/samples/urls.py [new file with mode: 0644]
azure/azure/samples/views.py [new file with mode: 0644]
azure/azure/scripts/__init__.py [new file with mode: 0644]
azure/azure/scripts/api.py [new file with mode: 0644]
azure/azure/settings-cover.py [new file with mode: 0644]
azure/azure/settings.py [new file with mode: 0644]
azure/azure/swagger/__init__.py [new file with mode: 0644]
azure/azure/swagger/image_utils.py [new file with mode: 0644]
azure/azure/swagger/nova_utils.py [new file with mode: 0644]
azure/azure/swagger/tests.py [new file with mode: 0644]
azure/azure/swagger/urls.py [new file with mode: 0644]
azure/azure/swagger/utils.py [new file with mode: 0644]
azure/azure/swagger/views.py [new file with mode: 0644]
azure/azure/swagger/views/__init__.py [new file with mode: 0644]
azure/azure/swagger/views/multivim.swagger.json [new file with mode: 0644]
azure/azure/swagger/views/registry/__init__.py [new file with mode: 0644]
azure/azure/swagger/views/registry/views.py [new file with mode: 0644]
azure/azure/swagger/views/swagger_json.py [new file with mode: 0644]
azure/azure/swagger/volume_utils.py [new file with mode: 0644]
azure/azure/tests/__init__.py [new file with mode: 0644]
azure/azure/tests/test_aai_client.py [new file with mode: 0644]
azure/azure/tests/test_restcall.py [new file with mode: 0644]
azure/azure/urls.py [new file with mode: 0644]
azure/azure/wsgi.py [new file with mode: 0644]
azure/docker/Dockerfile [new file with mode: 0644]
azure/docker/build_image.sh [new file with mode: 0644]
azure/docker/docker-entrypoint.sh [new file with mode: 0644]
azure/docker/instance-config.sh [new file with mode: 0644]
azure/docker/instance-init.sh [new file with mode: 0644]
azure/docker/instance-run.sh [new file with mode: 0644]
azure/images/empty.txt [new file with mode: 0644]
azure/initialize.sh [new file with mode: 0644]
azure/logs/empty.txt [new file with mode: 0644]
azure/manage.py [new file with mode: 0644]
azure/pom.xml [new file with mode: 0644]
azure/requirements.txt [new file with mode: 0644]
azure/run.sh [new file with mode: 0644]
azure/setup.py [new file with mode: 0644]
azure/stop.sh [new file with mode: 0644]
azure/tox.ini [new file with mode: 0644]
azure/version.properties [new file with mode: 0644]
pom.xml [new file with mode: 0644]
sonar.sh [new file with mode: 0755]
version.properties [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..64c410c
--- /dev/null
@@ -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 (file)
index 0000000..a5cf8f2
--- /dev/null
@@ -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 (file)
index 0000000..0dd49ad
--- /dev/null
@@ -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â€\9d);
+* 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â€\9d);
+* 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 (file)
index 0000000..cdca6b2
--- /dev/null
@@ -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 (file)
index 0000000..61a4883
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
diff --git a/azure/azure/api_v2/__init__.py b/azure/azure/api_v2/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/azure/azure/api_v2/api_definition/__init__.py b/azure/azure/api_v2/api_definition/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/azure/azure/api_v2/api_definition/hosts.yaml b/azure/azure/api_v2/api_definition/hosts.yaml
new file mode 100644 (file)
index 0000000..88eaa09
--- /dev/null
@@ -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 (file)
index 0000000..723884c
--- /dev/null
@@ -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 (file)
index 0000000..c00808f
--- /dev/null
@@ -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 (file)
index 0000000..e159593
--- /dev/null
@@ -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 (file)
index 0000000..e48d570
--- /dev/null
@@ -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 (file)
index 0000000..3f5a8a1
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
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 (file)
index 0000000..ec66268
--- /dev/null
@@ -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 (file)
index 0000000..0b6f995
--- /dev/null
@@ -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 (file)
index 0000000..8573a46
--- /dev/null
@@ -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 (file)
index 0000000..104597a
--- /dev/null
@@ -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 (file)
index 0000000..2c13403
--- /dev/null
@@ -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 (file)
index 0000000..2b73c15
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
diff --git a/azure/azure/event_listener/i18n.py b/azure/azure/event_listener/i18n.py
new file mode 100644 (file)
index 0000000..8fd9a6e
--- /dev/null
@@ -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 (file)
index 0000000..4376fe7
--- /dev/null
@@ -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 (file)
index 0000000..7f1f830
--- /dev/null
@@ -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 (file)
index 0000000..1edc44b
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
diff --git a/azure/azure/pub/config/__init__.py b/azure/azure/pub/config/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/azure/azure/pub/config/config.py b/azure/azure/pub/config/config.py
new file mode 100644 (file)
index 0000000..db09fd6
--- /dev/null
@@ -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 (file)
index 0000000..7bbc427
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
diff --git a/azure/azure/pub/database/models.py b/azure/azure/pub/database/models.py
new file mode 100644 (file)
index 0000000..757430b
--- /dev/null
@@ -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 (file)
index 0000000..3f38f2c
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
diff --git a/azure/azure/pub/msapi/extsys.py b/azure/azure/pub/msapi/extsys.py
new file mode 100644 (file)
index 0000000..f0b9dcc
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
diff --git a/azure/azure/pub/utils/enumutil.py b/azure/azure/pub/utils/enumutil.py
new file mode 100644 (file)
index 0000000..eb7a22c
--- /dev/null
@@ -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 (file)
index 0000000..1868300
--- /dev/null
@@ -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 (file)
index 0000000..be6e8a0
--- /dev/null
@@ -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 (file)
index 0000000..4b28098
--- /dev/null
@@ -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 (file)
index 0000000..f838956
--- /dev/null
@@ -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 (file)
index 0000000..d5ef329
--- /dev/null
@@ -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 (file)
index 0000000..61d7114
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
diff --git a/azure/azure/pub/vim/const.py b/azure/azure/pub/vim/const.py
new file mode 100644 (file)
index 0000000..dc5a3a4
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
diff --git a/azure/azure/samples/tests.py b/azure/azure/samples/tests.py
new file mode 100644 (file)
index 0000000..e801e48
--- /dev/null
@@ -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 (file)
index 0000000..39da376
--- /dev/null
@@ -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 (file)
index 0000000..6576450
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
diff --git a/azure/azure/scripts/api.py b/azure/azure/scripts/api.py
new file mode 100644 (file)
index 0000000..792fd5d
--- /dev/null
@@ -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 (file)
index 0000000..b4fecdd
--- /dev/null
@@ -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 (file)
index 0000000..ace6e8f
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
diff --git a/azure/azure/swagger/image_utils.py b/azure/azure/swagger/image_utils.py
new file mode 100644 (file)
index 0000000..a17565e
--- /dev/null
@@ -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 (file)
index 0000000..86f10a2
--- /dev/null
@@ -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 (file)
index 0000000..4631455
--- /dev/null
@@ -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 (file)
index 0000000..f80fa26
--- /dev/null
@@ -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 (file)
index 0000000..cb7e4f0
--- /dev/null
@@ -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 (file)
index 0000000..f0b7028
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
diff --git a/azure/azure/swagger/views/multivim.swagger.json b/azure/azure/swagger/views/multivim.swagger.json
new file mode 100644 (file)
index 0000000..de3419f
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
diff --git a/azure/azure/swagger/views/registry/views.py b/azure/azure/swagger/views/registry/views.py
new file mode 100644 (file)
index 0000000..b5db805
--- /dev/null
@@ -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 (file)
index 0000000..91c00dd
--- /dev/null
@@ -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 (file)
index 0000000..ae11285
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
diff --git a/azure/azure/tests/test_aai_client.py b/azure/azure/tests/test_aai_client.py
new file mode 100644 (file)
index 0000000..31ff37d
--- /dev/null
@@ -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 (file)
index 0000000..bf45ccf
--- /dev/null
@@ -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 (file)
index 0000000..028f008
--- /dev/null
@@ -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 (file)
index 0000000..355b604
--- /dev/null
@@ -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 (file)
index 0000000..1b232ae
--- /dev/null
@@ -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 (file)
index 0000000..24ba356
--- /dev/null
@@ -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 (file)
index 0000000..5566aff
--- /dev/null
@@ -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 (file)
index 0000000..6500d03
--- /dev/null
@@ -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 (file)
index 0000000..cd2222c
--- /dev/null
@@ -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 (file)
index 0000000..90d3e9d
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
diff --git a/azure/initialize.sh b/azure/initialize.sh
new file mode 100644 (file)
index 0000000..6128a8b
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
diff --git a/azure/manage.py b/azure/manage.py
new file mode 100644 (file)
index 0000000..4a98417
--- /dev/null
@@ -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 (file)
index 0000000..42740aa
--- /dev/null
@@ -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 (file)
index 0000000..aa98578
--- /dev/null
@@ -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 (file)
index 0000000..6883739
--- /dev/null
@@ -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 (file)
index 0000000..c81e04f
--- /dev/null
@@ -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 (file)
index 0000000..04e822a
--- /dev/null
@@ -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 (file)
index 0000000..d6559ab
--- /dev/null
@@ -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 (file)
index 0000000..831b951
--- /dev/null
@@ -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 (file)
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 (executable)
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 (file)
index 0000000..831b951
--- /dev/null
@@ -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