From eebea06b843c02dc2c456263bbc62c0d796c26ee Mon Sep 17 00:00:00 2001 From: Pablo Montes Moreno Date: Mon, 27 Feb 2017 12:33:10 +0100 Subject: [PATCH 01/16] Implemented basic test for RO. It runs basic RO functionality tests as well as scenario based tests. Tested with different vendors OpenStack installations Change-Id: I3217da96e1ae7e42942b6f5a4e127fab7ed25635 Signed-off-by: Pablo Montes Moreno --- openmanoclient.py | 18 +- ...scenario_additional_disk_empty_volume.yaml | 40 ++ .../vnfd_additional_disk_empty_volume.yaml | 63 ++ .../floating_ip/scenario_floating_ip.yaml | 40 ++ .../floating_ip/vnfd_floating_ip.yaml | 59 ++ .../scenario_additional_disk_based_image.yaml | 40 ++ .../vnfd_additional_disk_based_image.yaml | 64 ++ .../scenario_vnf_no_port_security.yaml | 40 ++ .../vnfd_no_port_security.yaml | 59 ++ .../scenario_simple-cloud-init.yaml | 34 + .../vnfd_linux-cloud-init.yaml | 67 ++ .../simple_linux/scenario_simple_linux.yaml | 34 + test/RO_tests/simple_linux/vnfd_linux.yaml | 42 ++ .../scenario_multi_vnfc.yaml | 34 + .../vnfd_linux_2VMs_v02.yaml | 104 +++ test/test_RO.py | 623 ++++++++++++++++++ 16 files changed, 1356 insertions(+), 5 deletions(-) create mode 100644 test/RO_tests/empy_volume/scenario_additional_disk_empty_volume.yaml create mode 100644 test/RO_tests/empy_volume/vnfd_additional_disk_empty_volume.yaml create mode 100644 test/RO_tests/floating_ip/scenario_floating_ip.yaml create mode 100644 test/RO_tests/floating_ip/vnfd_floating_ip.yaml create mode 100644 test/RO_tests/image_based_volume/scenario_additional_disk_based_image.yaml create mode 100644 test/RO_tests/image_based_volume/vnfd_additional_disk_based_image.yaml create mode 100644 test/RO_tests/no_port_security/scenario_vnf_no_port_security.yaml create mode 100644 test/RO_tests/no_port_security/vnfd_no_port_security.yaml create mode 100644 test/RO_tests/simple_cloud_init/scenario_simple-cloud-init.yaml create mode 100644 test/RO_tests/simple_cloud_init/vnfd_linux-cloud-init.yaml create mode 100644 test/RO_tests/simple_linux/scenario_simple_linux.yaml create mode 100644 test/RO_tests/simple_linux/vnfd_linux.yaml create mode 100644 test/RO_tests/simple_multi_vnfc/scenario_multi_vnfc.yaml create mode 100644 test/RO_tests/simple_multi_vnfc/vnfd_linux_2VMs_v02.yaml create mode 100755 test/test_RO.py diff --git a/openmanoclient.py b/openmanoclient.py index 0d9aa4ae..c11f747e 100644 --- a/openmanoclient.py +++ b/openmanoclient.py @@ -74,7 +74,7 @@ class openmanoclient(): self.datacenter_id = kwargs.get("datacenter_id") self.datacenter_name = kwargs.get("datacenter_name") self.datacenter = None - self.logger = logging.getLogger('manoclient') + self.logger = logging.getLogger(kwargs.get('logger','manoclient')) if kwargs.get("debug"): self.logger.setLevel(logging.DEBUG) @@ -424,7 +424,10 @@ class openmanoclient(): Return: Raises an exception on error, not found, found several, not free Obtain a dictionary with format {'result': text indicating deleted} ''' - return self._del_item("datacenters", uuid, name, all_tenants=True) + if not uuid: + # check that exist + uuid = self._get_item_uuid("datacenters", uuid, name, all_tenants=True) + return self._del_item("datacenters", uuid, name, all_tenants=None) def create_datacenter(self, descriptor=None, descriptor_format=None, name=None, vim_url=None, **kwargs): #, type="openvim", public=False, description=None): @@ -832,9 +835,14 @@ class openmanoclient(): Return: Raises an exception on error Obtain a dictionary with format {'tenant':{new_tenant_info}} ''' - if item not in ("tenants", "networks"): - raise OpenmanoBadParamsException("Unknown value for item '{}', must be 'tenants' or 'nets'".format(str(item))) - + if item not in ("tenants", "networks", "images"): + raise OpenmanoBadParamsException("Unknown value for item '{}', must be 'tenants', 'nets' or " + "images".format(str(item))) + + image_actions = ['list','get','show','delete'] + if item == "images" and action not in image_actions: + raise OpenmanoBadParamsException("Only available actions for item '{}' are {}\n" + "Requested action was '{}'".format(item, ', '.join(image_actions), action)) if all_tenants: tenant_text = "/any" else: diff --git a/test/RO_tests/empy_volume/scenario_additional_disk_empty_volume.yaml b/test/RO_tests/empy_volume/scenario_additional_disk_empty_volume.yaml new file mode 100644 index 00000000..61f5f38a --- /dev/null +++ b/test/RO_tests/empy_volume/scenario_additional_disk_empty_volume.yaml @@ -0,0 +1,40 @@ +## +# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U. +# This file is part of openmano +# All Rights Reserved. +# +# 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. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact with: nfvlabs@tid.es +## +--- +schema_version: 2 +scenario: + name: vnf_additional_disk_empty_volume + description: Just deploy vnf_2_disks + public: false # if available for other tenants + vnfs: + vnf_2_disks: # vnf name in the scenario + #identify an already openmano uploaded VNF either by vnf_id (uuid, prefered) or vnf_name + #vnf_id: 0c0dcc20-c5d5-11e6-a9fb-fa163e2ae06e #prefered id method + vnf_name: vnf_additional_disk_empty_volume #can fail if several vnfs matches this name + #graph: {"y":399,"x":332,"ifaces":{"left":[["xe0","d"],["xe1","d"]],"bottom":[["eth0","v"],["eth1","m"]]}} + networks: + mgmt: + # Connections based on external networks (datacenter nets) must include the external network in the list of nodes + type: bridge + external: true #this will be connected outside + interfaces: + - vnf_2_disks: mgmt0 + diff --git a/test/RO_tests/empy_volume/vnfd_additional_disk_empty_volume.yaml b/test/RO_tests/empy_volume/vnfd_additional_disk_empty_volume.yaml new file mode 100644 index 00000000..0e0f3eb3 --- /dev/null +++ b/test/RO_tests/empy_volume/vnfd_additional_disk_empty_volume.yaml @@ -0,0 +1,63 @@ +## +# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U. +# This file is part of openmano +# All Rights Reserved. +# +# 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. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact with: nfvlabs@tid.es +## +--- +vnf: + name: vnf_additional_disk_empty_volume + description: VNF with additional volume based on image + # class: parent # Optional. Used to organize VNFs + external-connections: + - name: mgmt0 + type: mgmt # "mgmt" (autoconnect to management net), "bridge", "data" + VNFC: TEMPLATE-VM # Virtual Machine this interface belongs to + local_iface_name: mgmt0 # interface name inside this Virtual Machine (must be defined in the VNFC section) + description: Management interface + VNFC: # Virtual machine array + - name: TEMPLATE-VM # name of Virtual Machine + description: TEMPLATE description + image name: ubuntu16.04 + # image metadata: {"bus":"ide", "os_type":"windows", "use_incremental": "no" } #Optional + # processor: #Optional + # model: Intel(R) Xeon(R) CPU E5-4620 0 @ 2.20GHz + # features: ["64b", "iommu", "lps", "tlbps", "hwsv", "dioc", "ht"] + # hypervisor: #Optional + # type: QEMU-kvm + # version: "10002|12001|2.6.32-358.el6.x86_64" + vcpus: 1 # Only for traditional cloud VMs. Number of virtual CPUs (oversubscription is allowed). + ram: 1000 # Only for traditional cloud VMs. Memory in MBytes (not from hugepages, oversubscription is allowed) + disk: 5 # disk size in GiB, by default 1 + #numas: + #- paired-threads: 5 # "cores", "paired-threads", "threads" + # paired-threads-id: [ [0,1], [2,3], [4,5], [6,7], [8,9] ] # By default follows incremental order + # memory: 14 # GBytes + # interfaces: [] + bridge-ifaces: + - name: mgmt0 + vpci: "0000:00:0a.0" # Optional. Virtual PCI address + bandwidth: 1 Mbps # Optional. Informative only + # mac_address: '20:33:45:56:77:46' #avoid this option if possible + # model: 'virtio' # ("virtio","e1000","ne2k_pci","pcnet","rtl8139") By default, it is automatically filled by libvirt + devices: # Optional, order determines device letter asignation (hda, hdb, ...) + - type: disk # "disk","cdrom","xml" + size: 1 + # image metadata: {"bus":"ide", "os_type":"windows", "use_incremental": "no" } + # vpci: "0000:00:03.0" # Optional, not for disk or cdrom + # Additional Virtual Machines would be included here + diff --git a/test/RO_tests/floating_ip/scenario_floating_ip.yaml b/test/RO_tests/floating_ip/scenario_floating_ip.yaml new file mode 100644 index 00000000..48470508 --- /dev/null +++ b/test/RO_tests/floating_ip/scenario_floating_ip.yaml @@ -0,0 +1,40 @@ +## +# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U. +# This file is part of openmano +# All Rights Reserved. +# +# 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. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact with: nfvlabs@tid.es +## +--- +schema_version: 2 +scenario: + name: vnf_floating_ip + description: vnf_floating_ip + public: false # if available for other tenants + vnfs: + vnf_floating_ip: # vnf name in the scenario + #identify an already openmano uploaded VNF either by vnf_id (uuid, prefered) or vnf_name + #vnf_id: 0c0dcc20-c5d5-11e6-a9fb-fa163e2ae06e #prefered id method + vnf_name: vnf_floating_ip #can fail if several vnfs matches this name + #graph: {"y":399,"x":332,"ifaces":{"left":[["xe0","d"],["xe1","d"]],"bottom":[["eth0","v"],["eth1","m"]]}} + networks: + mgmt: + # Connections based on external networks (datacenter nets) must include the external network in the list of nodes + type: bridge + external: true #this will be connected outside + interfaces: + - vnf_floating_ip: mgmt0 + diff --git a/test/RO_tests/floating_ip/vnfd_floating_ip.yaml b/test/RO_tests/floating_ip/vnfd_floating_ip.yaml new file mode 100644 index 00000000..47c73ba1 --- /dev/null +++ b/test/RO_tests/floating_ip/vnfd_floating_ip.yaml @@ -0,0 +1,59 @@ +## +# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U. +# This file is part of openmano +# All Rights Reserved. +# +# 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. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact with: nfvlabs@tid.es +## +--- +vnf: + name: vnf_floating_ip + description: VNF disabling port_security option in mgmt interface + # class: parent # Optional. Used to organize VNFs + external-connections: + - name: mgmt0 + type: mgmt # "mgmt" (autoconnect to management net), "bridge", "data" + VNFC: vnf_floating_ip # Virtual Machine this interface belongs to + local_iface_name: mgmt0 # interface name inside this Virtual Machine (must be defined in the VNFC section) + description: Management interface + VNFC: # Virtual machine array + - name: vnf_floating_ip # name of Virtual Machine + description: vnf_floating_ip + image name: ubuntu16.04 + # image metadata: {"bus":"ide", "os_type":"windows", "use_incremental": "no" } #Optional + # processor: #Optional + # model: Intel(R) Xeon(R) CPU E5-4620 0 @ 2.20GHz + # features: ["64b", "iommu", "lps", "tlbps", "hwsv", "dioc", "ht"] + # hypervisor: #Optional + # type: QEMU-kvm + # version: "10002|12001|2.6.32-358.el6.x86_64" + vcpus: 1 # Only for traditional cloud VMs. Number of virtual CPUs (oversubscription is allowed). + ram: 1000 # Only for traditional cloud VMs. Memory in MBytes (not from hugepages, oversubscription is allowed) + disk: 5 # disk size in GiB, by default 1 + #numas: + #- paired-threads: 5 # "cores", "paired-threads", "threads" + # paired-threads-id: [ [0,1], [2,3], [4,5], [6,7], [8,9] ] # By default follows incremental order + # memory: 14 # GBytes + # interfaces: [] + bridge-ifaces: + - name: mgmt0 + vpci: "0000:00:0a.0" # Optional. Virtual PCI address + bandwidth: 1 Mbps # Optional. Informative only + floating-ip: True + # mac_address: '20:33:45:56:77:46' #avoid this option if possible + # model: 'virtio' # ("virtio","e1000","ne2k_pci","pcnet","rtl8139") By default, it is automatically filled by libvirt + # Additional Virtual Machines would be included here + diff --git a/test/RO_tests/image_based_volume/scenario_additional_disk_based_image.yaml b/test/RO_tests/image_based_volume/scenario_additional_disk_based_image.yaml new file mode 100644 index 00000000..20f28cb4 --- /dev/null +++ b/test/RO_tests/image_based_volume/scenario_additional_disk_based_image.yaml @@ -0,0 +1,40 @@ +## +# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U. +# This file is part of openmano +# All Rights Reserved. +# +# 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. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact with: nfvlabs@tid.es +## +--- +schema_version: 2 +scenario: + name: vnf_additional_disk_based_image + description: Just deploy vnf_2_disks + public: false # if available for other tenants + vnfs: + vnf_2_disks: # vnf name in the scenario + #identify an already openmano uploaded VNF either by vnf_id (uuid, prefered) or vnf_name + #vnf_id: 0c0dcc20-c5d5-11e6-a9fb-fa163e2ae06e #prefered id method + vnf_name: vnf_additional_disk_based_image #can fail if several vnfs matches this name + #graph: {"y":399,"x":332,"ifaces":{"left":[["xe0","d"],["xe1","d"]],"bottom":[["eth0","v"],["eth1","m"]]}} + networks: + mgmt: + # Connections based on external networks (datacenter nets) must include the external network in the list of nodes + type: bridge + external: true #this will be connected outside + interfaces: + - vnf_2_disks: mgmt0 + diff --git a/test/RO_tests/image_based_volume/vnfd_additional_disk_based_image.yaml b/test/RO_tests/image_based_volume/vnfd_additional_disk_based_image.yaml new file mode 100644 index 00000000..57898318 --- /dev/null +++ b/test/RO_tests/image_based_volume/vnfd_additional_disk_based_image.yaml @@ -0,0 +1,64 @@ +## +# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U. +# This file is part of openmano +# All Rights Reserved. +# +# 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. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact with: nfvlabs@tid.es +## +--- +vnf: + name: vnf_additional_disk_based_image + description: VNF with additional volume based on image + # class: parent # Optional. Used to organize VNFs + external-connections: + - name: mgmt0 + type: mgmt # "mgmt" (autoconnect to management net), "bridge", "data" + VNFC: TEMPLATE-VM # Virtual Machine this interface belongs to + local_iface_name: mgmt0 # interface name inside this Virtual Machine (must be defined in the VNFC section) + description: Management interface + VNFC: # Virtual machine array + - name: TEMPLATE-VM # name of Virtual Machine + description: TEMPLATE description + image name: image_name.qcow2 + # image metadata: {"bus":"ide", "os_type":"windows", "use_incremental": "no" } #Optional + # processor: #Optional + # model: Intel(R) Xeon(R) CPU E5-4620 0 @ 2.20GHz + # features: ["64b", "iommu", "lps", "tlbps", "hwsv", "dioc", "ht"] + # hypervisor: #Optional + # type: QEMU-kvm + # version: "10002|12001|2.6.32-358.el6.x86_64" + vcpus: 1 # Only for traditional cloud VMs. Number of virtual CPUs (oversubscription is allowed). + ram: 1000 # Only for traditional cloud VMs. Memory in MBytes (not from hugepages, oversubscription is allowed) + disk: 5 # disk size in GiB, by default 1 + #numas: + #- paired-threads: 5 # "cores", "paired-threads", "threads" + # paired-threads-id: [ [0,1], [2,3], [4,5], [6,7], [8,9] ] # By default follows incremental order + # memory: 14 # GBytes + # interfaces: [] + bridge-ifaces: + - name: mgmt0 + vpci: "0000:00:0a.0" # Optional. Virtual PCI address + bandwidth: 1 Mbps # Optional. Informative only + # mac_address: '20:33:45:56:77:46' #avoid this option if possible + # model: 'virtio' # ("virtio","e1000","ne2k_pci","pcnet","rtl8139") By default, it is automatically filled by libvirt + devices: # Optional, order determines device letter asignation (hda, hdb, ...) + - type: disk # "disk","cdrom","xml" + image name: image_name.qcow2 + size: 2 + # image metadata: {"bus":"ide", "os_type":"windows", "use_incremental": "no" } + # vpci: "0000:00:03.0" # Optional, not for disk or cdrom + # Additional Virtual Machines would be included here + diff --git a/test/RO_tests/no_port_security/scenario_vnf_no_port_security.yaml b/test/RO_tests/no_port_security/scenario_vnf_no_port_security.yaml new file mode 100644 index 00000000..07c85abb --- /dev/null +++ b/test/RO_tests/no_port_security/scenario_vnf_no_port_security.yaml @@ -0,0 +1,40 @@ +## +# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U. +# This file is part of openmano +# All Rights Reserved. +# +# 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. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact with: nfvlabs@tid.es +## +--- +schema_version: 2 +scenario: + name: vnf_no_port_security + description: vnf_no_port_security + public: false # if available for other tenants + vnfs: + vnf_no_port_security: # vnf name in the scenario + #identify an already openmano uploaded VNF either by vnf_id (uuid, prefered) or vnf_name + #vnf_id: 0c0dcc20-c5d5-11e6-a9fb-fa163e2ae06e #prefered id method + vnf_name: vnf_no_port_security #can fail if several vnfs matches this name + #graph: {"y":399,"x":332,"ifaces":{"left":[["xe0","d"],["xe1","d"]],"bottom":[["eth0","v"],["eth1","m"]]}} + networks: + mgmt: + # Connections based on external networks (datacenter nets) must include the external network in the list of nodes + type: bridge + external: true #this will be connected outside + interfaces: + - vnf_no_port_security: mgmt0 + diff --git a/test/RO_tests/no_port_security/vnfd_no_port_security.yaml b/test/RO_tests/no_port_security/vnfd_no_port_security.yaml new file mode 100644 index 00000000..4815298a --- /dev/null +++ b/test/RO_tests/no_port_security/vnfd_no_port_security.yaml @@ -0,0 +1,59 @@ +## +# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U. +# This file is part of openmano +# All Rights Reserved. +# +# 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. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact with: nfvlabs@tid.es +## +--- +vnf: + name: vnf_no_port_security + description: VNF disabling port_security option in mgmt interface + # class: parent # Optional. Used to organize VNFs + external-connections: + - name: mgmt0 + type: mgmt # "mgmt" (autoconnect to management net), "bridge", "data" + VNFC: vnf_no_port_security # Virtual Machine this interface belongs to + local_iface_name: mgmt0 # interface name inside this Virtual Machine (must be defined in the VNFC section) + description: Management interface + VNFC: # Virtual machine array + - name: vnf_no_port_security # name of Virtual Machine + description: vnf_no_port_security + image name: ubuntu16.04 + # image metadata: {"bus":"ide", "os_type":"windows", "use_incremental": "no" } #Optional + # processor: #Optional + # model: Intel(R) Xeon(R) CPU E5-4620 0 @ 2.20GHz + # features: ["64b", "iommu", "lps", "tlbps", "hwsv", "dioc", "ht"] + # hypervisor: #Optional + # type: QEMU-kvm + # version: "10002|12001|2.6.32-358.el6.x86_64" + vcpus: 1 # Only for traditional cloud VMs. Number of virtual CPUs (oversubscription is allowed). + ram: 1000 # Only for traditional cloud VMs. Memory in MBytes (not from hugepages, oversubscription is allowed) + disk: 5 # disk size in GiB, by default 1 + #numas: + #- paired-threads: 5 # "cores", "paired-threads", "threads" + # paired-threads-id: [ [0,1], [2,3], [4,5], [6,7], [8,9] ] # By default follows incremental order + # memory: 14 # GBytes + # interfaces: [] + bridge-ifaces: + - name: mgmt0 + vpci: "0000:00:0a.0" # Optional. Virtual PCI address + bandwidth: 1 Mbps # Optional. Informative only + port-security: False + # mac_address: '20:33:45:56:77:46' #avoid this option if possible + # model: 'virtio' # ("virtio","e1000","ne2k_pci","pcnet","rtl8139") By default, it is automatically filled by libvirt + # Additional Virtual Machines would be included here + diff --git a/test/RO_tests/simple_cloud_init/scenario_simple-cloud-init.yaml b/test/RO_tests/simple_cloud_init/scenario_simple-cloud-init.yaml new file mode 100644 index 00000000..18ed3e9b --- /dev/null +++ b/test/RO_tests/simple_cloud_init/scenario_simple-cloud-init.yaml @@ -0,0 +1,34 @@ +## +# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U. +# This file is part of openmano +# All Rights Reserved. +# +# 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. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact with: nfvlabs@tid.es +## +--- +schema_version: 2 +scenario: + name: simple-cloud-init + description: Simple network scenario consisting of a single VNF connected to an external network + vnfs: + linux1: # vnf/net name in the scenario + vnf_name: linux-cloud-init # VNF name as introduced in OPENMANO DB + networks: + mgmt: # provide a name for this net or connection + external: true + interfaces: + - linux1: eth0 # Node and its interface + diff --git a/test/RO_tests/simple_cloud_init/vnfd_linux-cloud-init.yaml b/test/RO_tests/simple_cloud_init/vnfd_linux-cloud-init.yaml new file mode 100644 index 00000000..92e14fb2 --- /dev/null +++ b/test/RO_tests/simple_cloud_init/vnfd_linux-cloud-init.yaml @@ -0,0 +1,67 @@ +## +# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U. +# This file is part of openmano +# All Rights Reserved. +# +# 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. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact with: nfvlabs@tid.es +## + +--- +schema_version: "0.2" +vnf: + name: linux-cloud-init + description: Single-VM VNF with a traditional cloud VM based on generic Linux OS + external-connections: + - name: eth0 + type: mgmt + description: General purpose interface + VNFC: linux-VM + local_iface_name: eth0 + VNFC: + - name: linux-VM + description: Generic Linux Virtual Machine + #Copy the image to a compute path and edit this path + image name: ubuntu16.04 + vcpus: 1 # Only for traditional cloud VMs. Number of virtual CPUs (oversubscription is allowed). + ram: 2048 # Only for traditional cloud VMs. Memory in MBytes (not from hugepages, oversubscription is allowed) + disk: 20 + bridge-ifaces: + - name: eth0 + vpci: "0000:00:11.0" + numas: [] + boot-data: + key-pairs: + - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCy2w9GHMKKNkpCmrDK2ovc3XBYDETuLWwaW24S+feHhLBQiZlzh3gSQoINlA+2ycM9zYbxl4BGzEzpTVyCQFZv5PidG4m6ox7LR+KYkDcITMyjsVuQJKDvt6oZvRt6KbChcCi0n2JJD/oUiJbBFagDBlRslbaFI2mmqmhLlJ5TLDtmYxzBLpjuX4m4tv+pdmQVfg7DYHsoy0hllhjtcDlt1nn05WgWYRTu7mfQTWfVTavu+OjIX3e0WN6NW7yIBWZcE/Q9lC0II3W7PZDE3QaT55se4SPIO2JTdqsx6XGbekdG1n6adlduOI27sOU5m4doiyJ8554yVbuDB/z5lRBD alfonso.tiernosepulveda@telefonica.com + users: + - name: atierno + key-pairs: + - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCy2w9GHMKKNkpCmrDK2ovc3XBYDETuLWwaW24S+feHhLBQiZlzh3gSQoINlA+2ycM9zYbxl4BGzEzpTVyCQFZv5PidG4m6ox7LR+KYkDcITMyjsVuQJKDvt6oZvRt6KbChcCi0n2JJD/oUiJbBFagDBlRslbaFI2mmqmhLlJ5TLDtmYxzBLpjuX4m4tv+pdmQVfg7DYHsoy0hllhjtcDlt1nn05WgWYRTu7mfQTWfVTavu+OjIX3e0WN6NW7yIBWZcE/Q9lC0II3W7PZDE3QaT55se4SPIO2JTdqsx6XGbekdG1n6adlduOI27sOU5m4doiyJ8554yVbuDB/z5lRBD alfonso.tiernosepulveda@telefonica.com + boot-data-drive: true + config-files: + - content: | + auto enp0s3 + iface enp0s3 inet dhcp + dest: /etc/network/interfaces.d/enp0s3.cfg + permissions: '0644' + owner: root:root + - content: | + #! /bin/bash + ls -al >> /var/log/osm.log + dest: /etc/rc.local + permissions: '0755' + - content: "file content" + dest: /etc/test_delete + diff --git a/test/RO_tests/simple_linux/scenario_simple_linux.yaml b/test/RO_tests/simple_linux/scenario_simple_linux.yaml new file mode 100644 index 00000000..a7c2087b --- /dev/null +++ b/test/RO_tests/simple_linux/scenario_simple_linux.yaml @@ -0,0 +1,34 @@ +## +# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U. +# This file is part of openmano +# All Rights Reserved. +# +# 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. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact with: nfvlabs@tid.es +## +--- +schema_version: 2 +scenario: + name: simple + description: Simple network scenario consisting of a single VNF connected to an external network + vnfs: + linux1: # vnf/net name in the scenario + vnf_name: linux # VNF name as introduced in OPENMANO DB + networks: + mgmt: # provide a name for this net or connection + external: true + interfaces: + - linux1: eth0 # Node and its interface + diff --git a/test/RO_tests/simple_linux/vnfd_linux.yaml b/test/RO_tests/simple_linux/vnfd_linux.yaml new file mode 100644 index 00000000..47c84984 --- /dev/null +++ b/test/RO_tests/simple_linux/vnfd_linux.yaml @@ -0,0 +1,42 @@ +## +# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U. +# This file is part of openmano +# All Rights Reserved. +# +# 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. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact with: nfvlabs@tid.es +## +--- +vnf: + name: linux + description: Single-VM VNF with a traditional cloud VM based on generic Linux OS + external-connections: + - name: eth0 + type: bridge + VNFC: linux-VM + local_iface_name: eth0 + description: General purpose interface + VNFC: + - name: linux-VM + description: Generic Linux Virtual Machine + #Copy the image to a compute path and edit this path + image name: image_name.qcow2 + vcpus: 1 # Only for traditional cloud VMs. Number of virtual CPUs (oversubscription is allowed). + ram: 1024 # Only for traditional cloud VMs. Memory in MBytes (not from hugepages, oversubscription is allowed) + disk: 10 + bridge-ifaces: + - name: eth0 + vpci: "0000:00:11.0" + numas: [] diff --git a/test/RO_tests/simple_multi_vnfc/scenario_multi_vnfc.yaml b/test/RO_tests/simple_multi_vnfc/scenario_multi_vnfc.yaml new file mode 100644 index 00000000..8de79937 --- /dev/null +++ b/test/RO_tests/simple_multi_vnfc/scenario_multi_vnfc.yaml @@ -0,0 +1,34 @@ +## +# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U. +# This file is part of openmano +# All Rights Reserved. +# +# 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. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact with: nfvlabs@tid.es +## +--- +schema_version: 2 +scenario: + name: simple_multi_vnfc + description: Simple network scenario consisting of a multi VNFC VNF connected to an external network + vnfs: + linux1: # vnf/net name in the scenario + vnf_name: linux_2VMs_v02 # VNF name as introduced in OPENMANO DB + networks: + mgmt: # provide a name for this net or connection + external: true + interfaces: + - linux1: control0 # Node and its interface + diff --git a/test/RO_tests/simple_multi_vnfc/vnfd_linux_2VMs_v02.yaml b/test/RO_tests/simple_multi_vnfc/vnfd_linux_2VMs_v02.yaml new file mode 100644 index 00000000..3a09672e --- /dev/null +++ b/test/RO_tests/simple_multi_vnfc/vnfd_linux_2VMs_v02.yaml @@ -0,0 +1,104 @@ +## +# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U. +# This file is part of openmano +# All Rights Reserved. +# +# 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. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact with: nfvlabs@tid.es +## +--- +schema_version: "0.2" +vnf: + name: linux_2VMs_v02 + description: "Example of a linux VNF consisting of two VMs with one internal network" + # class: parent # Optional. Used to organize VNFs + internal-connections: + - name: internalnet + description: internalnet + type: e-lan + implementation: overlay + ip-profile: + ip-version: IPv4 + subnet-address: 192.168.1.0/24 + gateway-address: 192.168.1.1 + dns-address: 8.8.8.8 + dhcp: + enabled: true + start-address: 192.168.1.100 + count: 100 + elements: + - VNFC: linux_2VMs-VM1 + local_iface_name: xe0 + ip_address: 192.168.1.2 + - VNFC: linux_2VMs-VM2 + local_iface_name: xe0 + ip_address: 192.168.1.3 + external-connections: + - name: control0 + type: mgmt + VNFC: linux_2VMs-VM1 + local_iface_name: eth0 + description: control interface VM1 + - name: control1 + type: mgmt + VNFC: linux_2VMs-VM2 + local_iface_name: eth0 + description: control interface VM2 + - name: in + type: bridge + VNFC: linux_2VMs-VM1 + local_iface_name: xe1 + description: data interface input + - name: out + type: bridge + VNFC: linux_2VMs-VM2 + local_iface_name: xe1 + description: data interface output + VNFC: + - name: linux_2VMs-VM1 + description: "Linux VM1 with 4 CPUs, 2 GB RAM and 3 bridge interfaces" + #Copy the image to a compute path and edit this path + image name: TestVM + disk: 10 + vcpus: 4 + ram: 2048 + bridge-ifaces: + - name: eth0 + vpci: "0000:00:09.0" + bandwidth: 1 Mbps # Optional, informative only + - name: xe0 + vpci: "0000:00:11.0" + bandwidth: 1 Mbps + - name: xe1 + vpci: "0000:00:12.0" + bandwidth: 1 Mbps + - name: linux_2VMs-VM2 + description: "Linux VM2 with 2 CPUs, 2 GB RAM and 3 bridge interfaces" + #Copy the image to a compute path and edit this path + image name: TestVM + disk: 10 + vcpus: 2 + ram: 2048 + bridge-ifaces: + - name: eth0 + vpci: "0000:00:09.0" + bandwidth: 1 Mbps # Optional, informative only + - name: xe0 + vpci: "0000:00:11.0" + bandwidth: 1 Mbps + - name: xe1 + vpci: "0000:00:12.0" + bandwidth: 1 Mbps + diff --git a/test/test_RO.py b/test/test_RO.py new file mode 100755 index 00000000..d6667556 --- /dev/null +++ b/test/test_RO.py @@ -0,0 +1,623 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +## +# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U. +# This file is part of openmano +# All Rights Reserved. +# +# 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. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact with: nfvlabs@tid.es +## + +''' +Module for testing openmano functionality. It uses openmanoclient.py for invoking openmano +''' +__author__="Pablo Montes" +__date__ ="$16-Feb-2017 17:08:16$" +__version__="0.0.1" +version_date="Feb 2017" + +import logging +import imp +import os +from optparse import OptionParser +import unittest +import string +import inspect +import random +import traceback +import glob +import yaml +import sys +import time + +global test_number +global test_directory +global scenario_test_folder +global test_image_name +global management_network + +''' +IMPORTANT NOTE +All unittest classes for code based tests must have prefix 'test_' in order to be taken into account for tests +''' +class test_tenant_operations(unittest.TestCase): + test_index = 1 + tenant_name = None + test_text = None + + @classmethod + def setUpClass(cls): + logger.info("{}. {}".format(test_number, cls.__name__)) + + @classmethod + def tearDownClass(cls): + globals().__setitem__('test_number', globals().__getitem__('test_number') + 1) + + def tearDown(self): + exec_info = sys.exc_info() + if exec_info == (None, None, None): + logger.info(self.__class__.test_text+" -> TEST OK") + else: + logger.warning(self.__class__.test_text+" -> TEST NOK") + error_trace = traceback.format_exception(exec_info[0], exec_info[1], exec_info[2]) + msg = "" + for line in error_trace: + msg = msg + line + logger.critical("{}".format(msg)) + + def test_000_create_RO_tenant(self): + self.__class__.tenant_name = _get_random_string(20) + self.__class__.test_text = "{}.{}. TEST {}".format(test_number, self.__class__.test_index, + inspect.currentframe().f_code.co_name) + self.__class__.test_index += 1 + tenant = client.create_tenant(name=self.__class__.tenant_name, description=self.__class__.tenant_name) + logger.debug("{}".format(tenant)) + self.assertEqual(tenant.get('tenant', {}).get('name', ''), self.__class__.tenant_name) + + def test_010_list_RO_tenant(self): + self.__class__.test_text = "{}.{}. TEST {}".format(test_number, self.__class__.test_index, + inspect.currentframe().f_code.co_name) + self.__class__.test_index += 1 + tenant = client.get_tenant(name=self.__class__.tenant_name) + logger.debug("{}".format(tenant)) + self.assertEqual(tenant.get('tenant', {}).get('name', ''), self.__class__.tenant_name) + + def test_020_delete_RO_tenant(self): + self.__class__.test_text = "{}.{}. TEST {}".format(test_number, self.__class__.test_index, + inspect.currentframe().f_code.co_name) + self.__class__.test_index += 1 + tenant = client.delete_tenant(name=self.__class__.tenant_name) + logger.debug("{}".format(tenant)) + assert('deleted' in tenant.get('result',"")) + +class test_datacenter_operations(unittest.TestCase): + test_index = 1 + datacenter_name = None + test_text = None + + @classmethod + def setUpClass(cls): + logger.info("{}. {}".format(test_number, cls.__name__)) + + @classmethod + def tearDownClass(cls): + globals().__setitem__('test_number', globals().__getitem__('test_number') + 1) + + def tearDown(self): + exec_info = sys.exc_info() + if exec_info == (None, None, None): + logger.info(self.__class__.test_text+" -> TEST OK") + else: + logger.warning(self.__class__.test_text+" -> TEST NOK") + error_trace = traceback.format_exception(exec_info[0], exec_info[1], exec_info[2]) + msg = "" + for line in error_trace: + msg = msg + line + logger.critical("{}".format(msg)) + + def test_000_create_datacenter(self): + self.__class__.test_text = "{}.{}. TEST {}".format(test_number, self.__class__.test_index, + inspect.currentframe().f_code.co_name) + self.__class__.datacenter_name = _get_random_string(20) + self.__class__.test_index += 1 + self.datacenter = client.create_datacenter(name=self.__class__.datacenter_name, vim_url="http://fakeurl/fake") + logger.debug("{}".format(self.datacenter)) + self.assertEqual (self.datacenter.get('datacenter', {}).get('name',''), self.__class__.datacenter_name) + + def test_010_list_datacenter(self): + self.__class__.test_text = "{}.{}. TEST {}".format(test_number, self.__class__.test_index, + inspect.currentframe().f_code.co_name) + + self.__class__.test_index += 1 + self.datacenter = client.get_datacenter(all_tenants=True, name=self.__class__.datacenter_name) + logger.debug("{}".format(self.datacenter)) + self.assertEqual (self.datacenter.get('datacenter', {}).get('name', ''), self.__class__.datacenter_name) + + def test_020_attach_datacenter(self): + self.__class__.test_text = "{}.{}. TEST {}".format(test_number, self.__class__.test_index, + inspect.currentframe().f_code.co_name) + + self.__class__.test_index += 1 + self.datacenter = client.attach_datacenter(name=self.__class__.datacenter_name, vim_tenant_name='fake') + logger.debug("{}".format(self.datacenter)) + assert ('vim_tenants' in self.datacenter.get('datacenter', {})) + + def test_030_list_attached_datacenter(self): + self.__class__.test_text = "{}.{}. TEST {}".format(test_number, self.__class__.test_index, + inspect.currentframe().f_code.co_name) + + self.__class__.test_index += 1 + self.datacenter = client.get_datacenter(all_tenants=False, name=self.__class__.datacenter_name) + logger.debug("{}".format(self.datacenter)) + self.assertEqual (self.datacenter.get('datacenter', {}).get('name', ''), self.__class__.datacenter_name) + + def test_040_detach_datacenter(self): + self.__class__.test_text = "{}.{}. TEST {}".format(test_number, self.__class__.test_index, + inspect.currentframe().f_code.co_name) + + self.__class__.test_index += 1 + self.datacenter = client.detach_datacenter(name=self.__class__.datacenter_name) + logger.debug("{}".format(self.datacenter)) + assert ('detached' in self.datacenter.get('result', "")) + + def test_050_delete_datacenter(self): + self.__class__.test_text = "{}.{}. TEST {}".format(test_number, self.__class__.test_index, + inspect.currentframe().f_code.co_name) + + self.__class__.test_index += 1 + self.datacenter = client.delete_datacenter(name=self.__class__.datacenter_name) + logger.debug("{}".format(self.datacenter)) + assert('deleted' in self.datacenter.get('result',"")) + +class test_VIM_network_operations(unittest.TestCase): + test_index = 1 + vim_network_name = None + test_text = None + vim_network_uuid = None + + @classmethod + def setUpClass(cls): + logger.info("{}. {}".format(test_number, cls.__name__)) + + @classmethod + def tearDownClass(cls): + globals().__setitem__('test_number', globals().__getitem__('test_number') + 1) + + def tearDown(self): + exec_info = sys.exc_info() + if exec_info == (None, None, None): + logger.info(self.__class__.test_text + " -> TEST OK") + else: + logger.warning(self.__class__.test_text + " -> TEST NOK") + error_trace = traceback.format_exception(exec_info[0], exec_info[1], exec_info[2]) + msg = "" + for line in error_trace: + msg = msg + line + logger.critical("{}".format(msg)) + + def test_000_create_VIM_network(self): + self.__class__.test_text = "{}.{}. TEST {}".format(test_number, self.__class__.test_index, + inspect.currentframe().f_code.co_name) + self.__class__.vim_network_name = _get_random_string(20) + self.__class__.test_index += 1 + network = client.vim_action("create", "networks", name=self.__class__.vim_network_name) + logger.debug("{}".format(network)) + self.__class__.vim_network_uuid = network["network"]["id"] + self.assertEqual(network.get('network', {}).get('name', ''), self.__class__.vim_network_name) + + def test_010_list_VIM_networks(self): + self.__class__.test_text = "{}.{}. TEST {}".format(test_number, self.__class__.test_index, + inspect.currentframe().f_code.co_name) + self.__class__.test_index += 1 + networks = client.vim_action("list", "networks") + logger.debug("{}".format(networks)) + + def test_020_get_VIM_network_by_uuid(self): + self.__class__.test_text = "{}.{}. TEST {}".format(test_number, self.__class__.test_index, + inspect.currentframe().f_code.co_name) + + self.__class__.test_index += 1 + network = client.vim_action("show", "networks", uuid=self.__class__.vim_network_uuid) + logger.debug("{}".format(network)) + self.assertEqual(network.get('network', {}).get('name', ''), self.__class__.vim_network_name) + + def test_030_delete_VIM_network_by_uuid(self): + self.__class__.test_text = "{}.{}. TEST {}".format(test_number, self.__class__.test_index, + inspect.currentframe().f_code.co_name) + + self.__class__.test_index += 1 + network = client.vim_action("delete", "networks", uuid=self.__class__.vim_network_uuid) + logger.debug("{}".format(network)) + assert ('deleted' in network.get('result', "")) + +class test_VIM_image_operations(unittest.TestCase): + test_index = 1 + test_text = None + + @classmethod + def setUpClass(cls): + logger.info("{}. {}".format(test_number, cls.__name__)) + + @classmethod + def tearDownClass(cls): + globals().__setitem__('test_number', globals().__getitem__('test_number') + 1) + + def tearDown(self): + exec_info = sys.exc_info() + if exec_info == (None, None, None): + logger.info(self.__class__.test_text + " -> TEST OK") + else: + logger.warning(self.__class__.test_text + " -> TEST NOK") + error_trace = traceback.format_exception(exec_info[0], exec_info[1], exec_info[2]) + msg = "" + for line in error_trace: + msg = msg + line + logger.critical("{}".format(msg)) + + def test_000_list_VIM_images(self): + self.__class__.test_text = "{}.{}. TEST {}".format(test_number, self.__class__.test_index, + inspect.currentframe().f_code.co_name) + self.__class__.test_index += 1 + images = client.vim_action("list", "images") + logger.debug("{}".format(images)) + +''' +The following is a non critical test that will fail most of the times. +In case of OpenStack datacenter these tests will only success if RO has access to the admin endpoint +This test will only be executed in case it is specifically requested by the user +''' +class test_VIM_tenant_operations(unittest.TestCase): + test_index = 1 + vim_tenant_name = None + test_text = None + vim_tenant_uuid = None + + @classmethod + def setUpClass(cls): + logger.info("{}. {}".format(test_number, cls.__name__)) + logger.warning("In case of OpenStack datacenter these tests will only success " + "if RO has access to the admin endpoint") + + @classmethod + def tearDownClass(cls): + globals().__setitem__('test_number', globals().__getitem__('test_number') + 1) + + def tearDown(self): + exec_info = sys.exc_info() + if exec_info == (None, None, None): + logger.info(self.__class__.test_text + " -> TEST OK") + else: + logger.warning(self.__class__.test_text + " -> TEST NOK") + error_trace = traceback.format_exception(exec_info[0], exec_info[1], exec_info[2]) + msg = "" + for line in error_trace: + msg = msg + line + logger.critical("{}".format(msg)) + + def test_000_create_VIM_tenant(self): + self.__class__.test_text = "{}.{}. TEST {}".format(test_number, self.__class__.test_index, + inspect.currentframe().f_code.co_name) + self.__class__.vim_tenant_name = _get_random_string(20) + self.__class__.test_index += 1 + tenant = client.vim_action("create", "tenants", name=self.__class__.vim_tenant_name) + logger.debug("{}".format(tenant)) + self.__class__.vim_tenant_uuid = tenant["tenant"]["id"] + self.assertEqual(tenant.get('tenant', {}).get('name', ''), self.__class__.vim_tenant_name) + + def test_010_list_VIM_tenants(self): + self.__class__.test_text = "{}.{}. TEST {}".format(test_number, self.__class__.test_index, + inspect.currentframe().f_code.co_name) + self.__class__.test_index += 1 + tenants = client.vim_action("list", "tenants") + logger.debug("{}".format(tenants)) + + def test_020_get_VIM_tenant_by_uuid(self): + self.__class__.test_text = "{}.{}. TEST {}".format(test_number, self.__class__.test_index, + inspect.currentframe().f_code.co_name) + + self.__class__.test_index += 1 + tenant = client.vim_action("show", "tenants", uuid=self.__class__.vim_tenant_uuid) + logger.debug("{}".format(tenant)) + self.assertEqual(tenant.get('tenant', {}).get('name', ''), self.__class__.vim_tenant_name) + + def test_030_delete_VIM_tenant_by_uuid(self): + self.__class__.test_text = "{}.{}. TEST {}".format(test_number, self.__class__.test_index, + inspect.currentframe().f_code.co_name) + + self.__class__.test_index += 1 + tenant = client.vim_action("delete", "tenants", uuid=self.__class__.vim_tenant_uuid) + logger.debug("{}".format(tenant)) + assert ('deleted' in tenant.get('result', "")) + +''' +IMPORTANT NOTE +The following unittest class does not have the 'test_' on purpose. This test is the one used for the +scenario based tests. +''' +class descriptor_based_scenario_test(unittest.TestCase): + test_index = 0 + test_text = None + scenario_test_path = None + scenario_uuid = None + instance_scenario_uuid = None + to_delete_list = [] + + @classmethod + def setUpClass(cls): + cls.test_index = 1 + cls.to_delete_list = [] + cls.scenario_test_path = test_directory + '/' + scenario_test_folder + logger.info("{}. {} {}".format(test_number, cls.__name__, scenario_test_folder)) + + @classmethod + def tearDownClass(cls): + globals().__setitem__('test_number', globals().__getitem__('test_number') + 1) + + def tearDown(self): + exec_info = sys.exc_info() + if exec_info == (None, None, None): + logger.info(self.__class__.test_text + " -> TEST OK") + else: + logger.warning(self.__class__.test_text + " -> TEST NOK") + error_trace = traceback.format_exception(exec_info[0], exec_info[1], exec_info[2]) + msg = "" + for line in error_trace: + msg = msg + line + logger.critical("{}".format(msg)) + + + def test_000_load_scenario(self): + self.__class__.test_text = "{}.{}. TEST {} {}".format(test_number, self.__class__.test_index, + inspect.currentframe().f_code.co_name, + scenario_test_folder) + self.__class__.test_index += 1 + vnfd_files = glob.glob(self.__class__.scenario_test_path+'/vnfd_*.yaml') + scenario_file = glob.glob(self.__class__.scenario_test_path + '/scenario_*.yaml') + if len(vnfd_files) == 0 or len(scenario_file) > 1: + raise Exception('Test '+scenario_test_folder+' not valid. It must contain an scenario file and at least one' + ' vnfd file') + + #load all vnfd + for vnfd in vnfd_files: + with open(vnfd, 'r') as stream: + vnf_descriptor = yaml.load(stream) + + vnfc_list = vnf_descriptor['vnf']['VNFC'] + for vnfc in vnfc_list: + vnfc['image name'] = test_image_name + devices = vnfc.get('devices',[]) + for device in devices: + if device['type'] == 'disk' and 'image name' in device: + device['image name'] = test_image_name + + logger.debug("VNF descriptor: {}".format(vnf_descriptor)) + vnf = client.create_vnf(descriptor=vnf_descriptor) + logger.debug(vnf) + self.__class__.to_delete_list.insert(0, {"item": "vnf", "function": client.delete_vnf, + "params": {"uuid": vnf['vnf']['uuid']}}) + + #load the scenario definition + with open(scenario_file[0], 'r') as stream: + scenario_descriptor = yaml.load(stream) + networks = scenario_descriptor['scenario']['networks'] + networks[management_network] = networks.pop('mgmt') + logger.debug("Scenario descriptor: {}".format(scenario_descriptor)) + scenario = client.create_scenario(descriptor=scenario_descriptor) + logger.debug(scenario) + self.__class__.to_delete_list.insert(0,{"item": "scenario", "function": client.delete_scenario, + "params":{"uuid": scenario['scenario']['uuid']} }) + self.__class__.scenario_uuid = scenario['scenario']['uuid'] + + def test_010_instantiate_scenario(self): + self.__class__.test_text = "{}.{}. TEST {} {}".format(test_number, self.__class__.test_index, + inspect.currentframe().f_code.co_name, + scenario_test_folder) + self.__class__.test_index += 1 + + instance = client.create_instance(scenario_id=self.__class__.scenario_uuid, name=self.__class__.test_text) + logger.debug(instance) + self.__class__.to_delete_list.insert(0, {"item": "instance", "function": client.delete_instance, + "params": {"uuid": instance['uuid']}}) + + def test_020_clean_deployment(self): + self.__class__.test_text = "{}.{}. TEST {} {}".format(test_number, self.__class__.test_index, + inspect.currentframe().f_code.co_name, + scenario_test_folder) + self.__class__.test_index += 1 + #At the moment if you delete an scenario right after creating it, in openstack datacenters + #sometimes scenario ports get orphaned. This sleep is just a dirty workaround + time.sleep(5) + for item in self.__class__.to_delete_list: + response = item["function"](**item["params"]) + logger.debug(response) + +def _get_random_string(maxLength): + '''generates a string with random characters string.letters and string.digits + with a random length up to maxLength characters. If maxLength is <15 it will be changed automatically to 15 + ''' + prefix = 'testing_' + min_string = 15 + minLength = min_string - len(prefix) + if maxLength < min_string: maxLength = min_string + maxLength -= len(prefix) + length = random.randint(minLength,maxLength) + return 'testing_'+"".join([random.choice(string.letters+string.digits) for i in xrange(length)]) + +if __name__=="__main__": + sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + import openmanoclient + + parser = OptionParser() + + #Optional arguments + parser.add_option("-v",'--version', help='Show current version', dest='version', action="store_true", default=False) + parser.add_option('--debug', help='Set logs to debug level', dest='debug', action="store_true", default=False) + parser.add_option('--failed', help='Set logs to show only failed tests. --debug disables this option', + dest='failed', action="store_true", default=False) + parser.add_option('-u', '--url', dest='endpoint_url', help='Set the openmano server url. By default ' + 'http://localhost:9090/openmano', + default='http://localhost:9090/openmano') + default_logger_file = os.path.dirname(__file__)+'/'+os.path.splitext(os.path.basename(__file__))[0]+'.log' + parser.add_option('--logger_file', dest='logger_file', help='Set the logger file. By default '+default_logger_file, + default=default_logger_file) + parser.add_option('--list-tests', help='List all available tests', dest='list-tests', action="store_true", + default=False) + parser.add_option('--test', '--tests', help='Specify the tests to run', dest='tests', default=None) + + #Mandatory arguments + parser.add_option("-t", '--tenant', dest='tenant_name', help='MANDATORY. Set the tenant name to test') + parser.add_option('-d', '--datacenter', dest='datacenter_name', help='MANDATORY, Set the datacenter name to test') + parser.add_option("-i", '--image-name', dest='image-name', help='MANDATORY. Image name of an Ubuntu 16.04 image ' + 'that will be used for testing available in the ' + 'datacenter.') + parser.add_option("-n", '--mgmt-net-name', dest='mgmt-net', help='MANDATORY. Set the tenant name to test') + + (options, args) = parser.parse_args() + + # default logger level is INFO. Options --debug and --failed override this, being --debug prioritary + logger_level = 'INFO' + if options.__dict__['debug']: + logger_level = 'DEBUG' + elif options.__dict__['failed']: + logger_level = 'WARNING' + logger_name = os.path.basename(__file__) + logger = logging.getLogger(logger_name) + logger.setLevel(logger_level) + + # Configure a logging handler to store in a logging file + fileHandler = logging.FileHandler(options.__dict__['logger_file']) + formatter_fileHandler = logging.Formatter('%(asctime)s %(name)s %(levelname)s: %(message)s') + fileHandler.setFormatter(formatter_fileHandler) + logger.addHandler(fileHandler) + + # Configure a handler to print to stdout + consoleHandler = logging.StreamHandler(sys.stdout) + formatter_consoleHandler = logging.Formatter('%(message)s') + consoleHandler.setFormatter(formatter_consoleHandler) + logger.addHandler(consoleHandler) + + logger.debug('Program started with the following arguments: ' + str(options.__dict__)) + + #If version is required print it and exit + if options.__dict__['version']: + logger.info("{}".format((sys.argv[0], __version__+" version", version_date))) + logger.info ("(c) Copyright Telefonica") + sys.exit(0) + + test_directory = os.path.dirname(__file__) + "/RO_tests" + test_directory_content = os.listdir(test_directory) + clsmembers = inspect.getmembers(sys.modules[__name__], inspect.isclass) + + # If only want to obtain a tests list print it and exit + if options.__dict__['list-tests']: + tests_names = [] + for cls in clsmembers: + if cls[0].startswith('test_'): + tests_names.append(cls[0]) + + msg = "The code based tests are:\n\t" + ', '.join(sorted(tests_names))+'\n'+\ + "The descriptor based tests are:\n\t"+ ', '.join(sorted(test_directory_content))+'\n'+\ + "NOTE: The test test_VIM_tenant_operations will fail in case the used datacenter is type OpenStack " \ + "unless RO has access to the admin endpoint. Therefore this test is excluded by default" + + logger.info(msg) + sys.exit(0) + + #Make sure required arguments are present + required = "tenant_name datacenter_name image-name mgmt-net".split() + error = False + for r in required: + if options.__dict__[r] is None: + print "ERROR: parameter "+r+" is required" + error = True + if error: + parser.print_help() + sys.exit(1) + + # set test image name and management network + test_image_name = options.__dict__['image-name'] + management_network = options.__dict__['mgmt-net'] + + #Create the list of tests to be run + descriptor_based_tests = [] + code_based_tests = [] + if options.__dict__['tests'] != None: + tests = sorted(options.__dict__['tests'].split(',')) + for test in tests: + matches_code_based_tests = [item for item in clsmembers if item[0] == test] + if test in test_directory_content: + descriptor_based_tests.append(test) + elif len(matches_code_based_tests) > 0: + code_based_tests.append(matches_code_based_tests[0][1]) + else: + logger.critical("Test {} is not among the possible ones".format(test)) + sys.exit(1) + else: + #include all tests + descriptor_based_tests = test_directory_content + for cls in clsmembers: + #We exclude 'test_VIM_tenant_operations' unless it is specifically requested by the user + if cls[0].startswith('test_') and cls[0] != 'test_VIM_tenant_operations': + code_based_tests.append(cls[1]) + + logger.debug("descriptor_based_tests to be executed: {}".format(descriptor_based_tests)) + logger.debug("code_based_tests to be executed: {}".format(code_based_tests)) + + # import openmanoclient from relative path + client = openmanoclient.openmanoclient( + endpoint_url=options.__dict__['endpoint_url'], + tenant_name=options.__dict__['tenant_name'], + datacenter_name = options.__dict__['datacenter_name'], + debug = options.__dict__['debug'], logger = logger_name) + + # TextTestRunner stream is set to /dev/null in order to avoid the method to directly print the result of tests. + # This is handled in the tests using logging. + stream = open('/dev/null', 'w') + test_number=1 + executed = 0 + failed = 0 + + #Run code based tests + basic_tests_suite = unittest.TestSuite() + for test in code_based_tests: + basic_tests_suite.addTest(unittest.makeSuite(test)) + result = unittest.TextTestRunner(stream=stream).run(basic_tests_suite) + executed += result.testsRun + failed += len(result.failures) + len(result.errors) + if len(result.failures) > 0: + logger.debug("failures : {}".format(result.failures)) + if len(result.errors) > 0: + logger.debug("errors : {}".format(result.errors)) + + # Additionally to the previous tests, scenario based tests will be executed. + # This scenario based tests are defined as directories inside the directory defined in 'test_directory' + for test in descriptor_based_tests: + scenario_test_folder = test + test_suite = unittest.TestSuite() + test_suite.addTest(unittest.makeSuite(descriptor_based_scenario_test)) + result = unittest.TextTestRunner(stream=stream).run(test_suite) + executed += result.testsRun + failed += len(result.failures) + len(result.errors) + if len(result.failures) > 0: + logger.debug("failures : {}".format(result.failures)) + if len(result.errors) > 0: + logger.debug("errors : {}".format(result.errors)) + + #Log summary + logger.warning("Total number of tests: {}; Total number of failures/errors: {}".format(executed, failed)) + + sys.exit(0) \ No newline at end of file -- 2.25.1 From a7d34d04639c12d4260518c6e92f3f5701f5e150 Mon Sep 17 00:00:00 2001 From: tierno Date: Thu, 23 Feb 2017 14:42:07 +0100 Subject: [PATCH 02/16] vimconn.py: Better documentation. Unify new_network method for all vimconns. Add new parameter persistem_info at constructor. New method check_vim_connectivity. Change-Id: Ie2c7900fe12fd5ae01ef2bb4891b79a0f9173285 Signed-off-by: tierno --- nfvo.py | 3 +- vimconn.py | 349 +++++++++++++++++++++++++++------------------ vimconn_openvim.py | 6 +- 3 files changed, 213 insertions(+), 145 deletions(-) diff --git a/nfvo.py b/nfvo.py index 4565f00a..80a606ba 100644 --- a/nfvo.py +++ b/nfvo.py @@ -2855,7 +2855,8 @@ def vim_action_create(mydb, tenant_id, datacenter, item, descriptor): net_type = net.pop("type", "bridge") net_public = net.pop("shared", False) net_ipprofile = net.pop("ip_profile", None) - content = myvim.new_network(net_name, net_type, net_ipprofile, shared=net_public, **net) + net_vlan = net.pop("vlan", None) + content = myvim.new_network(net_name, net_type, net_ipprofile, shared=net_public, vlan=net_vlan) #, **net) elif item=="tenants": tenant = descriptor["tenant"] content = myvim.new_tenant(tenant["name"], tenant.get("description")) diff --git a/vimconn.py b/vimconn.py index ad06dc8b..c1721cdb 100644 --- a/vimconn.py +++ b/vimconn.py @@ -21,10 +21,10 @@ # contact with: nfvlabs@tid.es ## -''' +""" vimconn implement an Abstract class for the vim connector plugins with the definition of the method to be implemented. -''' +""" __author__="Alfonso Tierno" __date__ ="$16-oct-2015 11:09:29$" @@ -42,57 +42,69 @@ HTTP_Service_Unavailable = 503 HTTP_Internal_Server_Error = 500 class vimconnException(Exception): - '''Common and base class Exception for all vimconnector exceptions''' + """Common and base class Exception for all vimconnector exceptions""" def __init__(self, message, http_code=HTTP_Bad_Request): Exception.__init__(self, message) self.http_code = http_code class vimconnConnectionException(vimconnException): - '''Connectivity error with the VIM''' + """Connectivity error with the VIM""" def __init__(self, message, http_code=HTTP_Service_Unavailable): vimconnException.__init__(self, message, http_code) class vimconnUnexpectedResponse(vimconnException): - '''Get an wrong response from VIM''' + """Get an wrong response from VIM""" def __init__(self, message, http_code=HTTP_Service_Unavailable): vimconnException.__init__(self, message, http_code) class vimconnAuthException(vimconnException): - '''Invalid credentials or authorization to perform this action over the VIM''' + """Invalid credentials or authorization to perform this action over the VIM""" def __init__(self, message, http_code=HTTP_Unauthorized): vimconnException.__init__(self, message, http_code) class vimconnNotFoundException(vimconnException): - '''The item is not found at VIM''' + """The item is not found at VIM""" def __init__(self, message, http_code=HTTP_Not_Found): vimconnException.__init__(self, message, http_code) class vimconnConflictException(vimconnException): - '''There is a conflict, e.g. more item found than one''' + """There is a conflict, e.g. more item found than one""" def __init__(self, message, http_code=HTTP_Conflict): vimconnException.__init__(self, message, http_code) +class vimconnNotSupportedException(vimconnException): + """The request is not supported by connector""" + def __init__(self, message, http_code=HTTP_Service_Unavailable): + vimconnException.__init__(self, message, http_code) + class vimconnNotImplemented(vimconnException): - '''The method is not implemented by the connected''' + """The method is not implemented by the connected""" def __init__(self, message, http_code=HTTP_Not_Implemented): vimconnException.__init__(self, message, http_code) class vimconnector(): - '''Abstract base class for all the VIM connector plugins + """Abstract base class for all the VIM connector plugins These plugins must implement a vimconnector class derived from this - and all these methods - ''' - def __init__(self, uuid, name, tenant_id, tenant_name, url, url_admin=None, user=None, passwd=None, log_level=None, config={}): + and all these privated methods + """ + def __init__(self, uuid, name, tenant_id, tenant_name, url, url_admin=None, user=None, passwd=None, log_level=None, + config={}, persitent_info={}): """Constructor of VIM - "uuid": id asigned to this VIM - "name": name assigned to this VIM, can be used for logging - "tenant_id", tenant_name: VIM tenant to be used - "url_admin": optional, url used for administrative tasks - "user", "passwd": credentials of the VIM user - "log_level": if must use a different log_level than the general one - "config": dictionary with extra VIM information. This contains a consolidate version of general VIM config at create - and particular VIM config at attach - Returns can raise an exception if some check is done and fails + Params: + 'uuid': id asigned to this VIM + 'name': name assigned to this VIM, can be used for logging + 'tenant_id', 'tenant_name': (only one of them is mandatory) VIM tenant to be used + 'url_admin': (optional), url used for administrative tasks + 'user', 'passwd': credentials of the VIM user + 'log_level': provider if it should use a different log_level than the general one + 'config': dictionary with extra VIM information. This contains a consolidate version of general VIM config + at creation and particular VIM config at teh attachment + 'persistent_info': dict where the class can store information that will be available among class + destroy/creation cycles. This info is unique per VIM/credential. At first call it will contain an + empty dict. Useful to store login/tokens information for speed up communication + + Returns: Raise an exception is some needed parameter is missing, but it must not do any connectivity + check against the VIM """ self.id = uuid self.name = name @@ -151,6 +163,12 @@ class vimconnector(): else: raise KeyError("Invalid key '%s'" %str(index)) + def check_vim_connectivity(self): + """Checks VIM can be reached and user credentials are ok. + Returns None if success or raised vimconnConnectionException, vimconnAuthException, ... + """ + raise vimconnNotImplemented( "Should have implemented this" ) + def new_tenant(self,tenant_name,tenant_description): """Adds a new tenant to VIM with this name and description, this is done using admin_url if provided "tenant_name": string max lenght 64 @@ -167,90 +185,118 @@ class vimconnector(): raise vimconnNotImplemented( "Should have implemented this" ) def get_tenant_list(self, filter_dict={}): - '''Obtain tenants of VIM + """Obtain tenants of VIM filter_dict dictionary that can contain the following keys: name: filter by tenant name id: filter by tenant uuid/id Returns the tenant list of dictionaries, and empty list if no tenant match all the filers: [{'name':', 'id':', ...}, ...] - ''' + """ raise vimconnNotImplemented( "Should have implemented this" ) - def new_network(self,net_name, net_type, ip_profile=None, shared=False): + def new_network(self, net_name, net_type, ip_profile=None, shared=False, vlan=None): """Adds a tenant network to VIM - net_name is the name - net_type can be 'bridge','data'.'ptp'. TODO: this need to be revised - ip_profile is a dict containing the IP parameters of the network - "ip-version": {"type":"string", "enum":["IPv4","IPv6"]}, - "subnet-address": ip_prefix_schema, - "gateway-address": ip_schema, - "dns-address": ip_schema, - "dhcp": dhcp_schema - - shared is a boolean - Returns the network identifier on success or raises and exeption on failure + Params: + 'net_name': name of the network + 'net_type': one of: + 'bridge': overlay isolated network + 'data': underlay E-LAN network for Passthrough and SRIOV interfaces + 'ptp': underlay E-LINE network for Passthrough and SRIOV interfaces. + 'ip_profile': is a dict containing the IP parameters of the network (Currently only IPv4 is implemented) + 'ip-version': can be one of ["IPv4","IPv6"] + 'subnet-address': ip_prefix_schema, that is X.X.X.X/Y + 'gateway-address': (Optional) ip_schema, that is X.X.X.X + 'dns-address': (Optional) ip_schema, + 'dhcp': (Optional) dict containing + 'enabled': {"type": "boolean"}, + 'start-address': ip_schema, first IP to grant + 'count': number of IPs to grant. + 'shared': if this network can be seen/use by other tenants/organization + 'vlan': in case of a data or ptp net_type, the intended vlan tag to be used for the network + Returns the network identifier on success or raises and exception on failure """ raise vimconnNotImplemented( "Should have implemented this" ) def get_network_list(self, filter_dict={}): - '''Obtain tenant networks of VIM - Filter_dict can be: - name: network name - id: network uuid - shared: boolean - tenant_id: tenant - admin_state_up: boolean - status: 'ACTIVE' - Returns the network list of dictionaries: - [{}, ...] - List can be empty - ''' + """Obtain tenant networks of VIM + Params: + 'filter_dict' (optional) contains entries to return only networks that matches ALL entries: + name: string => returns only networks with this name + id: string => returns networks with this VIM id, this imply returns one network at most + shared: boolean >= returns only networks that are (or are not) shared + tenant_id: sting => returns only networks that belong to this tenant/project + ,#(not used yet) admin_state_up: boolean => returns only networks that are (or are not) in admin state active + #(not used yet) status: 'ACTIVE','ERROR',... => filter networks that are on this status + Returns the network list of dictionaries. each dictionary contains: + 'id': (mandatory) VIM network id + 'name': (mandatory) VIM network name + 'status': (mandatory) can be 'ACTIVE', 'INACTIVE', 'DOWN', 'BUILD', 'ERROR', 'VIM_ERROR', 'OTHER' + 'error_msg': (optional) text that explains the ERROR status + other VIM specific fields: (optional) whenever possible using the same naming of filter_dict param + List can be empty if no network map the filter_dict. Raise an exception only upon VIM connectivity, + authorization, or some other unspecific error + """ raise vimconnNotImplemented( "Should have implemented this" ) def get_network(self, net_id): - '''Obtain network details of net_id VIM network' - Return a dict with the fields at filter_dict (see get_network_list) plus some VIM specific>}, ...]''' + """Obtain network details from the 'net_id' VIM network + Return a dict that contains: + 'id': (mandatory) VIM network id, that is, net_id + 'name': (mandatory) VIM network name + 'status': (mandatory) can be 'ACTIVE', 'INACTIVE', 'DOWN', 'BUILD', 'ERROR', 'VIM_ERROR', 'OTHER' + 'error_msg': (optional) text that explains the ERROR status + other VIM specific fields: (optional) whenever possible using the same naming of filter_dict param + Raises an exception upon error or when network is not found + """ raise vimconnNotImplemented( "Should have implemented this" ) def delete_network(self, net_id): - '''Deletes a tenant network from VIM, provide the network id. - Returns the network identifier or raise an exception''' + """Deletes a tenant network from VIM + Returns the network identifier or raises an exception upon error or when network is not found + """ raise vimconnNotImplemented( "Should have implemented this" ) def refresh_nets_status(self, net_list): - '''Get the status of the networks - Params: the list of network identifiers - Returns a dictionary with: - net_id: #VIM id of this network - status: #Mandatory. Text with one of: - # DELETED (not found at vim) - # VIM_ERROR (Cannot connect to VIM, VIM response error, ...) - # OTHER (Vim reported other status not understood) - # ERROR (VIM indicates an ERROR status) - # ACTIVE, INACTIVE, DOWN (admin down), - # BUILD (on building process) - # - error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR - vim_info: #Text with plain information obtained from vim (yaml.safe_dump) - - ''' + """Get the status of the networks + Params: + 'net_list': a list with the VIM network id to be get the status + Returns a dictionary with: + 'net_id': #VIM id of this network + status: #Mandatory. Text with one of: + # DELETED (not found at vim) + # VIM_ERROR (Cannot connect to VIM, authentication problems, VIM response error, ...) + # OTHER (Vim reported other status not understood) + # ERROR (VIM indicates an ERROR status) + # ACTIVE, INACTIVE, DOWN (admin down), + # BUILD (on building process) + error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR + vim_info: #Text with plain information obtained from vim (yaml.safe_dump) + 'net_id2': ... + """ raise vimconnNotImplemented( "Should have implemented this" ) def get_flavor(self, flavor_id): - '''Obtain flavor details from the VIM - Returns the flavor dict details {'id':<>, 'name':<>, other vim specific } #TODO to concrete - ''' + """Obtain flavor details from the VIM + Returns the flavor dict details {'id':<>, 'name':<>, other vim specific } + Raises an exception upon error or if not found + """ raise vimconnNotImplemented( "Should have implemented this" ) def get_flavor_id_from_data(self, flavor_dict): """Obtain flavor id that match the flavor description - Returns the flavor_id or raises a vimconnNotFoundException + Params: + 'flavor_dict': dictionary that contains: + 'disk': main hard disk in GB + 'ram': meomry in MB + 'vcpus': number of virtual cpus + #TODO: complete parameters for EPA + Returns the flavor_id or raises a vimconnNotFoundException """ raise vimconnNotImplemented( "Should have implemented this" ) def new_flavor(self, flavor_data): - '''Adds a tenant flavor to VIM + """Adds a tenant flavor to VIM flavor_data contains a dictionary with information, keys: name: flavor name ram: memory (cloud type) in MBytes @@ -266,30 +312,24 @@ class vimconnector(): vpci: requested virtual PCI address disk: disk size is_public: - - - #TODO to concrete - Returns the flavor identifier''' + Returns the flavor identifier""" raise vimconnNotImplemented( "Should have implemented this" ) def delete_flavor(self, flavor_id): - '''Deletes a tenant flavor from VIM identify by its id - Returns the used id or raise an exception''' + """Deletes a tenant flavor from VIM identify by its id + Returns the used id or raise an exception""" raise vimconnNotImplemented( "Should have implemented this" ) - def new_image(self,image_dict): - ''' - Adds a tenant image to VIM - Returns: - 200, image-id if the image is created - <0, message if there is an error - ''' + def new_image(self, image_dict): + """ Adds a tenant image to VIM + Returns the image id or raises an exception if failed + """ raise vimconnNotImplemented( "Should have implemented this" ) def delete_image(self, image_id): - '''Deletes a tenant image from VIM''' - '''Returns the HTTP response code and a message indicating details of the success or fail''' + """Deletes a tenant image from VIM + Returns the image_id if image is deleted or raises an exception on error""" raise vimconnNotImplemented( "Should have implemented this" ) def get_image_id_from_path(self, path): @@ -299,7 +339,7 @@ class vimconnector(): raise vimconnNotImplemented( "Should have implemented this" ) def get_image_list(self, filter_dict={}): - '''Obtain tenant images from VIM + """Obtain tenant images from VIM Filter_dict can be: name: image name id: image uuid @@ -308,44 +348,66 @@ class vimconnector(): Returns the image list of dictionaries: [{}, ...] List can be empty - ''' + """ raise vimconnNotImplemented( "Should have implemented this" ) - def new_vminstance(self,name,description,start,image_id,flavor_id,net_list,cloud_config=None,disk_list=None): - '''Adds a VM instance to VIM + def new_vminstance(self, name, description, start, image_id, flavor_id, net_list, cloud_config=None, + disk_list=None): + """Adds a VM instance to VIM Params: - start: indicates if VM must start or boot in pause mode. Ignored - image_id,flavor_id: image and flavor uuid - net_list: list of interfaces, each one is a dictionary with: - name: - net_id: network uuid to connect - vpci: virtual vcpi to assign - model: interface model, virtio, e2000, ... - mac_address: - use: 'data', 'bridge', 'mgmt' - type: 'virtual', 'PF', 'VF', 'VFnotShared' - vim_id: filled/added by this function - cloud_config: can be a text script to be passed directly to cloud-init, - or an object to inject users and ssh keys with format: - key-pairs: [] list of keys to install to the default user - users: [{ name, key-pairs: []}] list of users to add with their key-pair - #TODO ip, security groups - Returns >=0, the instance identifier - <0, error_text - ''' + 'start': (boolean) indicates if VM must start or created in pause mode. + 'image_id','flavor_id': image and flavor VIM id to use for the VM + 'net_list': list of interfaces, each one is a dictionary with: + 'name': (optional) name for the interface. + 'net_id': VIM network id where this interface must be connect to. Mandatory for type==virtual + 'vpci': (optional) virtual vPCI address to assign at the VM. Can be ignored depending on VIM capabilities + 'model': (optional and only have sense for type==virtual) interface model: virtio, e2000, ... + 'mac_address': (optional) mac address to assign to this interface + #TODO: CHECK if an optional 'vlan' parameter is needed for VIMs when type if VF and net_id is not provided, + the VLAN tag to be used. In case net_id is provided, the internal network vlan is used for tagging VF + 'type': (mandatory) can be one of: + 'virtual', in this case always connected to a network of type 'net_type=bridge' + 'PF' (passthrough): depending on VIM capabilities it can be connected to a data/ptp network ot it + can created unconnected + 'VF' (SRIOV with VLAN tag): same as PF for network connectivity. + 'VFnotShared'(SRIOV without VLAN tag) same as PF for network connectivity. VF where no other VFs + are allocated on the same physical NIC + 'bw': (optional) only for PF/VF/VFnotShared. Minimal Bandwidth required for the interface in GBPS + After execution the method will add the key: + 'vim_id': must be filled/added by this method with the VIM identifier generated by the VIM for this + interface. 'net_list' is modified + 'cloud_config': (optional) dictionary with: + 'key-pairs': (optional) list of strings with the public key to be inserted to the default user + 'users': (optional) list of users to be inserted, each item is a dict with: + 'name': (mandatory) user name, + 'key-pairs': (optional) list of strings with the public key to be inserted to the user + 'user-data': (optional) string is a text script to be passed directly to cloud-init + 'config-files': (optional). List of files to be transferred. Each item is a dict with: + 'dest': (mandatory) string with the destination absolute path + 'encoding': (optional, by default text). Can be one of: + 'b64', 'base64', 'gz', 'gz+b64', 'gz+base64', 'gzip+b64', 'gzip+base64' + 'content' (mandatory): string with the content of the file + 'permissions': (optional) string with file permissions, typically octal notation '0644' + 'owner': (optional) file owner, string with the format 'owner:group' + 'boot-data-drive': boolean to indicate if user-data must be passed using a boot drive (hard disk) + 'disk_list': (optional) list with additional disks to the VM. Each item is a dict with: + 'image_id': (optional). VIM id of an existing image. If not provided an empty disk must be mounted + 'size': (mandatory) string with the size of the disk in GB + Returns the instance identifier or raises an exception on error + """ raise vimconnNotImplemented( "Should have implemented this" ) def get_vminstance(self,vm_id): - '''Returns the VM instance information from VIM''' + """Returns the VM instance information from VIM""" raise vimconnNotImplemented( "Should have implemented this" ) def delete_vminstance(self, vm_id): - '''Removes a VM instance from VIM''' - '''Returns the instance identifier''' + """Removes a VM instance from VIM + Returns the instance identifier""" raise vimconnNotImplemented( "Should have implemented this" ) def refresh_vms_status(self, vm_list): - '''Get the status of the virtual machines and their interfaces/ports + """Get the status of the virtual machines and their interfaces/ports Params: the list of VM identifiers Returns a dictionary with: vm_id: #VIM id of this Virtual Machine @@ -355,27 +417,30 @@ class vimconnector(): # OTHER (Vim reported other status not understood) # ERROR (VIM indicates an ERROR status) # ACTIVE, PAUSED, SUSPENDED, INACTIVE (not running), - # CREATING (on building process), ERROR + # BUILD (on building process), ERROR # ACTIVE:NoMgmtIP (Active but any of its interface has an IP address # error_msg: #Text with VIM error message, if any. Or the VIM connection ERROR vim_info: #Text with plain information obtained from vim (yaml.safe_dump) - interfaces: - - vim_info: #Text with plain information obtained from vim (yaml.safe_dump) + interfaces: list with interface info. Each item a dictionary with: + vim_info: #Text with plain information obtained from vim (yaml.safe_dump) mac_address: #Text format XX:XX:XX:XX:XX:XX - vim_net_id: #network id where this interface is connected + vim_net_id: #network id where this interface is connected, if provided at creation vim_interface_id: #interface/port VIM id ip_address: #null, or text with IPv4, IPv6 address - ''' + physical_compute: #identification of compute node where PF,VF interface is allocated + physical_pci: #PCI address of the NIC that hosts the PF,VF + physical_vlan: #physical VLAN used for VF + """ raise vimconnNotImplemented( "Should have implemented this" ) def action_vminstance(self, vm_id, action_dict): - '''Send and action over a VM instance from VIM - Returns the vm_id if the action was successfully sent to the VIM''' + """Send and action over a VM instance from VIM + Returns the vm_id if the action was successfully sent to the VIM""" raise vimconnNotImplemented( "Should have implemented this" ) - def get_vminstance_console(self,vm_id, console_type="vnc"): - ''' + def get_vminstance_console(self, vm_id, console_type="vnc"): + """ Get a console for the virtual machine Params: vm_id: uuid of the VM @@ -387,53 +452,53 @@ class vimconnector(): server: usually ip address port: the http, ssh, ... port suffix: extra text, e.g. the http path and query string - ''' + """ raise vimconnNotImplemented( "Should have implemented this" ) #NOT USED METHODS in current version def host_vim2gui(self, host, server_dict): - '''Transform host dictionary from VIM format to GUI format, + """Transform host dictionary from VIM format to GUI format, and append to the server_dict - ''' + """ raise vimconnNotImplemented( "Should have implemented this" ) def get_hosts_info(self): - '''Get the information of deployed hosts - Returns the hosts content''' + """Get the information of deployed hosts + Returns the hosts content""" raise vimconnNotImplemented( "Should have implemented this" ) def get_hosts(self, vim_tenant): - '''Get the hosts and deployed instances - Returns the hosts content''' + """Get the hosts and deployed instances + Returns the hosts content""" raise vimconnNotImplemented( "Should have implemented this" ) def get_processor_rankings(self): - '''Get the processor rankings in the VIM database''' + """Get the processor rankings in the VIM database""" raise vimconnNotImplemented( "Should have implemented this" ) def new_host(self, host_data): - '''Adds a new host to VIM''' - '''Returns status code of the VIM response''' + """Adds a new host to VIM""" + """Returns status code of the VIM response""" raise vimconnNotImplemented( "Should have implemented this" ) def new_external_port(self, port_data): - '''Adds a external port to VIM''' - '''Returns the port identifier''' + """Adds a external port to VIM""" + """Returns the port identifier""" raise vimconnNotImplemented( "Should have implemented this" ) def new_external_network(self,net_name,net_type): - '''Adds a external network to VIM (shared)''' - '''Returns the network identifier''' + """Adds a external network to VIM (shared)""" + """Returns the network identifier""" raise vimconnNotImplemented( "Should have implemented this" ) def connect_port_network(self, port_id, network_id, admin=False): - '''Connects a external port to a network''' - '''Returns status code of the VIM response''' + """Connects a external port to a network""" + """Returns status code of the VIM response""" raise vimconnNotImplemented( "Should have implemented this" ) def new_vminstancefromJSON(self, vm_data): - '''Adds a VM instance to VIM''' - '''Returns the instance identifier''' + """Adds a VM instance to VIM""" + """Returns the instance identifier""" raise vimconnNotImplemented( "Should have implemented this" ) diff --git a/vimconn_openvim.py b/vimconn_openvim.py index fb59eccc..9d6748a9 100644 --- a/vimconn_openvim.py +++ b/vimconn_openvim.py @@ -481,7 +481,7 @@ class vimconnector(vimconn.vimconnector): except requests.exceptions.RequestException as e: self._format_request_exception(e) - def new_network(self,net_name, net_type, ip_profile=None, shared=False, **vim_specific): + def new_network(self,net_name, net_type, ip_profile=None, shared=False, vlan=None): #, **vim_specific): '''Adds a tenant network to VIM''' '''Returns the network identifier''' try: @@ -489,7 +489,9 @@ class vimconnector(vimconn.vimconnector): if net_type=="bridge": net_type="bridge_data" payload_req = {"name": net_name, "type": net_type, "tenant_id": self.tenant, "shared": shared} - payload_req.update(vim_specific) + if vlan: + payload_req["provider:vlan"] = vlan + # payload_req.update(vim_specific) url = self.url+'/networks' self.logger.info("Adding a new network POST: %s DATA: %s", url, str(payload_req)) vim_response = requests.post(url, headers = self.headers_req, data=json.dumps({"network": payload_req}) ) -- 2.25.1 From b3d367468fb470724f4846bed34af7859e136524 Mon Sep 17 00:00:00 2001 From: tierno Date: Fri, 3 Mar 2017 23:51:05 +0100 Subject: [PATCH 03/16] new persistent_info param at vimconn class Task management at vim_thread Change-Id: Ica8ef928811a973ac8edc669692ad2c867d9518a Signed-off-by: tierno --- nfvo.py | 298 +++++++++++++++++++++++++++++++++++-------- vim_thread.py | 205 +++++++++++++++++++++-------- vimconn.py | 2 + vimconn_openstack.py | 6 +- vimconn_openvim.py | 4 +- vimconn_vmware.py | 3 +- 6 files changed, 405 insertions(+), 113 deletions(-) diff --git a/nfvo.py b/nfvo.py index 80a606ba..14ec54f1 100644 --- a/nfvo.py +++ b/nfvo.py @@ -39,6 +39,9 @@ import vimconn import logging import collections from db_base import db_base_Exception +import nfvo_db +from threading import Lock +from time import time global global_config global vimconn_imported @@ -49,7 +52,13 @@ default_volume_size = '5' #size in GB vimconn_imported = {} # dictionary with VIM type as key, loaded module as value vim_threads = {"running":{}, "deleting": {}, "names": []} # threads running for attached-VIMs +vim_persistent_info = {} logger = logging.getLogger('openmano.nfvo') +task_lock = Lock() +task_dict = {} +last_task_id = 0.0 +db=None +db_lock=Lock() class NfvoException(Exception): def __init__(self, message, http_code): @@ -57,12 +66,35 @@ class NfvoException(Exception): Exception.__init__(self, message) +def get_task_id(): + global last_task_id + task_id = time() + if task_id <= last_task_id: + task_id = last_task_id + 0.000001 + last_task_id = task_id + return "TASK.{:.6f}".format(task_id) + + +def new_task(name, params, store=True, depends=None): + task_id = get_task_id() + task = {"status": "enqueued", "id": task_id, "name": name, "params": params} + if depends: + task["depends"] = depends + if store: + task_dict[task_id] = task + return task + + +def is_task_id(id): + return True if id[:5] == "TASK." else False + + def get_non_used_vim_name(datacenter_name, datacenter_id, tenant_name, tenant_id): name = datacenter_name[:16] if name not in vim_threads["names"]: vim_threads["names"].append(name) return name - name = datatacenter_name[:16] + "." + tenant_name[:16] + name = datacenter_name[:16] + "." + tenant_name[:16] if name not in vim_threads["names"]: vim_threads["names"].append(name) return name @@ -72,6 +104,9 @@ def get_non_used_vim_name(datacenter_name, datacenter_id, tenant_name, tenant_id def start_service(mydb): + global db, global_config + db = nfvo_db.nfvo_db() + db.connect(global_config['db_host'], global_config['db_user'], global_config['db_passwd'], global_config['db_name']) from_= 'tenants_datacenters as td join datacenters as d on td.datacenter_id=d.uuid join datacenter_tenants as dt on td.datacenter_tenant_id=dt.uuid' select_ = ('type','d.config as config','d.uuid as datacenter_id', 'vim_url', 'vim_url_admin', 'd.name as datacenter_name', 'dt.uuid as datacenter_tenant_id','dt.vim_tenant_name as vim_tenant_name','dt.vim_tenant_id as vim_tenant_id', @@ -95,33 +130,37 @@ def start_service(mydb): if module_info and module_info[0]: file.close(module_info[0]) raise NfvoException("Unknown vim type '{}'. Can not open file '{}.py'; {}: {}".format( - vim["type"], module, type(e).__name__, str(e)), HTTP_Bad_Request) + vim["type"], module, type(e).__name__, str(e)), HTTP_Bad_Request) + thread_id = vim["datacenter_id"] + "." + vim['nfvo_tenant_id'] + vim_persistent_info[thread_id] = {} try: #if not tenant: # return -HTTP_Bad_Request, "You must provide a valid tenant name or uuid for VIM %s" % ( vim["type"]) myvim = vimconn_imported[ vim["type"] ].vimconnector( - uuid=vim['datacenter_id'], name=vim['datacenter_name'], - tenant_id=vim['vim_tenant_id'], tenant_name=vim['vim_tenant_name'], - url=vim['vim_url'], url_admin=vim['vim_url_admin'], - user=vim['user'], passwd=vim['passwd'], - config=extra - ) + uuid=vim['datacenter_id'], name=vim['datacenter_name'], + tenant_id=vim['vim_tenant_id'], tenant_name=vim['vim_tenant_name'], + url=vim['vim_url'], url_admin=vim['vim_url_admin'], + user=vim['user'], passwd=vim['passwd'], + config=extra, persistent_info=vim_persistent_info[thread_id] + ) except Exception as e: raise NfvoException("Error at VIM {}; {}: {}".format(vim["type"], type(e).__name__, str(e)), HTTP_Internal_Server_Error) thread_name = get_non_used_vim_name(vim['datacenter_name'], vim['vim_tenant_id'], vim['vim_tenant_name'], vim['vim_tenant_id']) - new_thread = vim_thread.vim_thread(myvim, thread_name) + new_thread = vim_thread.vim_thread(myvim, task_lock, thread_name, vim['datacenter_name'], + vim.get('datacenter_tenant_id'), db=db, db_lock=db_lock) new_thread.start() - thread_id = vim["datacenter_id"] + "-" + vim['nfvo_tenant_id'] vim_threads["running"][thread_id] = new_thread except db_base_Exception as e: raise NfvoException(str(e) + " at nfvo.get_vim", e.http_code) + def stop_service(): for thread_id,thread in vim_threads["running"].items(): - thread.insert_task("exit") + thread.insert_task(new_task("exit", None, store=False)) vim_threads["deleting"][thread_id] = thread - vim_threads["running"]={} + vim_threads["running"] = {} + def get_flavorlist(mydb, vnf_id, nfvo_tenant=None): '''Obtain flavorList @@ -144,6 +183,7 @@ def get_flavorlist(mydb, vnf_id, nfvo_tenant=None): flavorList.append(flavor['flavor_id']) return flavorList + def get_imagelist(mydb, vnf_id, nfvo_tenant=None): '''Obtain imageList return result, content: @@ -162,6 +202,7 @@ def get_imagelist(mydb, vnf_id, nfvo_tenant=None): imageList.append(image['image_id']) return imageList + def get_vim(mydb, nfvo_tenant=None, datacenter_id=None, datacenter_name=None, datacenter_tenant_id=None, vim_tenant=None, vim_tenant_name=None, vim_user=None, vim_passwd=None): '''Obtain a dictionary of VIM (datacenter) classes with some of the input parameters @@ -207,14 +248,22 @@ def get_vim(mydb, nfvo_tenant=None, datacenter_id=None, datacenter_name=None, da vim["type"], module, type(e).__name__, str(e)), HTTP_Bad_Request) try: + if 'nfvo_tenant_id' in vim: + thread_id = vim["datacenter_id"] + "." + vim['nfvo_tenant_id'] + if thread_id not in vim_persistent_info: + vim_persistent_info[thread_id] = {} + persistent_info = vim_persistent_info[thread_id] + else: + persistent_info = {} #if not tenant: # return -HTTP_Bad_Request, "You must provide a valid tenant name or uuid for VIM %s" % ( vim["type"]) vim_dict[ vim['datacenter_id'] ] = vimconn_imported[ vim["type"] ].vimconnector( uuid=vim['datacenter_id'], name=vim['datacenter_name'], - tenant_id=vim.get('vim_tenant_id',vim_tenant), tenant_name=vim.get('vim_tenant_name',vim_tenant_name), + tenant_id=vim.get('vim_tenant_id',vim_tenant), + tenant_name=vim.get('vim_tenant_name',vim_tenant_name), url=vim['vim_url'], url_admin=vim['vim_url_admin'], user=vim.get('user',vim_user), passwd=vim.get('passwd',vim_passwd), - config=extra + config=extra, persistent_info=persistent_info ) except Exception as e: raise NfvoException("Error at VIM {}; {}: {}".format(vim["type"], type(e).__name__, str(e)), HTTP_Internal_Server_Error) @@ -222,6 +271,7 @@ def get_vim(mydb, nfvo_tenant=None, datacenter_id=None, datacenter_name=None, da except db_base_Exception as e: raise NfvoException(str(e) + " at nfvo.get_vim", e.http_code) + def rollback(mydb, vims, rollback_list): undeleted_items=[] #delete things by reverse order @@ -262,6 +312,7 @@ def rollback(mydb, vims, rollback_list): else: return False," Rollback fails to delete: " + str(undeleted_items) + def check_vnf_descriptor(vnf_descriptor, vnf_descriptor_version=1): global global_config #create a dictionary with vnfc-name: vnfc:interface-list key:values pairs @@ -458,6 +509,7 @@ def create_or_use_image(mydb, vims, image_dict, rollback_list, only_create_at_vi return image_vim_id if only_create_at_vim else image_mano_id + def create_or_use_flavor(mydb, vims, flavor_dict, rollback_list, only_create_at_vim=False, return_on_error = None): temp_flavor_dict= {'disk':flavor_dict.get('disk',1), 'ram':flavor_dict.get('ram'), @@ -610,6 +662,7 @@ def create_or_use_flavor(mydb, vims, flavor_dict, rollback_list, only_create_at_ return flavor_vim_id if only_create_at_vim else flavor_mano_id + def new_vnf(mydb, tenant_id, vnf_descriptor): global global_config @@ -744,6 +797,7 @@ def new_vnf(mydb, tenant_id, vnf_descriptor): #logger.error("start_scenario %s", error_text) raise NfvoException(error_text, e.http_code) + def new_vnf_v02(mydb, tenant_id, vnf_descriptor): global global_config @@ -877,6 +931,7 @@ def new_vnf_v02(mydb, tenant_id, vnf_descriptor): #logger.error("start_scenario %s", error_text) raise NfvoException(error_text, e.http_code) + def get_vnf_id(mydb, tenant_id, vnf_id): #check valid tenant_id check_tenant(mydb, tenant_id) @@ -1034,6 +1089,7 @@ def delete_vnf(mydb,tenant_id,vnf_id,datacenter=None,vim_tenant=None): #if undeletedItems: # return "delete_vnf. Undeleted: %s" %(undeletedItems) + def get_hosts_info(mydb, nfvo_tenant_id, datacenter_name=None): result, vims = get_vim(mydb, nfvo_tenant_id, None, datacenter_name) if result < 0: @@ -1047,6 +1103,7 @@ def get_hosts_info(mydb, nfvo_tenant_id, datacenter_name=None): topology = {'name':myvim['name'] , 'servers': servers} return result, topology + def get_hosts(mydb, nfvo_tenant_id): vims = get_vim(mydb, nfvo_tenant_id) if len(vims) == 0: @@ -1082,6 +1139,7 @@ def get_hosts(mydb, nfvo_tenant_id): except vimconn.vimconnException as e: raise NfvoException("Not possible to get_host_list from VIM: {}".format(str(e)), e.http_code) + def new_scenario(mydb, tenant_id, topo): # result, vims = get_vim(mydb, tenant_id) @@ -1365,6 +1423,7 @@ def new_scenario(mydb, tenant_id, topo): return c + def new_scenario_v02(mydb, tenant_id, scenario_dict, version): """ This creates a new scenario for version 0.2 and 0.3""" scenario = scenario_dict["scenario"] @@ -1488,12 +1547,14 @@ def new_scenario_v02(mydb, tenant_id, scenario_dict, version): scenario_id = mydb.new_scenario(scenario) return scenario_id + def edit_scenario(mydb, tenant_id, scenario_id, data): data["uuid"] = scenario_id data["tenant_id"] = tenant_id c = mydb.edit_scenario( data ) return c + def start_scenario(mydb, tenant_id, scenario_id, instance_scenario_name, instance_scenario_description, datacenter=None,vim_tenant=None, startvms=True): #print "Checking that nfvo_tenant_id exists and getting the VIM URI and the VIM tenant_id" datacenter_id, myvim = get_datacenter_by_name_uuid(mydb, tenant_id, datacenter, vim_tenant=vim_tenant) @@ -1696,6 +1757,7 @@ def start_scenario(mydb, tenant_id, scenario_id, instance_scenario_name, instanc #logger.error("start_scenario %s", error_text) raise NfvoException(error_text, e.http_code) + def unify_cloud_config(cloud_config_preserve, cloud_config): ''' join the cloud config information into cloud_config_preserve. In case of conflict cloud_config_preserve preserves @@ -1772,6 +1834,33 @@ def unify_cloud_config(cloud_config_preserve, cloud_config): return new_cloud_config +def get_vim_thread(tenant_id, datacenter_id_name=None, datacenter_tenant_id=None): + datacenter_id = None + datacenter_name = None + thread = None + if datacenter_id_name: + if utils.check_valid_uuid(datacenter_id_name): + datacenter_id = datacenter_id_name + else: + datacenter_name = datacenter_id_name + if datacenter_id: + thread = vim_threads["running"].get(datacenter_id + "." + tenant_id) + else: + for k, v in vim_threads["running"].items(): + datacenter_tenant = k.split(".") + if datacenter_tenant[0] == datacenter_id and datacenter_tenant[1] == tenant_id: + if thread: + raise NfvoException("More than one datacenters found, try to identify with uuid", HTTP_Conflict) + thread = v + elif not datacenter_id and datacenter_tenant[1] == tenant_id: + if thread.datacenter_name == datacenter_name: + if thread: + raise NfvoException("More than one datacenters found, try to identify with uuid", HTTP_Conflict) + thread = v + if not thread: + raise NfvoException("datacenter '{}' not found".format(str(datacenter_id_name)), HTTP_Not_Found) + return thread + def get_datacenter_by_name_uuid(mydb, tenant_id, datacenter_id_name=None, **extra_filter): datacenter_id = None @@ -1789,6 +1878,7 @@ def get_datacenter_by_name_uuid(mydb, tenant_id, datacenter_id_name=None, **extr raise NfvoException("More than one datacenters found, try to identify with uuid", HTTP_Conflict) return vims.keys()[0], vims.values()[0] + def update(d, u): '''Takes dict d and updates it with the values in dict u.''' '''It merges all depth levels''' @@ -1800,17 +1890,20 @@ def update(d, u): d[k] = u[k] return d + def create_instance(mydb, tenant_id, instance_dict): - #print "Checking that nfvo_tenant_id exists and getting the VIM URI and the VIM tenant_id" - #logger.debug("Creating instance...") + # print "Checking that nfvo_tenant_id exists and getting the VIM URI and the VIM tenant_id" + # logger.debug("Creating instance...") scenario = instance_dict["scenario"] #find main datacenter myvims = {} + myvim_threads = {} datacenter2tenant = {} datacenter = instance_dict.get("datacenter") default_datacenter_id, vim = get_datacenter_by_name_uuid(mydb, tenant_id, datacenter) myvims[default_datacenter_id] = vim + myvim_threads[default_datacenter_id] = get_vim_thread(tenant_id, default_datacenter_id) datacenter2tenant[default_datacenter_id] = vim['config']['datacenter_tenant_id'] #myvim_tenant = myvim['tenant_id'] # default_datacenter_name = vim['name'] @@ -1831,10 +1924,11 @@ def create_instance(mydb, tenant_id, instance_dict): logger.debug("Creating instance from scenario-dict:\n%s", yaml.safe_dump(scenarioDict, indent=4, default_flow_style=False)) #TODO remove instance_name = instance_dict["name"] instance_description = instance_dict.get("description") + instance_tasks={} try: - #0 check correct parameters + # 0 check correct parameters for net_name, net_instance_desc in instance_dict.get("networks",{}).iteritems(): - found=False + found = False for scenario_net in scenarioDict['nets']: if net_name == scenario_net["name"]: found = True @@ -1850,13 +1944,14 @@ def create_instance(mydb, tenant_id, instance_dict): #Add this datacenter to myvims d, v = get_datacenter_by_name_uuid(mydb, tenant_id, site["datacenter"]) myvims[d] = v + myvim_threads[d] = get_vim_thread(tenant_id, site["datacenter"]) datacenter2tenant[d] = v['config']['datacenter_tenant_id'] - site["datacenter"] = d #change name to id + site["datacenter"] = d #change name to id else: if site_without_datacenter_field: raise NfvoException("Found more than one entries without datacenter field at instance:networks:{}:sites".format(net_name), HTTP_Bad_Request) site_without_datacenter_field = True - site["datacenter"] = default_datacenter_id #change name to id + site["datacenter"] = default_datacenter_id #change name to id for vnf_name, vnf_instance_desc in instance_dict.get("vnfs",{}).iteritems(): found=False @@ -1867,10 +1962,11 @@ def create_instance(mydb, tenant_id, instance_dict): if not found: raise NfvoException("Invalid vnf name '{}' at instance:vnfs".format(vnf_instance_desc), HTTP_Bad_Request) if "datacenter" in vnf_instance_desc: - #Add this datacenter to myvims + # Add this datacenter to myvims if vnf_instance_desc["datacenter"] not in myvims: d, v = get_datacenter_by_name_uuid(mydb, tenant_id, vnf_instance_desc["datacenter"]) myvims[d] = v + myvim_threads[d] = get_vim_thread(tenant_id, vnf_instance_desc["datacenter"]) datacenter2tenant[d] = v['config']['datacenter_tenant_id'] scenario_vnf["datacenter"] = vnf_instance_desc["datacenter"] @@ -1910,7 +2006,7 @@ def create_instance(mydb, tenant_id, instance_dict): logger.debug("Creating instance scenario-dict MERGED:\n%s", yaml.safe_dump(scenarioDict, indent=4, default_flow_style=False)) - #1. Creating new nets (sce_nets) in the VIM" + # 1. Creating new nets (sce_nets) in the VIM" for sce_net in scenarioDict['nets']: sce_net["vim_id_sites"]={} descriptor_net = instance_dict.get("networks",{}).get(sce_net["name"],{}) @@ -1922,9 +2018,11 @@ def create_instance(mydb, tenant_id, instance_dict): if site.get("datacenter"): vim = myvims[ site["datacenter"] ] datacenter_id = site["datacenter"] + myvim_thread = myvim_threads[ site["datacenter"] ] else: vim = myvims[ default_datacenter_id ] datacenter_id = default_datacenter_id + myvim_thread = myvim_threads[default_datacenter_id] net_type = sce_net['type'] lookfor_filter = {'admin_state_up': True, 'status': 'ACTIVE'} #'shared': True if sce_net["external"]: @@ -1982,48 +2080,58 @@ def create_instance(mydb, tenant_id, instance_dict): create_network = False if create_network: #if network is not external - network_id = vim.new_network(net_vim_name, net_type, sce_net.get('ip_profile',None)) - sce_net["vim_id_sites"][datacenter_id] = network_id - auxNetDict['scenario'][sce_net['uuid']][datacenter_id] = network_id - rollbackList.append({'what':'network', 'where':'vim', 'vim_id':datacenter_id, 'uuid':network_id}) + task = new_task("new-net", (net_vim_name, net_type, sce_net.get('ip_profile',None))) + task_id = myvim_thread.insert_task(task) + instance_tasks[task_id] = task + #network_id = vim.new_network(net_vim_name, net_type, sce_net.get('ip_profile',None)) + sce_net["vim_id_sites"][datacenter_id] = task_id + auxNetDict['scenario'][sce_net['uuid']][datacenter_id] = task_id + rollbackList.append({'what':'network', 'where':'vim', 'vim_id':datacenter_id, 'uuid':task_id}) sce_net["created"] = True - #2. Creating new nets (vnf internal nets) in the VIM" + # 2. Creating new nets (vnf internal nets) in the VIM" #For each vnf net, we create it and we add it to instanceNetlist. for sce_vnf in scenarioDict['vnfs']: for net in sce_vnf['nets']: if sce_vnf.get("datacenter"): vim = myvims[ sce_vnf["datacenter"] ] datacenter_id = sce_vnf["datacenter"] + myvim_thread = myvim_threads[ sce_vnf["datacenter"]] else: vim = myvims[ default_datacenter_id ] datacenter_id = default_datacenter_id + myvim_thread = myvim_threads[default_datacenter_id] descriptor_net = instance_dict.get("vnfs",{}).get(sce_vnf["name"],{}) net_name = descriptor_net.get("name") if not net_name: net_name = "%s.%s" %(instance_name, net["name"]) net_name = net_name[:255] #limit length net_type = net['type'] - network_id = vim.new_network(net_name, net_type, net.get('ip_profile',None)) - net['vim_id'] = network_id + task = new_task("new-net", (net_name, net_type, net.get('ip_profile',None))) + task_id = myvim_thread.insert_task(task) + instance_tasks[task_id] = task + # network_id = vim.new_network(net_name, net_type, net.get('ip_profile',None)) + net['vim_id'] = task_id if sce_vnf['uuid'] not in auxNetDict: auxNetDict[sce_vnf['uuid']] = {} - auxNetDict[sce_vnf['uuid']][net['uuid']] = network_id - rollbackList.append({'what':'network','where':'vim','vim_id':datacenter_id,'uuid':network_id}) + auxNetDict[sce_vnf['uuid']][net['uuid']] = task_id + rollbackList.append({'what':'network','where':'vim','vim_id':datacenter_id,'uuid':task_id}) net["created"] = True #print "auxNetDict:" #print yaml.safe_dump(auxNetDict, indent=4, default_flow_style=False) - #3. Creating new vm instances in the VIM + # 3. Creating new vm instances in the VIM #myvim.new_vminstance(self,vimURI,tenant_id,name,description,image_id,flavor_id,net_dict) for sce_vnf in scenarioDict['vnfs']: if sce_vnf.get("datacenter"): vim = myvims[ sce_vnf["datacenter"] ] + myvim_thread = myvim_threads[ sce_vnf["datacenter"] ] datacenter_id = sce_vnf["datacenter"] else: vim = myvims[ default_datacenter_id ] + myvim_thread = myvim_threads[ default_datacenter_id ] datacenter_id = default_datacenter_id sce_vnf["datacenter_id"] = datacenter_id i = 0 @@ -2046,9 +2154,6 @@ def create_instance(mydb, tenant_id, instance_dict): flavor_dict['extended']= yaml.load(flavor_dict['extended']) flavor_id = create_or_use_flavor(mydb, {datacenter_id: vim}, flavor_dict, rollbackList, True) - - - #Obtain information for additional disks extended_flavor_dict = mydb.get_rows(FROM='datacenters_flavors', SELECT=('extended',), WHERE={'vim_id': flavor_id}) if not extended_flavor_dict: @@ -2063,14 +2168,11 @@ def create_instance(mydb, tenant_id, instance_dict): if 'disks' in extended_flavor_dict_yaml: myVMDict['disks'] = extended_flavor_dict_yaml['disks'] - - - vm['vim_flavor_id'] = flavor_id - myVMDict['imageRef'] = vm['vim_image_id'] myVMDict['flavorRef'] = vm['vim_flavor_id'] myVMDict['networks'] = [] + task_depends = {} #TODO ALF. connect_mgmt_interfaces. Connect management interfaces if this is true for iface in vm['interfaces']: netDict = {} @@ -2121,6 +2223,8 @@ def create_instance(mydb, tenant_id, instance_dict): break else: netDict['net_id'] = auxNetDict[ sce_vnf['uuid'] ][ iface['net_id'] ] + if is_task_id(netDict['net_id']): + task_depends[netDict['net_id']] = instance_tasks[netDict['net_id']] #skip bridge ifaces not connected to any net #if 'net_id' not in netDict or netDict['net_id']==None: # continue @@ -2134,9 +2238,11 @@ def create_instance(mydb, tenant_id, instance_dict): cloud_config_vm = unify_cloud_config(vm["boot_data"], cloud_config) else: cloud_config_vm = cloud_config - vm_id = vim.new_vminstance(myVMDict['name'],myVMDict['description'],myVMDict.get('start', None), - myVMDict['imageRef'],myVMDict['flavorRef'],myVMDict['networks'], cloud_config = cloud_config_vm, - disk_list = myVMDict['disks']) + task = new_task("new-vm", (myVMDict['name'], myVMDict['description'], myVMDict.get('start', None), + myVMDict['imageRef'], myVMDict['flavorRef'], myVMDict['networks'], + cloud_config_vm, myVMDict['disks']), depends=task_depends) + vm_id = myvim_thread.insert_task(task) + instance_tasks[vm_id] = task vm['vim_id'] = vm_id rollbackList.append({'what':'vm','where':'vim','vim_id':datacenter_id,'uuid':vm_id}) @@ -2151,6 +2257,15 @@ def create_instance(mydb, tenant_id, instance_dict): logger.debug("create_instance Deployment done scenarioDict: %s", yaml.safe_dump(scenarioDict, indent=4, default_flow_style=False) ) instance_id = mydb.new_instance_scenario_as_a_whole(tenant_id,instance_name, instance_description, scenarioDict) + # Update database with those ended tasks + for task in instance_tasks.values(): + if task["status"] == "ok": + if task["name"] == "new-vm": + mydb.update_rows("instance_vms", UPDATE={"vim_vm_id": task["result"]}, + WHERE={"vim_vm_id": task["id"]}) + elif task["name"] == "new-net": + mydb.update_rows("instance_nets", UPDATE={"vim_net_id": task["result"]}, + WHERE={"vim_net_id": task["id"]}) return mydb.get_instance_scenario(instance_id) except (NfvoException, vimconn.vimconnException,db_base_Exception) as e: message = rollback(mydb, myvims, rollbackList) @@ -2164,6 +2279,7 @@ def create_instance(mydb, tenant_id, instance_dict): #logger.error("create_instance: %s", error_text) raise NfvoException(error_text, e.http_code) + def delete_instance(mydb, tenant_id, instance_id): #print "Checking that the instance_id exists and getting the instance dictionary" instanceDict = mydb.get_instance_scenario(instance_id, tenant_id) @@ -2176,13 +2292,20 @@ def delete_instance(mydb, tenant_id, instance_id): #2. delete from VIM error_msg = "" - myvims={} + myvims = {} + myvim_threads = {} #2.1 deleting VMs #vm_fail_list=[] for sce_vnf in instanceDict['vnfs']: datacenter_key = (sce_vnf["datacenter_id"], sce_vnf["datacenter_tenant_id"]) if datacenter_key not in myvims: + try: + myvim_thread = get_vim_thread(tenant_id, sce_vnf["datacenter_id"], sce_vnf["datacenter_tenant_id"]) + except NfvoException as e: + logger.error(str(e)) + myvim_thread = None + myvim_threads[datacenter_key] = myvim_thread vims = get_vim(mydb, tenant_id, datacenter_id=sce_vnf["datacenter_id"], datacenter_tenant_id=sce_vnf["datacenter_tenant_id"]) if len(vims) == 0: @@ -2192,12 +2315,32 @@ def delete_instance(mydb, tenant_id, instance_id): else: myvims[datacenter_key] = vims.values()[0] myvim = myvims[datacenter_key] + myvim_thread = myvim_threads[datacenter_key] for vm in sce_vnf['vms']: if not myvim: error_msg += "\n VM id={} cannot be deleted because datacenter={} not found".format(vm['vim_vm_id'], sce_vnf["datacenter_id"]) continue try: - myvim.delete_vminstance(vm['vim_vm_id']) + task=None + if is_task_id(vm['vim_vm_id']): + task_id = vm['vim_vm_id'] + old_task = task_dict.get(task_id) + if not old_task: + error_msg += "\n VM was scheduled for create, but task {} is not found".format(task_id) + continue + with task_lock: + if old_task["status"] == "enqueued": + old_task["status"] = "deleted" + elif old_task["status"] == "error": + continue + elif old_task["status"] == "processing": + task = new_task("del-vm", task_id, depends={task_id: old_task}) + else: #ok + task = new_task("del-vm", old_task["result"]) + else: + task = new_task("del-vm", vm['vim_vm_id'], store=False) + if task: + myvim_thread.insert_task(task) except vimconn.vimconnNotFoundException as e: error_msg+="\n VM VIM_id={} not found at datacenter={}".format(vm['vim_vm_id'], sce_vnf["datacenter_id"]) logger.warn("VM instance '%s'uuid '%s', VIM id '%s', from VNF_id '%s' not found", @@ -2214,6 +2357,12 @@ def delete_instance(mydb, tenant_id, instance_id): continue #skip not created nets datacenter_key = (net["datacenter_id"], net["datacenter_tenant_id"]) if datacenter_key not in myvims: + try: + myvim_thread = get_vim_thread(tenant_id, sce_vnf["datacenter_id"], sce_vnf["datacenter_tenant_id"]) + except NfvoException as e: + logger.error(str(e)) + myvim_thread = None + myvim_threads[datacenter_key] = myvim_thread vims = get_vim(mydb, tenant_id, datacenter_id=net["datacenter_id"], datacenter_tenant_id=net["datacenter_tenant_id"]) if len(vims) == 0: @@ -2222,25 +2371,48 @@ def delete_instance(mydb, tenant_id, instance_id): else: myvims[datacenter_key] = vims.values()[0] myvim = myvims[datacenter_key] + myvim_thread = myvim_threads[datacenter_key] if not myvim: error_msg += "\n Net VIM_id={} cannot be deleted because datacenter={} not found".format(net['vim_net_id'], net["datacenter_id"]) continue try: - myvim.delete_network(net['vim_net_id']) + task = None + if is_task_id(net['vim_net_id']): + task_id = net['vim_net_id'] + old_task = task_dict.get(task_id) + if not old_task: + error_msg += "\n NET was scheduled for create, but task {} is not found".format(task_id) + continue + with task_lock: + if old_task["status"] == "enqueued": + old_task["status"] = "deleted" + elif old_task["status"] == "error": + continue + elif old_task["status"] == "processing": + task = new_task("del-net", task_id, depends={task_id: old_task}) + else: # ok + task = new_task("del-net", old_task["result"]) + else: + task = new_task("del-net", net['vim_net_id'], store=False) + if task: + myvim_thread.insert_task(task) except vimconn.vimconnNotFoundException as e: - error_msg+="\n NET VIM_id={} not found at datacenter={}".format(net['vim_net_id'], net["datacenter_id"]) + error_msg += "\n NET VIM_id={} not found at datacenter={}".format(net['vim_net_id'], net["datacenter_id"]) logger.warn("NET '%s', VIM_id '%s', from VNF_net_id '%s' not found", - net['uuid'], net['vim_net_id'], str(net['vnf_net_id'])) + net['uuid'], net['vim_net_id'], str(net['vnf_net_id'])) except vimconn.vimconnException as e: - error_msg+="\n NET VIM_id={} at datacenter={} Error: {} {}".format(net['vim_net_id'], net["datacenter_id"], e.http_code, str(e)) + error_msg += "\n NET VIM_id={} at datacenter={} Error: {} {}".format(net['vim_net_id'], + net["datacenter_id"], + e.http_code, str(e)) logger.error("Error %d deleting NET '%s', VIM_id '%s', from VNF_net_id '%s': %s", - e.http_code, net['uuid'], net['vim_net_id'], str(net['vnf_net_id']), str(e)) - if len(error_msg)>0: + e.http_code, net['uuid'], net['vim_net_id'], str(net['vnf_net_id']), str(e)) + if len(error_msg) > 0: return 'instance ' + message + ' deleted but some elements could not be deleted, or already deleted (error: 404) from VIM: ' + error_msg else: return 'instance ' + message + ' deleted' + def refresh_instance(mydb, nfvo_tenant, instanceDict, datacenter=None, vim_tenant=None): '''Refreshes a scenario instance. It modifies instanceDict''' '''Returns: @@ -2405,6 +2577,7 @@ def refresh_instance(mydb, nfvo_tenant, instanceDict, datacenter=None, vim_tenan return 0, 'Scenario instance ' + instance_id + ' refreshed.' + def instance_action(mydb,nfvo_tenant,instance_id, action_dict): #print "Checking that the instance_id exists and getting the instance dictionary" instanceDict = mydb.get_instance_scenario(instance_id, nfvo_tenant) @@ -2477,6 +2650,7 @@ def instance_action(mydb,nfvo_tenant,instance_id, action_dict): else: return vm_result + def create_or_use_console_proxy_thread(console_server, console_port): #look for a non-used port console_thread_key = console_server + ":" + str(console_port) @@ -2501,6 +2675,7 @@ def create_or_use_console_proxy_thread(console_server, console_port): raise NfvoException(str(e), HTTP_Bad_Request) raise NfvoException("Not found any free 'http_console_ports'", HTTP_Conflict) + def check_tenant(mydb, tenant_id): '''check that tenant exists at database''' tenant = mydb.get_rows(FROM='nfvo_tenants', SELECT=('uuid',), WHERE={'uuid': tenant_id}) @@ -2508,10 +2683,12 @@ def check_tenant(mydb, tenant_id): raise NfvoException("tenant '{}' not found".format(tenant_id), HTTP_Not_Found) return + def new_tenant(mydb, tenant_dict): tenant_id = mydb.new_row("nfvo_tenants", tenant_dict, add_uuid=True) return tenant_id + def delete_tenant(mydb, tenant): #get nfvo_tenant info @@ -2519,6 +2696,7 @@ def delete_tenant(mydb, tenant): mydb.delete_row_by_id("nfvo_tenants", tenant_dict['uuid']) return tenant_dict['uuid'] + " " + tenant_dict["name"] + def new_datacenter(mydb, datacenter_descriptor): if "config" in datacenter_descriptor: datacenter_descriptor["config"]=yaml.safe_dump(datacenter_descriptor["config"],default_flow_style=True,width=256) @@ -2536,6 +2714,7 @@ def new_datacenter(mydb, datacenter_descriptor): datacenter_id = mydb.new_row("datacenters", datacenter_descriptor, add_uuid=True) return datacenter_id + def edit_datacenter(mydb, datacenter_id_name, datacenter_descriptor): #obtain data, check that only one exist datacenter = mydb.get_table_by_uuid_name('datacenters', datacenter_id_name) @@ -2563,12 +2742,14 @@ def edit_datacenter(mydb, datacenter_id_name, datacenter_descriptor): mydb.update_rows('datacenters', datacenter_descriptor, where) return datacenter_id + def delete_datacenter(mydb, datacenter): #get nfvo_tenant info datacenter_dict = mydb.get_table_by_uuid_name('datacenters', datacenter, 'datacenter') mydb.delete_row_by_id("datacenters", datacenter_dict['uuid']) return datacenter_dict['uuid'] + " " + datacenter_dict['name'] + def associate_datacenter_to_tenant(mydb, nfvo_tenant, datacenter, vim_tenant_id=None, vim_tenant_name=None, vim_username=None, vim_password=None, config=None): #get datacenter info datacenter_id, myvim = get_datacenter_by_name_uuid(mydb, None, datacenter) @@ -2630,12 +2811,13 @@ def associate_datacenter_to_tenant(mydb, nfvo_tenant, datacenter, vim_tenant_id= # create thread datacenter_id, myvim = get_datacenter_by_name_uuid(mydb, tenant_dict['uuid'], datacenter_id) # reload data thread_name = get_non_used_vim_name(datacenter_name, datacenter_id, tenant_dict['name'], tenant_dict['uuid']) - new_thread = vim_thread.vim_thread(myvim, thread_name) + new_thread = vim_thread.vim_thread(myvim, task_lock, thread_name, datacenter_name, db=db, db_lock=db_lock) new_thread.start() - vim_threads["running"][datacenter_id + "-" + tenant_dict['uuid']] = new_thread - + thread_id = datacenter_id + "." + tenant_dict['uuid'] + vim_threads["running"][thread_id] = new_thread return datacenter_id + def deassociate_datacenter_to_tenant(mydb, tenant_id, datacenter, vim_tenant_id=None): #get datacenter info datacenter_id, myvim = get_datacenter_by_name_uuid(mydb, None, datacenter) @@ -2675,12 +2857,13 @@ def deassociate_datacenter_to_tenant(mydb, tenant_id, datacenter, vim_tenant_id= except db_base_Exception as e: logger.error("Cannot delete datacenter_tenants " + str(e)) pass # the error will be caused because dependencies, vim_tenant can not be deleted - thread_id = datacenter_id + "-" + tenant_datacenter_item["nfvo_tenant_id"] + thread_id = datacenter_id + "." + tenant_datacenter_item["nfvo_tenant_id"] thread = vim_threads["running"][thread_id] - thread.insert_task("exit") + thread.insert_task(new_task("exit", None, store=False)) vim_threads["deleting"][thread_id] = thread return "datacenter {} detached. {}".format(datacenter_id, warning) + def datacenter_action(mydb, tenant_id, datacenter, action_dict): #DEPRECATED #get datacenter info @@ -2723,6 +2906,7 @@ def datacenter_action(mydb, tenant_id, datacenter, action_dict): else: raise NfvoException("Unknown action " + str(action_dict), HTTP_Bad_Request) + def datacenter_edit_netmap(mydb, tenant_id, datacenter, netmap, action_dict): #get datacenter info datacenter_id, _ = get_datacenter_by_name_uuid(mydb, tenant_id, datacenter) @@ -2732,6 +2916,7 @@ def datacenter_edit_netmap(mydb, tenant_id, datacenter, netmap, action_dict): WHERE={'datacenter_id':datacenter_id, what: netmap}) return result + def datacenter_new_netmap(mydb, tenant_id, datacenter, action_dict=None): #get datacenter info datacenter_id, myvim = get_datacenter_by_name_uuid(mydb, tenant_id, datacenter) @@ -2778,6 +2963,7 @@ def datacenter_new_netmap(mydb, tenant_id, datacenter, action_dict=None): net_list.append(net_nfvo) return net_list + def vim_action_get(mydb, tenant_id, datacenter, item, name): #get datacenter info datacenter_id, myvim = get_datacenter_by_name_uuid(mydb, tenant_id, datacenter) @@ -2809,6 +2995,7 @@ def vim_action_get(mydb, tenant_id, datacenter, item, name): print "vim_action Not possible to get_%s_list from VIM: %s " % (item, str(e)) raise NfvoException("Not possible to get_{}_list from VIM: {}".format(item, str(e)), e.http_code) + def vim_action_delete(mydb, tenant_id, datacenter, item, name): #get datacenter info if tenant_id == "any": @@ -2842,6 +3029,7 @@ def vim_action_delete(mydb, tenant_id, datacenter, item, name): return "{} {} {} deleted".format(item[:-1], item_id,item_name) + def vim_action_create(mydb, tenant_id, datacenter, item, descriptor): #get datacenter info logger.debug("vim_action_create descriptor %s", str(descriptor)) diff --git a/vim_thread.py b/vim_thread.py index 5460523b..42279a23 100644 --- a/vim_thread.py +++ b/vim_thread.py @@ -33,97 +33,194 @@ import time import Queue import logging import vimconn - +from db_base import db_base_Exception # from logging import Logger # import auxiliary_functions as af -# TODO: insert a logging system + +def is_task_id(id): + return True if id[:5] == "TASK." else False class vim_thread(threading.Thread): - def __init__(self, vimconn, name=None): - '''Init a thread. + def __init__(self, vimconn, task_lock, name=None, datacenter_name=None, datacenter_tenant_id=None, db=None, db_lock=None): + """Init a thread. Arguments: 'id' number of thead 'name' name of thread 'host','user': host ip or name to manage and user 'db', 'db_lock': database class and lock to use it in exclusion - ''' + """ + self.tasksResult = {} + """ It will contain a dictionary with + task_id: + status: enqueued,done,error,deleted,processing + result: VIM result, + """ threading.Thread.__init__(self) self.vim = vimconn + self.datacenter_name = datacenter_name + self.datacenter_tenant_id = datacenter_tenant_id if not name: - self.name = vimconn["id"] + "-" + vimconn["config"]["datacenter_tenant_id"] + self.name = vimconn["id"] + "." + vimconn["config"]["datacenter_tenant_id"] else: self.name = name self.logger = logging.getLogger('openmano.vim.'+self.name) + self.db = db + self.db_lock = db_lock - self.queueLock = threading.Lock() - self.taskQueue = Queue.Queue(2000) + self.task_lock = task_lock + self.task_queue = Queue.Queue(2000) - - def insert_task(self, task, *aditional): + def insert_task(self, task): try: - self.queueLock.acquire() - task = self.taskQueue.put( (task,) + aditional, timeout=5) - self.queueLock.release() - return 1, None + self.task_queue.put(task, False) + return task["id"] except Queue.Full: - return -1, "timeout inserting a task over host " + self.name + raise vimconn.vimconnException(self.name + ": timeout inserting a task") + + def del_task(self, task): + with self.task_lock: + if task["status"] == "enqueued": + task["status"] == "deleted" + return True + else: # task["status"] == "processing" + self.task_lock.release() + return False def run(self): self.logger.debug("Starting") while True: #TODO reload service while True: - self.queueLock.acquire() - if not self.taskQueue.empty(): - task = self.taskQueue.get() + if not self.task_queue.empty(): + task = self.task_queue.get() + self.task_lock.acquire() + if task["status"] == "deleted": + self.task_lock.release() + continue + task["status"] == "processing" + self.task_lock.release() else: - task = None - self.queueLock.release() - - if task is None: now=time.time() time.sleep(1) - continue - - if task[0] == 'instance': - pass - elif task[0] == 'image': - pass - elif task[0] == 'exit': - print self.name, ": processing task exit" - self.terminate() + continue + self.logger.debug("processing task id={} name={} params={}".format(task["id"], task["name"], + str(task["params"]))) + if task["name"] == 'exit' or task["name"] == 'reload': + result, content = self.terminate(task) + elif task["name"] == 'new-vm': + result, content = self.new_vm(task) + elif task["name"] == 'del-vm': + result, content = self.del_vm(task) + elif task["name"] == 'new-net': + result, content = self.new_net(task) + elif task["name"] == 'del-net': + result, content = self.del_net(task) + else: + error_text = "unknown task {}".format(task["name"]) + self.logger.error(error_text) + result = False + content = error_text + + with self.task_lock: + task["status"] = "done" if result else "error" + task["result"] = content + self.task_queue.task_done() + + if task["name"] == 'exit': return 0 - elif task[0] == 'reload': - print self.name, ": processing task reload terminating and relaunching" - self.terminate() + elif task["name"] == 'reload': break - elif task[0] == 'edit-iface': - pass - elif task[0] == 'restore-iface': - pass - elif task[0] == 'new-ovsbridge': - pass - elif task[0] == 'new-vxlan': - pass - elif task[0] == 'del-ovsbridge': - pass - elif task[0] == 'del-vxlan': - pass - elif task[0] == 'create-ovs-bridge-port': - pass - elif task[0] == 'del-ovs-port': - pass - else: - self.logger.error("unknown task %s", str(task)) self.logger.debug("Finishing") - def terminate(self): - pass + def terminate(self, task): + return True, None + + def new_net(self, task): + try: + task_id = task["id"] + params = task["params"] + net_id = self.vim.new_network(*params) + with self.db_lock: + self.db.update_rows("instance_nets", UPDATE={"vim_net_id": net_id}, WHERE={"vim_net_id": task_id}) + return True, net_id + except db_base_Exception as e: + self.logger.error("Error updating database %s", str(e)) + return True, net_id + except vimconn.vimconnException as e: + return False, str(e) + + def new_vm(self, task): + try: + params = task["params"] + task_id = task["id"] + depends = task.get("depends") + net_list = params[5] + for net in net_list: + if is_task_id(net["net_id"]): # change task_id into network_id + try: + task_net = depends[net["net_id"]] + with self.task_lock: + if task_net["status"] == "error": + return False, "Cannot create VM because depends on a network that cannot be created: " + \ + str(task_net["result"]) + elif task_net["status"] == "enqueued" or task_net["status"] == "processing": + return False, "Cannot create VM because depends on a network still not created" + network_id = task_net["result"] + net["net_id"] = network_id + except Exception as e: + return False, "Error trying to map from task_id={} to task result: {}".format(net["net_id"], + str(e)) + vm_id = self.vim.new_vminstance(*params) + with self.db_lock: + self.db.update_rows("instance_vms", UPDATE={"vim_vm_id": vm_id}, WHERE={"vim_vm_id": task_id}) + return True, vm_id + except db_base_Exception as e: + self.logger.error("Error updtaing database %s", str(e)) + return True, vm_id + except vimconn.vimconnException as e: + return False, str(e) + + def del_vm(self, task): + vm_id = task["params"] + if is_task_id(vm_id): + try: + task_create = task["depends"][vm_id] + with self.task_lock: + if task_create["status"] == "error": + return True, "VM was not created. It has error: " + str(task_create["result"]) + elif task_create["status"] == "enqueued" or task_create["status"] == "processing": + return False, "Cannot delete VM because still creating" + vm_id = task_create["result"] + except Exception as e: + return False, "Error trying to get task_id='{}':".format(vm_id, str(e)) + try: + return True, self.vim.delete_vminstance(vm_id) + except vimconn.vimconnException as e: + return False, str(e) + + def del_net(self, task): + net_id = task["params"] + if is_task_id(net_id): + try: + task_create = task["depends"][net_id] + with self.task_lock: + if task_create["status"] == "error": + return True, "net was not created. It has error: " + str(task_create["result"]) + elif task_create["status"] == "enqueued" or task_create["status"] == "processing": + return False, "Cannot delete net because still creating" + net_id = task_create["result"] + except Exception as e: + return False, "Error trying to get task_id='{}':".format(net_id, str(e)) + try: + return True, self.vim.delete_network(net_id) + except vimconn.vimconnException as e: + return False, str(e) + diff --git a/vimconn.py b/vimconn.py index c1721cdb..a9bd9be6 100644 --- a/vimconn.py +++ b/vimconn.py @@ -373,6 +373,8 @@ class vimconnector(): 'VFnotShared'(SRIOV without VLAN tag) same as PF for network connectivity. VF where no other VFs are allocated on the same physical NIC 'bw': (optional) only for PF/VF/VFnotShared. Minimal Bandwidth required for the interface in GBPS + 'port_security': (optional) If False it must avoid any traffic filtering at this interface. If missing + or True, it must apply the default VIM behaviour After execution the method will add the key: 'vim_id': must be filled/added by this method with the VIM identifier generated by the VIM for this interface. 'net_list' is modified diff --git a/vimconn_openstack.py b/vimconn_openstack.py index 7ba280b0..b501d9da 100644 --- a/vimconn_openstack.py +++ b/vimconn_openstack.py @@ -68,7 +68,8 @@ volume_timeout = 60 server_timeout = 60 class vimconnector(vimconn.vimconnector): - def __init__(self, uuid, name, tenant_id, tenant_name, url, url_admin=None, user=None, passwd=None, log_level=None, config={}): + def __init__(self, uuid, name, tenant_id, tenant_name, url, url_admin=None, user=None, passwd=None, + log_level=None, config={}, persistent_info={}): '''using common constructor parameters. In this case 'url' is the keystone authorization url, 'url_admin' is not use @@ -77,7 +78,8 @@ class vimconnector(vimconn.vimconnector): if config.get('APIversion') == 'v3.3': self.osc_api_version = 'v3.3' vimconn.vimconnector.__init__(self, uuid, name, tenant_id, tenant_name, url, url_admin, user, passwd, log_level, config) - + + self.persistent_info = persistent_info self.k_creds={} self.n_creds={} if self.config.get("insecure"): diff --git a/vimconn_openvim.py b/vimconn_openvim.py index 9d6748a9..e86b54db 100644 --- a/vimconn_openvim.py +++ b/vimconn_openvim.py @@ -323,11 +323,13 @@ get_processor_rankings_response_schema = { } class vimconnector(vimconn.vimconnector): - def __init__(self, uuid, name, tenant_id, tenant_name, url, url_admin=None, user=None, passwd=None,log_level="DEBUG",config={}): + def __init__(self, uuid, name, tenant_id, tenant_name, url, url_admin=None, user=None, passwd=None, + log_level="DEBUG", config={}, persistent_info={}): vimconn.vimconnector.__init__(self, uuid, name, tenant_id, tenant_name, url, url_admin, user, passwd, log_level, config) self.tenant = None self.headers_req = {'content-type': 'application/json'} self.logger = logging.getLogger('openmano.vim.openvim') + self.persistent_info = persistent_info if tenant_id: self.tenant = tenant_id diff --git a/vimconn_vmware.py b/vimconn_vmware.py index 3e81f513..cf7445d0 100644 --- a/vimconn_vmware.py +++ b/vimconn_vmware.py @@ -127,7 +127,7 @@ flavorlist = {} class vimconnector(vimconn.vimconnector): def __init__(self, uuid=None, name=None, tenant_id=None, tenant_name=None, - url=None, url_admin=None, user=None, passwd=None, log_level=None, config={}): + url=None, url_admin=None, user=None, passwd=None, log_level=None, config={}, persistent_info={}): """ Constructor create vmware connector to vCloud director. @@ -164,6 +164,7 @@ class vimconnector(vimconn.vimconnector): self.logger = logging.getLogger('openmano.vim.vmware') self.logger.setLevel(10) + self.persistent_info = persistent_info self.name = name self.id = uuid -- 2.25.1 From eb044522c3d119cb594b465380d4979f0988d216 Mon Sep 17 00:00:00 2001 From: kate Date: Mon, 6 Mar 2017 23:54:39 -0800 Subject: [PATCH 04/16] =?utf8?q?=20Changes=20in=20vimconn=5Fvmware.py=20:?= =?utf8?q?=20Modified=20flavor=20logic=20as=20per=20Code=20review=20commen?= =?utf8?q?ts.=20Fixed=20defect=20=E2=80=93=20167=20VMware=20connector=20ne?= =?utf8?q?eds=20to=20resolve=20vCenter=20host=20name.=20Fixed=20defect=20?= =?utf8?q?=E2=80=93=20185=20Plug=20test=20-=20Communication=20to=20VMware?= =?utf8?q?=20VIM=20does=20not=20work=20unless=20hostnames=20are=20added=20?= =?utf8?q?to=20/etc./hosts=20in=20RO=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Change-Id: Ibd83a870b4b086c4bd2d804e711adbf1c8cbb184 Signed-off-by: kate --- vimconn_vmware.py | 184 +++++++++++++++++++++++++--------------------- 1 file changed, 99 insertions(+), 85 deletions(-) diff --git a/vimconn_vmware.py b/vimconn_vmware.py index cf7445d0..a2242556 100644 --- a/vimconn_vmware.py +++ b/vimconn_vmware.py @@ -121,11 +121,10 @@ netStatus2manoFormat = {'ACTIVE': 'ACTIVE', 'PAUSED': 'PAUSED', 'INACTIVE': 'INA 'ERROR': 'ERROR', 'DELETED': 'DELETED' } -# dict used to store flavor in memory -flavorlist = {} - - class vimconnector(vimconn.vimconnector): + # dict used to store flavor in memory + flavorlist = {} + def __init__(self, uuid=None, name=None, tenant_id=None, tenant_name=None, url=None, url_admin=None, user=None, passwd=None, log_level=None, config={}, persistent_info={}): """ @@ -154,6 +153,7 @@ class vimconnector(vimconn.vimconnector): dict['admin_username'] dict['admin_password'] + config - Provide NSX and vCenter information Returns: Nothing. @@ -181,6 +181,10 @@ class vimconnector(vimconn.vimconnector): self.nsx_manager = None self.nsx_user = None self.nsx_password = None + self.vcenter_ip = None + self.vcenter_port = None + self.vcenter_user = None + self.vcenter_password = None if tenant_name is not None: orgnameandtenant = tenant_name.split(":") @@ -208,6 +212,11 @@ class vimconnector(vimconn.vimconnector): except KeyError: raise vimconn.vimconnException(message="Error: nsx manager or nsx user or nsx password is empty in Config") + self.vcenter_ip = config.get("vcenter_ip", None) + self.vcenter_port = config.get("vcenter_port", None) + self.vcenter_user = config.get("vcenter_user", None) + self.vcenter_password = config.get("vcenter_password", None) + self.org_uuid = None self.vca = None @@ -695,9 +704,9 @@ class vimconnector(vimconn.vimconnector): """Obtain flavor details from the VIM Returns the flavor dict details {'id':<>, 'name':<>, other vim specific } #TODO to concrete """ - if flavor_id not in flavorlist: + if flavor_id not in vimconnector.flavorlist: raise vimconn.vimconnNotFoundException("Flavor not found.") - return flavorlist[flavor_id] + return vimconnector.flavorlist[flavor_id] def new_flavor(self, flavor_data): """Adds a tenant flavor to VIM @@ -745,7 +754,7 @@ class vimconnector(vimconn.vimconnector): new_flavor[FLAVOR_DISK_KEY] = disk # generate a new uuid put to internal dict and return it. flavor_id = uuid.uuid4() - flavorlist[str(flavor_id)] = new_flavor + vimconnector.flavorlist[str(flavor_id)] = new_flavor self.logger.debug("Created flavor - {} : {}".format(flavor_id, new_flavor)) return str(flavor_id) @@ -755,10 +764,10 @@ class vimconnector(vimconn.vimconnector): Returns the used id or raise an exception """ - if flavor_id not in flavorlist: + if flavor_id not in vimconnector.flavorlist: raise vimconn.vimconnNotFoundException("Flavor not found.") - flavorlist.pop(flavor_id, None) + vimconnector.flavorlist.pop(flavor_id, None) return flavor_id def new_image(self, image_dict): @@ -1223,8 +1232,8 @@ class vimconnector(vimconn.vimconnector): """ self.logger.info("Creating new instance for entry {}".format(name)) - self.logger.debug("desc {} boot {} image_id: {} flavor_id: {} net_list: {} cloud_config {}". - format(description, start, image_id, flavor_id, net_list, cloud_config)) + self.logger.debug("desc {} boot {} image_id: {} flavor_id: {} net_list: {} cloud_config {}".format( + description, start, image_id, flavor_id, net_list, cloud_config)) vca = self.connect() if not vca: raise vimconn.vimconnConnectionException("self.connect() is failed.") @@ -1263,13 +1272,13 @@ class vimconnector(vimconn.vimconnector): vm_disk = None pci_devices_info = [] if flavor_id is not None: - if flavor_id not in flavorlist: + if flavor_id not in vimconnector.flavorlist: raise vimconn.vimconnNotFoundException("new_vminstance(): Failed create vApp {}: " "Failed retrieve flavor information " "flavor id {}".format(name, flavor_id)) else: try: - flavor = flavorlist[flavor_id] + flavor = vimconnector.flavorlist[flavor_id] vm_cpus = flavor[FLAVOR_VCPUS_KEY] vm_memory = flavor[FLAVOR_RAM_KEY] vm_disk = flavor[FLAVOR_DISK_KEY] @@ -1281,8 +1290,8 @@ class vimconnector(vimconn.vimconnector): for interface in numa.get("interfaces",() ): if interface["dedicated"].strip()=="yes": pci_devices_info.append(interface) - except KeyError: - raise vimconn.vimconnException("Corrupted flavor. {}".format(flavor_id)) + except Exception as exp: + raise vimconn.vimconnException("Corrupted flavor. {}.Exception: {}".format(flavor_id, exp)) # image upload creates template name as catalog name space Template. templateName = self.get_catalogbyid(catalog_uuid=image_id, catalogs=catalogs) @@ -2907,7 +2916,6 @@ class vimconnector(vimconn.vimconnector): vmext = vim_info.find('vmext:VmVimObjectRef', namespaces) if vmext is not None: vm_vcenter_info["vm_moref_id"] = vmext.find('vmext:MoRef', namespaces).text - vm_vcenter_info["vim_server_href"] = vmext.find('vmext:VimServerRef', namespaces).attrib['href'] parsed_respond["vm_vcenter_info"]= vm_vcenter_info virtual_hardware_section = children_section.find('ovf:VirtualHardwareSection', namespaces) @@ -3075,27 +3083,31 @@ class vimconnector(vimconn.vimconnector): vm_obj = None vcenter_conect = None self.logger.info("Add pci devices {} into vApp {}".format(pci_devices , vapp_uuid)) - #Assuming password of vCenter user is same as password of vCloud user - vm_moref_id , vm_vcenter_host , vm_vcenter_username, vm_vcenter_port = self.get_vcenter_info_rest(vapp_uuid) - self.logger.info("vm_moref_id, {} vm_vcenter_host {} vm_vcenter_username{} "\ - "vm_vcenter_port{}".format( - vm_moref_id, vm_vcenter_host, - vm_vcenter_username, vm_vcenter_port)) - if vm_moref_id and vm_vcenter_host and vm_vcenter_username: + try: + vm_vcenter_info = self.get_vm_vcenter_info(vapp_uuid) + except Exception as exp: + self.logger.error("Error occurred while getting vCenter infromationn"\ + " for VM : {}".format(exp)) + raise vimconn.vimconnException(message=exp) + + if vm_vcenter_info["vm_moref_id"]: context = None if hasattr(ssl, '_create_unverified_context'): context = ssl._create_unverified_context() try: no_of_pci_devices = len(pci_devices) if no_of_pci_devices > 0: - vcenter_conect = SmartConnect(host=vm_vcenter_host, user=vm_vcenter_username, - pwd=self.passwd, port=int(vm_vcenter_port) , - sslContext=context) + vcenter_conect = SmartConnect( + host=vm_vcenter_info["vm_vcenter_ip"], + user=vm_vcenter_info["vm_vcenter_user"], + pwd=vm_vcenter_info["vm_vcenter_password"], + port=int(vm_vcenter_info["vm_vcenter_port"]), + sslContext=context) atexit.register(Disconnect, vcenter_conect) content = vcenter_conect.RetrieveContent() #Get VM and its host - host_obj, vm_obj = self.get_vm_obj(content ,vm_moref_id) + host_obj, vm_obj = self.get_vm_obj(content ,vm_vcenter_info["vm_moref_id"]) self.logger.info("VM {} is currently on host {}".format(vm_obj, host_obj)) if host_obj and vm_obj: #get PCI devies from host on which vapp is currently installed @@ -3133,7 +3145,7 @@ class vimconnector(vimconn.vimconnector): if status: self.logger.info("Added PCI device {} to VM {}".format(pci_device,str(vm_obj))) else: - self.logger.info("Fail to add PCI device {} to VM {}".format(pci_device,str(vm_obj))) + self.logger.error("Fail to add PCI device {} to VM {}".format(pci_device,str(vm_obj))) return True, vm_obj, vcenter_conect else: self.logger.error("Currently there is no host with"\ @@ -3364,10 +3376,9 @@ class vimconnector(vimconn.vimconnector): exp)) return task - def get_vcenter_info_rest(self , vapp_uuid): + def get_vm_vcenter_info(self , vapp_uuid): """ - https://192.169.241.105/api/admin/extension/vimServer/cc82baf9-9f80-4468-bfe9-ce42b3f9dde5 - Method to get details of vCenter + Method to get details of vCenter and vm Args: vapp_uuid - uuid of vApp or VM @@ -3375,46 +3386,39 @@ class vimconnector(vimconn.vimconnector): Returns: Moref Id of VM and deails of vCenter """ - vm_moref_id = None - vm_vcenter = None - vm_vcenter_username = None - vm_vcenter_port = None - - vm_details = self.get_vapp_details_rest(vapp_uuid, need_admin_access=True) - if vm_details and "vm_vcenter_info" in vm_details: - vm_moref_id = vm_details["vm_vcenter_info"]["vm_moref_id"] - vim_server_href = vm_details["vm_vcenter_info"]["vim_server_href"] - - if vim_server_href: - vca = self.connect_as_admin() - if not vca: - raise vimconn.vimconnConnectionException("self.connect() is failed") - if vim_server_href is None: - self.logger.error("No url to get vcenter details") - - if vca.vcloud_session and vca.vcloud_session.organization: - response = Http.get(url=vim_server_href, - headers=vca.vcloud_session.get_vcloud_headers(), - verify=vca.verify, - logger=vca.logger) - - if response.status_code != requests.codes.ok: - self.logger.debug("GET REST API call {} failed. Return status code {}".format(vim_server_href, - response.status_code)) - try: - namespaces={"vmext":"http://www.vmware.com/vcloud/extension/v1.5", - "vcloud":"http://www.vmware.com/vcloud/v1.5" - } - xmlroot_respond = XmlElementTree.fromstring(response.content) - vm_vcenter_username = xmlroot_respond.find('vmext:Username', namespaces).text - vcenter_url = xmlroot_respond.find('vmext:Url', namespaces).text - vm_vcenter_port = vcenter_url.split(":")[2] - vm_vcenter = vcenter_url.split(":")[1].split("//")[1] + vm_vcenter_info = {} + + if self.vcenter_ip is not None: + vm_vcenter_info["vm_vcenter_ip"] = self.vcenter_ip + else: + raise vimconn.vimconnException(message="vCenter IP is not provided."\ + " Please provide vCenter IP while attaching datacenter to tenant in --config") + if self.vcenter_port is not None: + vm_vcenter_info["vm_vcenter_port"] = self.vcenter_port + else: + raise vimconn.vimconnException(message="vCenter port is not provided."\ + " Please provide vCenter port while attaching datacenter to tenant in --config") + if self.vcenter_user is not None: + vm_vcenter_info["vm_vcenter_user"] = self.vcenter_user + else: + raise vimconn.vimconnException(message="vCenter user is not provided."\ + " Please provide vCenter user while attaching datacenter to tenant in --config") + + if self.vcenter_password is not None: + vm_vcenter_info["vm_vcenter_password"] = self.vcenter_password + else: + raise vimconn.vimconnException(message="vCenter user password is not provided."\ + " Please provide vCenter user password while attaching datacenter to tenant in --config") + try: + vm_details = self.get_vapp_details_rest(vapp_uuid, need_admin_access=True) + if vm_details and "vm_vcenter_info" in vm_details: + vm_vcenter_info["vm_moref_id"] = vm_details["vm_vcenter_info"].get("vm_moref_id", None) - except Exception as exp : - self.logger.info("Error occurred calling rest api for vcenter information {}".format(exp)) + return vm_vcenter_info - return vm_moref_id , vm_vcenter , vm_vcenter_username, vm_vcenter_port + except Exception as exp: + self.logger.error("Error occurred while getting vCenter infromationn"\ + " for VM : {}".format(exp)) def get_vm_pci_details(self, vmuuid): @@ -3430,28 +3434,38 @@ class vimconnector(vimconn.vimconnector): """ vm_pci_devices_info = {} try: - vm_moref_id , vm_vcenter_host , vm_vcenter_username, vm_vcenter_port = self.get_vcenter_info_rest(vmuuid) - if vm_moref_id and vm_vcenter_host and vm_vcenter_username: + vm_vcenter_info = self.get_vm_vcenter_info(vmuuid) + if vm_vcenter_info["vm_moref_id"]: context = None if hasattr(ssl, '_create_unverified_context'): context = ssl._create_unverified_context() - vcenter_conect = SmartConnect(host=vm_vcenter_host, user=vm_vcenter_username, - pwd=self.passwd, port=int(vm_vcenter_port), - sslContext=context) + vcenter_conect = SmartConnect(host=vm_vcenter_info["vm_vcenter_ip"], + user=vm_vcenter_info["vm_vcenter_user"], + pwd=vm_vcenter_info["vm_vcenter_password"], + port=int(vm_vcenter_info["vm_vcenter_port"]), + sslContext=context + ) atexit.register(Disconnect, vcenter_conect) content = vcenter_conect.RetrieveContent() #Get VM and its host - host_obj, vm_obj = self.get_vm_obj(content ,vm_moref_id) - for device in vm_obj.config.hardware.device: - if type(device) == vim.vm.device.VirtualPCIPassthrough: - device_details={'devide_id':device.backing.id, - 'pciSlotNumber':device.slotInfo.pciSlotNumber - } - vm_pci_devices_info[device.deviceInfo.label] = device_details + if content: + host_obj, vm_obj = self.get_vm_obj(content ,vm_vcenter_info["vm_moref_id"]) + if host_obj and vm_obj: + vm_pci_devices_info["host_name"]= host_obj.name + vm_pci_devices_info["host_ip"]= host_obj.config.network.vnic[0].spec.ip.ipAddress + for device in vm_obj.config.hardware.device: + if type(device) == vim.vm.device.VirtualPCIPassthrough: + device_details={'devide_id':device.backing.id, + 'pciSlotNumber':device.slotInfo.pciSlotNumber, + } + vm_pci_devices_info[device.deviceInfo.label] = device_details + else: + self.logger.error("Can not connect to vCenter while getting "\ + "PCI devices infromationn") + return vm_pci_devices_info except Exception as exp: - self.logger.info("Error occurred while getting PCI devices infromationn"\ - " for VM {} : {}".format(vm_obj,exp)) - return vm_pci_devices_info - + self.logger.error("Error occurred while getting VM infromationn"\ + " for VM : {}".format(exp)) + raise vimconn.vimconnException(message=exp) -- 2.25.1 From bfdca49b35961ffde7382594870dea0bd12d1b3a Mon Sep 17 00:00:00 2001 From: bhangare Date: Sat, 11 Mar 2017 01:32:46 -0800 Subject: [PATCH 05/16] Changes in vimconn_vmware.py : Bug DE193 fixed: Added exceptions for PyVcloud library APIs. Assigned defaults if VNFD has incomplete IP profile for vmware connector. Signed-off-by: bhangare --- vimconn_vmware.py | 452 ++++++++++++++++++++++++++-------------------- 1 file changed, 252 insertions(+), 200 deletions(-) diff --git a/vimconn_vmware.py b/vimconn_vmware.py index a2242556..5d77aa4a 100644 --- a/vimconn_vmware.py +++ b/vimconn_vmware.py @@ -558,11 +558,11 @@ class vimconnector(vimconn.vimconnector): if vdc is None: raise vimconn.vimconnConnectionException("Can't retrieve information for a VDC {}.".format(self.tenant_name)) - vdcid = vdc.get_id().split(":")[3] - networks = vca.get_networks(vdc.get_name()) - network_list = [] - try: + vdcid = vdc.get_id().split(":")[3] + networks = vca.get_networks(vdc.get_name()) + network_list = [] + for network in networks: filter_entry = {} net_uuid = network.get_id().split(":") @@ -610,13 +610,13 @@ class vimconnector(vimconn.vimconnector): if not vca: raise vimconn.vimconnConnectionException("self.connect() is failed") - vdc = vca.get_vdc(self.tenant_name) - vdc_id = vdc.get_id().split(":")[3] + try: + vdc = vca.get_vdc(self.tenant_name) + vdc_id = vdc.get_id().split(":")[3] - networks = vca.get_networks(vdc.get_name()) - filter_dict = {} + networks = vca.get_networks(vdc.get_name()) + filter_dict = {} - try: for network in networks: vdc_network_id = network.get_id().split(":") if len(vdc_network_id) == 4 and vdc_network_id[3] == net_id: @@ -844,117 +844,124 @@ class vimconnector(vimconn.vimconnector): # create vApp Template and check the status if vCD able to read OVF it will respond with appropirate # status change. # if VCD can parse OVF we upload VMDK file - for catalog in vca.get_catalogs(): - if catalog_name != catalog.name: - continue - link = filter(lambda link: link.get_type() == "application/vnd.vmware.vcloud.media+xml" and - link.get_rel() == 'add', catalog.get_Link()) - assert len(link) == 1 - data = """ - %s vApp Template - """ % (escape(catalog_name), escape(description)) - headers = vca.vcloud_session.get_vcloud_headers() - headers['Content-Type'] = 'application/vnd.vmware.vcloud.uploadVAppTemplateParams+xml' - response = Http.post(link[0].get_href(), headers=headers, data=data, verify=vca.verify, logger=self.logger) - if response.status_code == requests.codes.created: - catalogItem = XmlElementTree.fromstring(response.content) - entity = [child for child in catalogItem if - child.get("type") == "application/vnd.vmware.vcloud.vAppTemplate+xml"][0] - href = entity.get('href') - template = href - response = Http.get(href, headers=vca.vcloud_session.get_vcloud_headers(), - verify=vca.verify, logger=self.logger) - - if response.status_code == requests.codes.ok: - media = mediaType.parseString(response.content, True) - link = filter(lambda link: link.get_rel() == 'upload:default', - media.get_Files().get_File()[0].get_Link())[0] - headers = vca.vcloud_session.get_vcloud_headers() - headers['Content-Type'] = 'Content-Type text/xml' - response = Http.put(link.get_href(), - data=open(media_file_name, 'rb'), - headers=headers, + try: + for catalog in vca.get_catalogs(): + if catalog_name != catalog.name: + continue + link = filter(lambda link: link.get_type() == "application/vnd.vmware.vcloud.media+xml" and + link.get_rel() == 'add', catalog.get_Link()) + assert len(link) == 1 + data = """ + %s vApp Template + """ % (escape(catalog_name), escape(description)) + headers = vca.vcloud_session.get_vcloud_headers() + headers['Content-Type'] = 'application/vnd.vmware.vcloud.uploadVAppTemplateParams+xml' + response = Http.post(link[0].get_href(), headers=headers, data=data, verify=vca.verify, logger=self.logger) + if response.status_code == requests.codes.created: + catalogItem = XmlElementTree.fromstring(response.content) + entity = [child for child in catalogItem if + child.get("type") == "application/vnd.vmware.vcloud.vAppTemplate+xml"][0] + href = entity.get('href') + template = href + response = Http.get(href, headers=vca.vcloud_session.get_vcloud_headers(), verify=vca.verify, logger=self.logger) - if response.status_code != requests.codes.ok: - self.logger.debug( - "Failed create vApp template for catalog name {} and image {}".format(catalog_name, - media_file_name)) - return False - - # TODO fix this with aync block - time.sleep(5) - - self.logger.debug("vApp template for catalog name {} and image {}".format(catalog_name, media_file_name)) - # uploading VMDK file - # check status of OVF upload and upload remaining files. - response = Http.get(template, - headers=vca.vcloud_session.get_vcloud_headers(), - verify=vca.verify, - logger=self.logger) + if response.status_code == requests.codes.ok: + media = mediaType.parseString(response.content, True) + link = filter(lambda link: link.get_rel() == 'upload:default', + media.get_Files().get_File()[0].get_Link())[0] + headers = vca.vcloud_session.get_vcloud_headers() + headers['Content-Type'] = 'Content-Type text/xml' + response = Http.put(link.get_href(), + data=open(media_file_name, 'rb'), + headers=headers, + verify=vca.verify, logger=self.logger) + if response.status_code != requests.codes.ok: + self.logger.debug( + "Failed create vApp template for catalog name {} and image {}".format(catalog_name, + media_file_name)) + return False + + # TODO fix this with aync block + time.sleep(5) + + self.logger.debug("vApp template for catalog name {} and image {}".format(catalog_name, media_file_name)) + + # uploading VMDK file + # check status of OVF upload and upload remaining files. + response = Http.get(template, + headers=vca.vcloud_session.get_vcloud_headers(), + verify=vca.verify, + logger=self.logger) - if response.status_code == requests.codes.ok: - media = mediaType.parseString(response.content, True) - number_of_files = len(media.get_Files().get_File()) - for index in xrange(0, number_of_files): - links_list = filter(lambda link: link.get_rel() == 'upload:default', - media.get_Files().get_File()[index].get_Link()) - for link in links_list: - # we skip ovf since it already uploaded. - if 'ovf' in link.get_href(): - continue - # The OVF file and VMDK must be in a same directory - head, tail = os.path.split(media_file_name) - file_vmdk = head + '/' + link.get_href().split("/")[-1] - if not os.path.isfile(file_vmdk): - return False - statinfo = os.stat(file_vmdk) - if statinfo.st_size == 0: - return False - hrefvmdk = link.get_href() - - if progress: - print("Uploading file: {}".format(file_vmdk)) - if progress: - widgets = ['Uploading file: ', Percentage(), ' ', Bar(), ' ', ETA(), ' ', - FileTransferSpeed()] - progress_bar = ProgressBar(widgets=widgets, maxval=statinfo.st_size).start() - - bytes_transferred = 0 - f = open(file_vmdk, 'rb') - while bytes_transferred < statinfo.st_size: - my_bytes = f.read(chunk_bytes) - if len(my_bytes) <= chunk_bytes: - headers = vca.vcloud_session.get_vcloud_headers() - headers['Content-Range'] = 'bytes %s-%s/%s' % ( - bytes_transferred, len(my_bytes) - 1, statinfo.st_size) - headers['Content-Length'] = str(len(my_bytes)) - response = Http.put(hrefvmdk, - headers=headers, - data=my_bytes, - verify=vca.verify, - logger=None) - - if response.status_code == requests.codes.ok: - bytes_transferred += len(my_bytes) - if progress: - progress_bar.update(bytes_transferred) - else: - self.logger.debug( - 'file upload failed with error: [%s] %s' % (response.status_code, - response.content)) - - f.close() - return False - f.close() - if progress: - progress_bar.finish() - time.sleep(10) - return True - else: - self.logger.debug("Failed retrieve vApp template for catalog name {} for OVF {}". - format(catalog_name, media_file_name)) - return False + if response.status_code == requests.codes.ok: + media = mediaType.parseString(response.content, True) + number_of_files = len(media.get_Files().get_File()) + for index in xrange(0, number_of_files): + links_list = filter(lambda link: link.get_rel() == 'upload:default', + media.get_Files().get_File()[index].get_Link()) + for link in links_list: + # we skip ovf since it already uploaded. + if 'ovf' in link.get_href(): + continue + # The OVF file and VMDK must be in a same directory + head, tail = os.path.split(media_file_name) + file_vmdk = head + '/' + link.get_href().split("/")[-1] + if not os.path.isfile(file_vmdk): + return False + statinfo = os.stat(file_vmdk) + if statinfo.st_size == 0: + return False + hrefvmdk = link.get_href() + + if progress: + print("Uploading file: {}".format(file_vmdk)) + if progress: + widgets = ['Uploading file: ', Percentage(), ' ', Bar(), ' ', ETA(), ' ', + FileTransferSpeed()] + progress_bar = ProgressBar(widgets=widgets, maxval=statinfo.st_size).start() + + bytes_transferred = 0 + f = open(file_vmdk, 'rb') + while bytes_transferred < statinfo.st_size: + my_bytes = f.read(chunk_bytes) + if len(my_bytes) <= chunk_bytes: + headers = vca.vcloud_session.get_vcloud_headers() + headers['Content-Range'] = 'bytes %s-%s/%s' % ( + bytes_transferred, len(my_bytes) - 1, statinfo.st_size) + headers['Content-Length'] = str(len(my_bytes)) + response = Http.put(hrefvmdk, + headers=headers, + data=my_bytes, + verify=vca.verify, + logger=None) + + if response.status_code == requests.codes.ok: + bytes_transferred += len(my_bytes) + if progress: + progress_bar.update(bytes_transferred) + else: + self.logger.debug( + 'file upload failed with error: [%s] %s' % (response.status_code, + response.content)) + + f.close() + return False + f.close() + if progress: + progress_bar.finish() + time.sleep(10) + return True + else: + self.logger.debug("Failed retrieve vApp template for catalog name {} for OVF {}". + format(catalog_name, media_file_name)) + return False + except Exception as exp: + self.logger.debug("Failed while uploading OVF to catalog {} for OVF file {} with Exception {}" + .format(catalog_name,media_file_name, exp)) + raise vimconn.vimconnException( + "Failed while uploading OVF to catalog {} for OVF file {} with Exception {}" + .format(catalog_name,media_file_name, exp)) self.logger.debug("Failed retrieve catalog name {} for OVF file {}".format(catalog_name, media_file_name)) return False @@ -1058,7 +1065,12 @@ class vimconnector(vimconn.vimconnector): self.logger.debug("File name {} Catalog Name {} file path {} " "vdc catalog name {}".format(filename, catalog_name, path, catalog_md5_name)) - catalogs = vca.get_catalogs() + try: + catalogs = vca.get_catalogs() + except Exception as exp: + self.logger.debug("Failed get catalogs() with Exception {} ".format(exp)) + raise vimconn.vimconnException("Failed get catalogs() with Exception {} ".format(exp)) + if len(catalogs) == 0: self.logger.info("Creating a new catalog entry {} in vcloud director".format(catalog_name)) result = self.create_vimcatalog(vca, catalog_md5_name) @@ -1325,25 +1337,37 @@ class vimconnector(vimconn.vimconnector): # use: 'data', 'bridge', 'mgmt' # create vApp. Set vcpu and ram based on flavor id. - vapptask = vca.create_vapp(self.tenant_name, vmname_andid, templateName, - self.get_catalogbyid(image_id, catalogs), - network_name=None, # None while creating vapp - network_mode=network_mode, - vm_name=vmname_andid, - vm_cpus=vm_cpus, # can be None if flavor is None - vm_memory=vm_memory) # can be None if flavor is None - - if vapptask is None or vapptask is False: - raise vimconn.vimconnUnexpectedResponse("new_vminstance(): failed deploy vApp {}".format(vmname_andid)) - if type(vapptask) is VappTask: - vca.block_until_completed(vapptask) + try: + vapptask = vca.create_vapp(self.tenant_name, vmname_andid, templateName, + self.get_catalogbyid(image_id, catalogs), + network_name=None, # None while creating vapp + network_mode=network_mode, + vm_name=vmname_andid, + vm_cpus=vm_cpus, # can be None if flavor is None + vm_memory=vm_memory) # can be None if flavor is None + + if vapptask is None or vapptask is False: + raise vimconn.vimconnUnexpectedResponse( + "new_vminstance(): failed to create vApp {}".format(vmname_andid)) + if type(vapptask) is VappTask: + vca.block_until_completed(vapptask) + + except Exception as exp: + raise vimconn.vimconnUnexpectedResponse( + "new_vminstance(): failed to create vApp {} with Exception:{}".format(vmname_andid, exp)) # we should have now vapp in undeployed state. - vapp = vca.get_vapp(vca.get_vdc(self.tenant_name), vmname_andid) - vapp_uuid = self.get_vappid(vca.get_vdc(self.tenant_name), vmname_andid) + try: + vapp = vca.get_vapp(vca.get_vdc(self.tenant_name), vmname_andid) + vapp_uuid = self.get_vappid(vca.get_vdc(self.tenant_name), vmname_andid) + except Exception as exp: + raise vimconn.vimconnUnexpectedResponse( + "new_vminstance(): Failed to retrieve vApp {} after creation: Exception:{}" + .format(vmname_andid, exp)) + if vapp is None: raise vimconn.vimconnUnexpectedResponse( - "new_vminstance(): Failed failed retrieve vApp {} after we deployed".format( + "new_vminstance(): Failed to retrieve vApp {} after creation".format( vmname_andid)) #Add PCI passthrough configrations @@ -1415,41 +1439,48 @@ class vimconnector(vimconn.vimconnector): if type(task) is GenericTask: vca.block_until_completed(task) nicIndex += 1 - except KeyError: - # it might be a case if specific mandatory entry in dict is empty - self.logger.debug("Key error {}".format(KeyError.message)) - raise vimconn.vimconnUnexpectedResponse("new_vminstance(): Failed create new vm instance {}".format(name)) - # deploy and power on vm - self.logger.debug("new_vminstance(): Deploying vApp {} ".format(name)) - deploytask = vapp.deploy(powerOn=False) - if type(deploytask) is GenericTask: - vca.block_until_completed(deploytask) - - # If VM has PCI devices reserve memory for VM - if PCI_devices_status and vm_obj and vcenter_conect: - memReserve = vm_obj.config.hardware.memoryMB - spec = vim.vm.ConfigSpec() - spec.memoryAllocation = vim.ResourceAllocationInfo(reservation=memReserve) - task = vm_obj.ReconfigVM_Task(spec=spec) - if task: - result = self.wait_for_vcenter_task(task, vcenter_conect) - self.logger.info("Reserved memmoery {} MB for "\ - "VM VM status: {}".format(str(memReserve),result)) - else: - self.logger.info("Fail to reserved memmoery {} to VM {}".format( - str(memReserve),str(vm_obj))) + # deploy and power on vm + self.logger.debug("new_vminstance(): Deploying vApp {} ".format(name)) + deploytask = vapp.deploy(powerOn=False) + if type(deploytask) is GenericTask: + vca.block_until_completed(deploytask) + + # If VM has PCI devices reserve memory for VM + if PCI_devices_status and vm_obj and vcenter_conect: + memReserve = vm_obj.config.hardware.memoryMB + spec = vim.vm.ConfigSpec() + spec.memoryAllocation = vim.ResourceAllocationInfo(reservation=memReserve) + task = vm_obj.ReconfigVM_Task(spec=spec) + if task: + result = self.wait_for_vcenter_task(task, vcenter_conect) + self.logger.info("Reserved memmoery {} MB for "\ + "VM VM status: {}".format(str(memReserve),result)) + else: + self.logger.info("Fail to reserved memmoery {} to VM {}".format( + str(memReserve),str(vm_obj))) - self.logger.debug("new_vminstance(): power on vApp {} ".format(name)) - poweron_task = vapp.poweron() - if type(poweron_task) is GenericTask: - vca.block_until_completed(poweron_task) + self.logger.debug("new_vminstance(): power on vApp {} ".format(name)) + poweron_task = vapp.poweron() + if type(poweron_task) is GenericTask: + vca.block_until_completed(poweron_task) + + except Exception as exp : + # it might be a case if specific mandatory entry in dict is empty or some other pyVcloud exception + self.logger.debug("new_vminstance(): Failed create new vm instance {}".format(name, exp)) + raise vimconn.vimconnException("new_vminstance(): Failed create new vm instance {}".format(name, exp)) # check if vApp deployed and if that the case return vApp UUID otherwise -1 wait_time = 0 vapp_uuid = None while wait_time <= MAX_WAIT_TIME: - vapp = vca.get_vapp(vca.get_vdc(self.tenant_name), vmname_andid) + try: + vapp = vca.get_vapp(vca.get_vdc(self.tenant_name), vmname_andid) + except Exception as exp: + raise vimconn.vimconnUnexpectedResponse( + "new_vminstance(): Failed to retrieve vApp {} after creation: Exception:{}" + .format(vmname_andid, exp)) + if vapp and vapp.me.deployed: vapp_uuid = self.get_vappid(vca.get_vdc(self.tenant_name), vmname_andid) break @@ -1719,18 +1750,18 @@ class vimconnector(vimconn.vimconnector): vmname = self.get_namebyvappid(vca, vdc, vmuuid) if vmname is not None: - the_vapp = vca.get_vapp(vdc, vmname) - vm_info = the_vapp.get_vms_details() - vm_status = vm_info[0]['status'] - vm_pci_details = self.get_vm_pci_details(vmuuid) - vm_info[0].update(vm_pci_details) + try: + the_vapp = vca.get_vapp(vdc, vmname) + vm_info = the_vapp.get_vms_details() + vm_status = vm_info[0]['status'] + vm_pci_details = self.get_vm_pci_details(vmuuid) + vm_info[0].update(vm_pci_details) - vm_dict = {'status': vcdStatusCode2manoFormat[the_vapp.me.get_status()], - 'error_msg': vcdStatusCode2manoFormat[the_vapp.me.get_status()], - 'vim_info': yaml.safe_dump(vm_info), 'interfaces': []} + vm_dict = {'status': vcdStatusCode2manoFormat[the_vapp.me.get_status()], + 'error_msg': vcdStatusCode2manoFormat[the_vapp.me.get_status()], + 'vim_info': yaml.safe_dump(vm_info), 'interfaces': []} - # get networks - try: + # get networks vm_app_networks = the_vapp.get_vms_network_info() for vapp_network in vm_app_networks: for vm_network in vapp_network: @@ -1747,8 +1778,8 @@ class vimconnector(vimconn.vimconnector): vm_dict["interfaces"].append(interface) # add a vm to vm dict vms_dict.setdefault(vmuuid, vm_dict) - except KeyError: - self.logger.debug("Error in respond {}".format(KeyError.message)) + except Exception as exp: + self.logger.debug("Error in response {}".format(exp)) self.logger.debug(traceback.format_exc()) return vms_dict @@ -1828,8 +1859,9 @@ class vimconnector(vimconn.vimconnector): # server.create_image() else: pass - except: - pass + except Exception as exp : + self.logger.debug("action_vminstance: Failed with Exception {}".format(exp)) + raise vimconn.vimconnException("action_vminstance: Failed with Exception {}".format(exp)) def get_vminstance_console(self, vm_id, console_type="vnc"): """ @@ -2303,8 +2335,8 @@ class vimconnector(vimconn.vimconnector): if network_uuid is None: return network_uuid - content = self.get_network_action(network_uuid=network_uuid) try: + content = self.get_network_action(network_uuid=network_uuid) vm_list_xmlroot = XmlElementTree.fromstring(content) network_configuration['status'] = vm_list_xmlroot.get("status") @@ -2320,8 +2352,9 @@ class vimconnector(vimconn.vimconnector): if tagKey != "": network_configuration[tagKey] = configuration.text.strip() return network_configuration - except: - pass + except Exception as exp : + self.logger.debug("get_vcd_network: Failed with Exception {}".format(exp)) + raise vimconn.vimconnException("get_vcd_network: Failed with Exception {}".format(exp)) return network_configuration @@ -2387,7 +2420,7 @@ class vimconnector(vimconn.vimconnector): vm_list_xmlroot = XmlElementTree.fromstring(content) vcd_uuid = vm_list_xmlroot.get('id').split(":") if len(vcd_uuid) == 4: - self.logger.info("Create new network name: {} uuid: {}".format(network_name, vcd_uuid[3])) + self.logger.info("Created new network name: {} uuid: {}".format(network_name, vcd_uuid[3])) return vcd_uuid[3] except: self.logger.debug("Failed create network {}".format(network_name)) @@ -2473,26 +2506,45 @@ class vimconnector(vimconn.vimconnector): except: return None - #Configure IP profile of the network - ip_profile = ip_profile if ip_profile is not None else DEFAULT_IP_PROFILE - - gateway_address=ip_profile['gateway_address'] - dhcp_count=int(ip_profile['dhcp_count']) - subnet_address=self.convert_cidr_to_netmask(ip_profile['subnet_address']) - - if ip_profile['dhcp_enabled']==True: - dhcp_enabled='true' - else: - dhcp_enabled='false' - dhcp_start_address=ip_profile['dhcp_start_address'] + try: + #Configure IP profile of the network + ip_profile = ip_profile if ip_profile is not None else DEFAULT_IP_PROFILE + + if 'gateway_address' not in ip_profile or ip_profile['gateway_address'] is None: + ip_profile['gateway_address']=DEFAULT_IP_PROFILE['gateway_address'] + if 'dhcp_count' not in ip_profile or ip_profile['dhcp_count'] is None: + ip_profile['dhcp_count']=DEFAULT_IP_PROFILE['dhcp_count'] + if 'subnet_address' not in ip_profile or ip_profile['subnet_address'] is None: + ip_profile['subnet_address']=DEFAULT_IP_PROFILE['subnet_address'] + if 'dhcp_enabled' not in ip_profile or ip_profile['dhcp_enabled'] is None: + ip_profile['dhcp_enabled']=DEFAULT_IP_PROFILE['dhcp_enabled'] + if 'dhcp_start_address' not in ip_profile or ip_profile['dhcp_start_address'] is None: + ip_profile['dhcp_start_address']=DEFAULT_IP_PROFILE['dhcp_start_address'] + if 'ip_version' not in ip_profile or ip_profile['ip_version'] is None: + ip_profile['ip_version']=DEFAULT_IP_PROFILE['ip_version'] + if 'dns_address' not in ip_profile or ip_profile['dns_address'] is None: + ip_profile['dns_address']=DEFAULT_IP_PROFILE['dns_address'] + + gateway_address=ip_profile['gateway_address'] + dhcp_count=int(ip_profile['dhcp_count']) + subnet_address=self.convert_cidr_to_netmask(ip_profile['subnet_address']) + + if ip_profile['dhcp_enabled']==True: + dhcp_enabled='true' + else: + dhcp_enabled='false' + dhcp_start_address=ip_profile['dhcp_start_address'] - #derive dhcp_end_address from dhcp_start_address & dhcp_count - end_ip_int = int(netaddr.IPAddress(dhcp_start_address)) - end_ip_int += dhcp_count - 1 - dhcp_end_address = str(netaddr.IPAddress(end_ip_int)) + #derive dhcp_end_address from dhcp_start_address & dhcp_count + end_ip_int = int(netaddr.IPAddress(dhcp_start_address)) + end_ip_int += dhcp_count - 1 + dhcp_end_address = str(netaddr.IPAddress(end_ip_int)) - ip_version=ip_profile['ip_version'] - dns_address=ip_profile['dns_address'] + ip_version=ip_profile['ip_version'] + dns_address=ip_profile['dns_address'] + except KeyError as exp: + self.logger.debug("Create Network REST: Key error {}".format(exp)) + raise vimconn.vimconnException("Create Network REST: Key error{}".format(exp)) # either use client provided UUID or search for a first available # if both are not defined we return none @@ -2569,8 +2621,8 @@ class vimconnector(vimconn.vimconnector): logger=vca.logger) if response.status_code != 201: - self.logger.debug("Create Network POST REST API call failed. Return status code {}" - .format(response.status_code)) + self.logger.debug("Create Network POST REST API call failed. Return status code {}, Response content: {}" + .format(response.status_code,response.content)) else: network = networkType.parseString(response.content, True) create_nw_task = network.get_Tasks().get_Task()[0] @@ -2578,7 +2630,7 @@ class vimconnector(vimconn.vimconnector): # if we all ok we respond with content after network creation completes # otherwise by default return None if create_nw_task is not None: - self.logger.debug("Create Network REST : Waiting for Nw creation complete") + self.logger.debug("Create Network REST : Waiting for Network creation complete") status = vca.block_until_completed(create_nw_task) if status: return response.content -- 2.25.1 From 95a1ae548b039be307efe8956dbc464081d9e445 Mon Sep 17 00:00:00 2001 From: tierno Date: Thu, 16 Mar 2017 13:17:24 +0100 Subject: [PATCH 06/16] vim_thread. Logs and store error status at vim fail Change-Id: Iaed74108b104c92e5e5763b7ce469b6fcb7015b1 Signed-off-by: tierno --- vim_thread.py | 41 +++++++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/vim_thread.py b/vim_thread.py index 42279a23..780d6e5d 100644 --- a/vim_thread.py +++ b/vim_thread.py @@ -142,18 +142,31 @@ class vim_thread(threading.Thread): def terminate(self, task): return True, None + def _format_vim_error_msg(self, error_text): + if len(error_text) >= 1024: + return error_text[:516] + " ... " + error_text[-500:] + return error_text + def new_net(self, task): try: task_id = task["id"] params = task["params"] net_id = self.vim.new_network(*params) - with self.db_lock: - self.db.update_rows("instance_nets", UPDATE={"vim_net_id": net_id}, WHERE={"vim_net_id": task_id}) - return True, net_id - except db_base_Exception as e: - self.logger.error("Error updating database %s", str(e)) + try: + with self.db_lock: + self.db.update_rows("instance_nets", UPDATE={"vim_net_id": net_id}, WHERE={"vim_net_id": task_id}) + except db_base_Exception as e: + self.logger.error("Error updating database %s", str(e)) return True, net_id except vimconn.vimconnException as e: + self.logger.error("Error creating NET, task=%s: %s", str(task_id), str(e)) + try: + with self.db_lock: + self.db.update_rows("instance_nets", + UPDATE={"error_msg": self._format_vim_error_msg(str(e)), "status": "VIM_ERROR"}, + WHERE={"vim_net_id": task_id}) + except db_base_Exception as e: + self.logger.error("Error updating database %s", str(e)) return False, str(e) def new_vm(self, task): @@ -178,13 +191,21 @@ class vim_thread(threading.Thread): return False, "Error trying to map from task_id={} to task result: {}".format(net["net_id"], str(e)) vm_id = self.vim.new_vminstance(*params) - with self.db_lock: - self.db.update_rows("instance_vms", UPDATE={"vim_vm_id": vm_id}, WHERE={"vim_vm_id": task_id}) - return True, vm_id - except db_base_Exception as e: - self.logger.error("Error updtaing database %s", str(e)) + try: + with self.db_lock: + self.db.update_rows("instance_vms", UPDATE={"vim_vm_id": vm_id}, WHERE={"vim_vm_id": task_id}) + except db_base_Exception as e: + self.logger.error("Error updating database %s", str(e)) return True, vm_id except vimconn.vimconnException as e: + self.logger.error("Error creating VM, task=%s: %s", str(task_id), str(e)) + try: + with self.db_lock: + self.db.update_rows("instance_vms", + UPDATE={"error_msg": self._format_vim_error_msg(str(e)), "status": "VIM_ERROR"}, + WHERE={"vim_vm_id": task_id}) + except db_base_Exception as e: + self.logger.error("Error updating database %s", str(e)) return False, str(e) def del_vm(self, task): -- 2.25.1 From 3ac5dc4a362acf309814d08d4cb3b4d518396718 Mon Sep 17 00:00:00 2001 From: kasar Date: Wed, 15 Mar 2017 06:28:22 -0700 Subject: [PATCH 07/16] 1.Modified code for instance actions 2.Added code for network adapter type support Change-Id: I406d185ebbdf93ddb0c9e373eae1cd037877ef90 Signed-off-by: kasar --- vimconn_vmware.py | 222 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 179 insertions(+), 43 deletions(-) diff --git a/vimconn_vmware.py b/vimconn_vmware.py index 5d77aa4a..b7fbf7cb 100644 --- a/vimconn_vmware.py +++ b/vimconn_vmware.py @@ -1431,13 +1431,24 @@ class vimconnector(vimconn.vimconnector): if type(task) is GenericTask: vca.block_until_completed(task) # connect network to VM - with all DHCP by default - self.logger.info("new_vminstance(): Connecting VM to a network {}".format(nets[0].name)) - task = vapp.connect_vms(nets[0].name, - connection_index=nicIndex, - connections_primary_index=primary_nic_index, - ip_allocation_mode='DHCP') - if type(task) is GenericTask: - vca.block_until_completed(task) + + type_list = ['PF','VF','VFnotShared'] + if 'type' in net and net['type'] not in type_list: + # fetching nic type from vnf + if 'model' in net: + nic_type = net['model'] + self.logger.info("new_vminstance(): adding network adapter "\ + "to a network {}".format(nets[0].name)) + self.add_network_adapter_to_vms(vapp, nets[0].name, + primary_nic_index, + nicIndex, + nic_type=nic_type) + else: + self.logger.info("new_vminstance(): adding network adapter "\ + "to a network {}".format(nets[0].name)) + self.add_network_adapter_to_vms(vapp, nets[0].name, + primary_nic_index, + nicIndex) nicIndex += 1 # deploy and power on vm @@ -1813,56 +1824,54 @@ class vimconnector(vimconn.vimconnector): if "start" in action_dict: vm_info = the_vapp.get_vms_details() vm_status = vm_info[0]['status'] - self.logger.info("Power on vApp: vm_status:{} {}".format(type(vm_status),vm_status)) + self.logger.info("action_vminstance: Power on vApp: {}".format(vapp_name)) if vm_status == "Suspended" or vm_status == "Powered off": power_on_task = the_vapp.poweron() - if power_on_task is not None and type(power_on_task) is GenericTask: - result = vca.block_until_completed(power_on_task) - if result: - self.logger.info("action_vminstance: Powered on vApp: {}".format(vapp_name)) - else: - self.logger.info("action_vminstance: Failed to power on vApp: {}".format(vapp_name)) - else: - self.logger.info("action_vminstance: Wait for vApp {} to power on".format(vapp_name)) - elif "rebuild" in action_dict: - self.logger.info("action_vminstance: Rebuilding vApp: {}".format(vapp_name)) - power_on_task = the_vapp.deploy(powerOn=True) - if type(power_on_task) is GenericTask: result = vca.block_until_completed(power_on_task) - if result: - self.logger.info("action_vminstance: Rebuilt vApp: {}".format(vapp_name)) - else: - self.logger.info("action_vminstance: Failed to rebuild vApp: {}".format(vapp_name)) - else: - self.logger.info("action_vminstance: Wait for vApp rebuild {} to power on".format(vapp_name)) + self.instance_actions_result("start", result, vapp_name) + elif "rebuild" in action_dict: + self.logger.info("action_vminstance: Rebuild vApp: {}".format(vapp_name)) + rebuild_task = the_vapp.deploy(powerOn=True) + result = vca.block_until_completed(rebuild_task) + self.instance_actions_result("rebuild", result, vapp_name) elif "pause" in action_dict: - pass - ## server.pause() + self.logger.info("action_vminstance: pause vApp: {}".format(vapp_name)) + pause_task = the_vapp.undeploy(action='suspend') + result = vca.block_until_completed(pause_task) + self.instance_actions_result("pause", result, vapp_name) elif "resume" in action_dict: - pass - ## server.resume() + self.logger.info("action_vminstance: resume vApp: {}".format(vapp_name)) + power_task = the_vapp.poweron() + result = vca.block_until_completed(power_task) + self.instance_actions_result("resume", result, vapp_name) elif "shutoff" in action_dict or "shutdown" in action_dict: + action_name , value = action_dict.items()[0] + self.logger.info("action_vminstance: {} vApp: {}".format(action_name, vapp_name)) power_off_task = the_vapp.undeploy(action='powerOff') - if type(power_off_task) is GenericTask: - result = vca.block_until_completed(power_off_task) - if result: - self.logger.info("action_vminstance: Powered off vApp: {}".format(vapp_name)) - else: - self.logger.info("action_vminstance: Failed to power off vApp: {}".format(vapp_name)) + result = vca.block_until_completed(power_off_task) + if action_name == "shutdown": + self.instance_actions_result("shutdown", result, vapp_name) else: - self.logger.info("action_vminstance: Wait for vApp {} to power off".format(vapp_name)) + self.instance_actions_result("shutoff", result, vapp_name) elif "forceOff" in action_dict: - the_vapp.reset() - elif "terminate" in action_dict: - the_vapp.delete() - # elif "createImage" in action_dict: - # server.create_image() + result = the_vapp.undeploy(action='force') + self.instance_actions_result("forceOff", result, vapp_name) + elif "reboot" in action_dict: + self.logger.info("action_vminstance: reboot vApp: {}".format(vapp_name)) + reboot_task = the_vapp.reboot() else: - pass + raise vimconn.vimconnException("action_vminstance: Invalid action {} or action is None.".format(action_dict)) + return vm__vim_uuid except Exception as exp : self.logger.debug("action_vminstance: Failed with Exception {}".format(exp)) raise vimconn.vimconnException("action_vminstance: Failed with Exception {}".format(exp)) + def instance_actions_result(self, action, result, vapp_name): + if result: + self.logger.info("action_vminstance: Sucessfully {} the vApp: {}".format(action, vapp_name)) + else: + self.logger.error("action_vminstance: Failed to {} vApp: {}".format(action, vapp_name)) + def get_vminstance_console(self, vm_id, console_type="vnc"): """ Get a console for the virtual machine @@ -3521,3 +3530,130 @@ class vimconnector(vimconn.vimconnector): " for VM : {}".format(exp)) raise vimconn.vimconnException(message=exp) + def add_network_adapter_to_vms(self, vapp, network_name, primary_nic_index, nicIndex, nic_type=None): + """ + Method to add network adapter type to vm + Args : + network_name - name of network + primary_nic_index - int value for primary nic index + nicIndex - int value for nic index + nic_type - specify model name to which add to vm + Returns: + None + """ + try: + vca = self.connect() + if not vca: + raise vimconn.vimconnConnectionException("Failed to connect vCloud director") + + if not nic_type: + for vms in vapp._get_vms(): + vm_id = (vms.id).split(':')[-1] + + url_rest_call = "{}/api/vApp/vm-{}/networkConnectionSection/".format(vca.host, vm_id) + + response = Http.get(url=url_rest_call, + headers=vca.vcloud_session.get_vcloud_headers(), + verify=vca.verify, + logger=vca.logger) + if response.status_code != 200: + self.logger.error("add_network_adapter_to_vms : Get network connection section "\ + "REST call {} failed status code : {}".format(url_rest_call, + response.status_code)) + self.logger.error("Failed reason to get network adapter : {} ".format(response.content)) + + data = response.content + if '' not in data: + item = """{} + + {} + true + DHCP + """.format(primary_nic_index, network_name, nicIndex) + data = data.replace('\n','\n{}\n'.format(item)) + else: + new_item = """ + {} + true + DHCP + """.format(network_name, nicIndex) + data = data.replace('\n','\n{}\n'.format(new_item)) + + headers = vca.vcloud_session.get_vcloud_headers() + headers['Content-Type'] = 'application/vnd.vmware.vcloud.networkConnectionSection+xml' + response = Http.put(url=url_rest_call, headers=headers, data=data, + verify=vca.verify, + logger=vca.logger) + + if response.status_code != 202: + self.logger.error("add_network_adapter_to_vms : Put network adapter REST call {} "\ + "failed status code : {}".format(url_rest_call, + response.status_code)) + self.logger.error("Failed reason to add network adapter: {} ".format(response.content)) + else: + nic_task = taskType.parseString(response.content, True) + if isinstance(nic_task, GenericTask): + vca.block_until_completed(nic_task) + self.logger.info("add_network_adapter_to_vms(): VM {} conneced to "\ + "default NIC type".format(vm_id)) + else: + self.logger.error("add_network_adapter_to_vms(): VM {} failed to "\ + "connect NIC type".format(vm_id)) + else: + for vms in vapp._get_vms(): + vm_id = (vms.id).split(':')[-1] + + url_rest_call = "{}/api/vApp/vm-{}/networkConnectionSection/".format(vca.host, vm_id) + + response = Http.get(url=url_rest_call, + headers=vca.vcloud_session.get_vcloud_headers(), + verify=vca.verify, + logger=vca.logger) + if response.status_code != 200: + self.logger.error("add_network_adapter_to_vms : Get network connection section "\ + "REST call {} failed status code : {}".format(url_rest_call, + response.status_code)) + self.logger.error("Failed reason to get network adapter : {} ".format(response.content)) + + data = response.content + if '' not in data: + item = """{} + + {} + true + DHCP + {} + """.format(primary_nic_index, network_name, nicIndex, nic_type) + data = data.replace('\n','\n{}\n'.format(item)) + else: + new_item = """ + {} + true + DHCP + {} + """.format(network_name, nicIndex, nic_type) + data = data.replace('\n','\n{}\n'.format(new_item)) + + headers = vca.vcloud_session.get_vcloud_headers() + headers['Content-Type'] = 'application/vnd.vmware.vcloud.networkConnectionSection+xml' + response = Http.put(url=url_rest_call, headers=headers, data=data, + verify=vca.verify, + logger=vca.logger) + + if response.status_code != 202: + self.logger.error("add_network_adapter_to_vms : Put network adapter call {} "\ + "failed status code : {}".format(url_rest_call, + response.status_code)) + self.logger.error("Failed reason to add network adapter: {} ".format(response.content)) + else: + nic_task = taskType.parseString(response.content, True) + if isinstance(nic_task, GenericTask): + vca.block_until_completed(nic_task) + self.logger.info("add_network_adapter_to_vms(): VM {} "\ + "conneced to NIC type {}".format(vm_id, nic_type)) + else: + self.logger.error("add_network_adapter_to_vms(): VM {} "\ + "failed to connect NIC type {}".format(vm_id, nic_type)) + except Exception as exp: + self.logger.error("add_network_adapter_to_vms() : exception {} occurred "\ + "while adding Network adapter".format(exp)) -- 2.25.1 From 4cb6e90ab3a28866243ce12a87aac9e1fea23b77 Mon Sep 17 00:00:00 2001 From: kasar Date: Sat, 18 Mar 2017 00:17:27 -0700 Subject: [PATCH 08/16] Fixed review comments Change-Id: I4cadcee95b970ff145ecfb93c57155c6d3175846 Signed-off-by: kasar --- vimconn_vmware.py | 55 ++++++++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/vimconn_vmware.py b/vimconn_vmware.py index b7fbf7cb..9e4e7608 100644 --- a/vimconn_vmware.py +++ b/vimconn_vmware.py @@ -3541,11 +3541,11 @@ class vimconnector(vimconn.vimconnector): Returns: None """ - try: - vca = self.connect() - if not vca: - raise vimconn.vimconnConnectionException("Failed to connect vCloud director") + vca = self.connect() + if not vca: + raise vimconn.vimconnConnectionException("Failed to connect vCloud director") + try: if not nic_type: for vms in vapp._get_vms(): vm_id = (vms.id).split(':')[-1] @@ -3557,10 +3557,12 @@ class vimconnector(vimconn.vimconnector): verify=vca.verify, logger=vca.logger) if response.status_code != 200: - self.logger.error("add_network_adapter_to_vms : Get network connection section "\ - "REST call {} failed status code : {}".format(url_rest_call, - response.status_code)) - self.logger.error("Failed reason to get network adapter : {} ".format(response.content)) + self.logger.error("REST call {} failed reason : {}"\ + "status code : {}".format(url_rest_call, + response.content, + response.status_code)) + raise vimconn.vimconnException("add_network_adapter_to_vms : Failed to get "\ + "network connection section") data = response.content if '' not in data: @@ -3584,12 +3586,13 @@ class vimconnector(vimconn.vimconnector): response = Http.put(url=url_rest_call, headers=headers, data=data, verify=vca.verify, logger=vca.logger) - if response.status_code != 202: - self.logger.error("add_network_adapter_to_vms : Put network adapter REST call {} "\ - "failed status code : {}".format(url_rest_call, - response.status_code)) - self.logger.error("Failed reason to add network adapter: {} ".format(response.content)) + self.logger.error("REST call {} failed reason : {}"\ + "status code : {} ".format(url_rest_call, + response.content, + response.status_code)) + raise vimconn.vimconnException("add_network_adapter_to_vms : Failed to update "\ + "network connection section") else: nic_task = taskType.parseString(response.content, True) if isinstance(nic_task, GenericTask): @@ -3610,11 +3613,12 @@ class vimconnector(vimconn.vimconnector): verify=vca.verify, logger=vca.logger) if response.status_code != 200: - self.logger.error("add_network_adapter_to_vms : Get network connection section "\ - "REST call {} failed status code : {}".format(url_rest_call, - response.status_code)) - self.logger.error("Failed reason to get network adapter : {} ".format(response.content)) - + self.logger.error("REST call {} failed reason : {}"\ + "status code : {}".format(url_rest_call, + response.content, + response.status_code)) + raise vimconn.vimconnException("add_network_adapter_to_vms : Failed to get "\ + "network connection section") data = response.content if '' not in data: item = """{} @@ -3641,10 +3645,12 @@ class vimconnector(vimconn.vimconnector): logger=vca.logger) if response.status_code != 202: - self.logger.error("add_network_adapter_to_vms : Put network adapter call {} "\ - "failed status code : {}".format(url_rest_call, - response.status_code)) - self.logger.error("Failed reason to add network adapter: {} ".format(response.content)) + self.logger.error("REST call {} failed reason : {}"\ + "status code : {}".format(url_rest_call, + response.content, + response.status_code)) + raise vimconn.vimconnException("add_network_adapter_to_vms : Failed to update "\ + "network connection section") else: nic_task = taskType.parseString(response.content, True) if isinstance(nic_task, GenericTask): @@ -3655,5 +3661,6 @@ class vimconnector(vimconn.vimconnector): self.logger.error("add_network_adapter_to_vms(): VM {} "\ "failed to connect NIC type {}".format(vm_id, nic_type)) except Exception as exp: - self.logger.error("add_network_adapter_to_vms() : exception {} occurred "\ - "while adding Network adapter".format(exp)) + self.logger.error("add_network_adapter_to_vms() : exception occurred "\ + "while adding Network adapter") + raise vimconn.vimconnException(message=exp) -- 2.25.1 From 3fbff9b6666ea4981e953c98d236b54fb86c583b Mon Sep 17 00:00:00 2001 From: Pablo Montes Moreno Date: Wed, 8 Mar 2017 11:28:15 +0100 Subject: [PATCH 09/16] Implemented functionality for SDN in RO regarding controllers and port mapping management. Also implemented the creation/deletion of dataplane networks using ovim. To be implemented creation/deletion of dataplane ports using ovim Change-Id: Iff71058bdd51b61f7391a7144636a08c56249498 Signed-off-by: Pablo Montes Moreno --- database_utils/migrate_mano_db.sh | 22 ++ httpserver.py | 160 +++++++- nfvo.py | 198 +++++++++- nfvo_db.py | 2 +- openmano | 382 ++++++++++++++++++- openmano_schemas.py | 72 ++++ openmanoclient.py | 6 +- openmanod.py | 6 +- sdn/sdn_port_mapping.yaml | 44 +++ test/RO_tests/sr_iov/scenario_p2p_sriov.yaml | 41 ++ test/RO_tests/sr_iov/vnfd_1sriov.yaml | 53 +++ vim_thread.py | 50 ++- vimconn.py | 2 + vimconn_openstack.py | 2 + 14 files changed, 1010 insertions(+), 30 deletions(-) create mode 100644 sdn/sdn_port_mapping.yaml create mode 100644 test/RO_tests/sr_iov/scenario_p2p_sriov.yaml create mode 100644 test/RO_tests/sr_iov/vnfd_1sriov.yaml diff --git a/database_utils/migrate_mano_db.sh b/database_utils/migrate_mano_db.sh index a8ec8f91..ba8a8294 100755 --- a/database_utils/migrate_mano_db.sh +++ b/database_utils/migrate_mano_db.sh @@ -187,6 +187,7 @@ DATABASE_TARGET_VER_NUM=0 [ $OPENMANO_VER_NUM -ge 5003 ] && DATABASE_TARGET_VER_NUM=17 #0.5.3 => 17 [ $OPENMANO_VER_NUM -ge 5004 ] && DATABASE_TARGET_VER_NUM=18 #0.5.4 => 18 [ $OPENMANO_VER_NUM -ge 5005 ] && DATABASE_TARGET_VER_NUM=19 #0.5.5 => 19 +[ $OPENMANO_VER_NUM -ge 5009 ] && DATABASE_TARGET_VER_NUM=20 #0.5.9 => 20 #TODO ... put next versions here @@ -730,6 +731,27 @@ function downgrade_from_19(){ echo "DELETE FROM schema_version WHERE version_int='19';" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 } +function upgrade_to_20(){ + echo " upgrade database from version 0.19 to version 0.20" + echo " add column 'sdn_net_id' at table 'instance_nets' and columns 'sdn_port_id', 'compute_node', 'pci' and 'vlan' to table 'instance_interfaces'" + echo "ALTER TABLE instance_nets ADD sdn_net_id varchar(36) DEFAULT NULL NULL COMMENT 'Network id in ovim';" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 + echo "ALTER TABLE instance_interfaces ADD sdn_port_id varchar(36) DEFAULT NULL NULL COMMENT 'Port id in ovim';" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 + echo "ALTER TABLE instance_interfaces ADD compute_node varchar(100) DEFAULT NULL NULL COMMENT 'Compute node id used to specify the SDN port mapping';" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 + echo "ALTER TABLE instance_interfaces ADD pci varchar(12) DEFAULT NULL NULL COMMENT 'PCI of the physical port in the host';" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 + echo "ALTER TABLE instance_interfaces ADD vlan SMALLINT UNSIGNED DEFAULT NULL NULL COMMENT 'VLAN tag used by the port';" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 + echo "INSERT INTO schema_version (version_int, version, openmano_ver, comments, date) VALUES (20, '0.20', '0.5.9', 'Added columns to store dataplane connectivity info', '2017-03-13');" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 +} +function downgrade_from_20(){ + echo " downgrade database from version 0.20 to version 0.19" + echo " remove column 'sdn_net_id' at table 'instance_nets' and columns 'sdn_port_id', 'compute_node', 'pci' and 'vlan' to table 'instance_interfaces'" + echo "ALTER TABLE instance_nets DROP COLUMN sdn_net_id;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 + echo "ALTER TABLE instance_interfaces DROP COLUMN vlan;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 + echo "ALTER TABLE instance_interfaces DROP COLUMN pci;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 + echo "ALTER TABLE instance_interfaces DROP COLUMN compute_node;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 + echo "ALTER TABLE instance_interfaces DROP COLUMN sdn_port_id;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 + echo "DELETE FROM schema_version WHERE version_int='20';" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 +} + function upgrade_to_X(){ echo " change 'datacenter_nets'" echo "ALTER TABLE datacenter_nets ADD COLUMN vim_tenant_id VARCHAR(36) NOT NULL AFTER datacenter_id, DROP INDEX name_datacenter_id, ADD UNIQUE INDEX name_datacenter_id (name, datacenter_id, vim_tenant_id);" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 diff --git a/httpserver.py b/httpserver.py index a0216e12..f412aa17 100644 --- a/httpserver.py +++ b/httpserver.py @@ -42,7 +42,9 @@ from openmano_schemas import vnfd_schema_v01, vnfd_schema_v02, \ scenario_action_schema, instance_scenario_action_schema, instance_scenario_create_schema_v01, \ tenant_schema, tenant_edit_schema,\ datacenter_schema, datacenter_edit_schema, datacenter_action_schema, datacenter_associate_schema,\ - object_schema, netmap_new_schema, netmap_edit_schema + object_schema, netmap_new_schema, netmap_edit_schema, sdn_controller_schema, sdn_controller_edit_schema, \ + sdn_port_mapping_schema + import nfvo import utils from db_base import db_base_Exception @@ -544,6 +546,138 @@ def http_edit_datacenter_id(datacenter_id_name): logger.error("Unexpected exception: ", exc_info=True) bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e)) +@bottle.route(url_base + '//sdn_controllers', method='POST') +def http_post_sdn_controller(tenant_id): + '''insert a sdn controller into the catalogue. ''' + #parse input data + logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url) + http_content,_ = format_in( sdn_controller_schema ) + try: + logger.debug("tenant_id: "+tenant_id) + #logger.debug("content: {}".format(http_content['sdn_controller'])) + + data = nfvo.sdn_controller_create(mydb, tenant_id, http_content['sdn_controller']) + return format_out({"sdn_controller": nfvo.sdn_controller_list(mydb, tenant_id, data)}) + except (nfvo.NfvoException, db_base_Exception) as e: + logger.error("http_post_sdn_controller error {}: {}".format(e.http_code, str(e))) + bottle.abort(e.http_code, str(e)) + except Exception as e: + logger.error("Unexpected exception: ", exc_info=True) + bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e)) + +@bottle.route(url_base + '//sdn_controllers/', method='PUT') +def http_put_sdn_controller_update(tenant_id, controller_id): + '''Update sdn controller''' + #parse input data + logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url) + http_content,_ = format_in( sdn_controller_edit_schema ) +# r = utils.remove_extra_items(http_content, datacenter_schema) +# if r: +# logger.debug("Remove received extra items %s", str(r)) + try: + #logger.debug("tenant_id: "+tenant_id) + logger.debug("content: {}".format(http_content['sdn_controller'])) + + data = nfvo.sdn_controller_update(mydb, tenant_id, controller_id, http_content['sdn_controller']) + return format_out({"sdn_controller": nfvo.sdn_controller_list(mydb, tenant_id, controller_id)}) + + except (nfvo.NfvoException, db_base_Exception) as e: + logger.error("http_post_sdn_controller error {}: {}".format(e.http_code, str(e))) + bottle.abort(e.http_code, str(e)) + except Exception as e: + logger.error("Unexpected exception: ", exc_info=True) + bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e)) + +@bottle.route(url_base + '//sdn_controllers', method='GET') +def http_get_sdn_controller(tenant_id): + '''get sdn controllers list, can use both uuid or name''' + try: + logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url) + + data = {'sdn_controllers': nfvo.sdn_controller_list(mydb, tenant_id)} + return format_out(data) + except (nfvo.NfvoException, db_base_Exception) as e: + logger.error("http_get_sdn_controller error {}: {}".format(e.http_code, str(e))) + bottle.abort(e.http_code, str(e)) + except Exception as e: + logger.error("Unexpected exception: ", exc_info=True) + bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e)) + +@bottle.route(url_base + '//sdn_controllers/', method='GET') +def http_get_sdn_controller_id(tenant_id, controller_id): + '''get sdn controller details, can use both uuid or name''' + try: + logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url) + data = nfvo.sdn_controller_list(mydb, tenant_id, controller_id) + return format_out({"sdn_controllers": data}) + except (nfvo.NfvoException, db_base_Exception) as e: + logger.error("http_get_sdn_controller_id error {}: {}".format(e.http_code, str(e))) + bottle.abort(e.http_code, str(e)) + except Exception as e: + logger.error("Unexpected exception: ", exc_info=True) + bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e)) + +@bottle.route(url_base + '//sdn_controllers/', method='DELETE') +def http_delete_sdn_controller_id(tenant_id, controller_id): + '''delete sdn controller, can use both uuid or name''' + try: + logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url) + data = nfvo.sdn_controller_delete(mydb, tenant_id, controller_id) + return format_out(data) + except (nfvo.NfvoException, db_base_Exception) as e: + logger.error("http_delete_sdn_controller_id error {}: {}".format(e.http_code, str(e))) + bottle.abort(e.http_code, str(e)) + except Exception as e: + logger.error("Unexpected exception: ", exc_info=True) + bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e)) + +@bottle.route(url_base + '//datacenters//sdn_mapping', method='POST') +def http_post_datacenter_sdn_port_mapping(tenant_id, datacenter_id): + '''Set the sdn port mapping for a datacenter. ''' + #parse input data + logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url) + http_content, _ = format_in(sdn_port_mapping_schema) +# r = utils.remove_extra_items(http_content, datacenter_schema) +# if r: +# logger.debug("Remove received extra items %s", str(r)) + try: + data = nfvo.datacenter_sdn_port_mapping_set(mydb, tenant_id, datacenter_id, http_content['sdn_port_mapping']) + return format_out({"sdn_port_mapping": data}) + except (nfvo.NfvoException, db_base_Exception) as e: + logger.error("http_post_datacenter_sdn_port_mapping error {}: {}".format(e.http_code, str(e))) + bottle.abort(e.http_code, str(e)) + except Exception as e: + logger.error("Unexpected exception: ", exc_info=True) + bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e)) + +@bottle.route(url_base + '//datacenters//sdn_mapping', method='GET') +def http_get_datacenter_sdn_port_mapping(tenant_id, datacenter_id): + '''get datacenter sdn mapping details, can use both uuid or name''' + try: + logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url) + + data = nfvo.datacenter_sdn_port_mapping_list(mydb, tenant_id, datacenter_id) + return format_out({"sdn_port_mapping": data}) + except (nfvo.NfvoException, db_base_Exception) as e: + logger.error("http_get_datacenter_sdn_port_mapping error {}: {}".format(e.http_code, str(e))) + bottle.abort(e.http_code, str(e)) + except Exception as e: + logger.error("Unexpected exception: ", exc_info=True) + bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e)) + +@bottle.route(url_base + '//datacenters//sdn_mapping', method='DELETE') +def http_delete_datacenter_sdn_port_mapping(tenant_id, datacenter_id): + '''clean datacenter sdn mapping, can use both uuid or name''' + try: + logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url) + data = nfvo.datacenter_sdn_port_mapping_delete(mydb, tenant_id, datacenter_id) + return format_out({"result": data}) + except (nfvo.NfvoException, db_base_Exception) as e: + logger.error("http_delete_datacenter_sdn_port_mapping error {}: {}".format(e.http_code, str(e))) + bottle.abort(e.http_code, str(e)) + except Exception as e: + logger.error("Unexpected exception: ", exc_info=True) + bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e)) @bottle.route(url_base + '//datacenters//networks', method='GET') #deprecated @bottle.route(url_base + '//datacenters//netmaps', method='GET') @@ -739,6 +873,30 @@ def http_associate_datacenters(tenant_id, datacenter_id): logger.error("Unexpected exception: ", exc_info=True) bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e)) +@bottle.route(url_base + '//datacenters/', method='PUT') +def http_associate_datacenters_edit(tenant_id, datacenter_id): + '''associate an existing datacenter to a this tenant. ''' + logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url) + #parse input data + http_content,_ = format_in( datacenter_associate_schema ) + r = utils.remove_extra_items(http_content, datacenter_associate_schema) + if r: + logger.debug("Remove received extra items %s", str(r)) + try: + id_ = nfvo.edit_datacenter_to_tenant(mydb, tenant_id, datacenter_id, + http_content['datacenter'].get('vim_tenant'), + http_content['datacenter'].get('vim_tenant_name'), + http_content['datacenter'].get('vim_username'), + http_content['datacenter'].get('vim_password'), + http_content['datacenter'].get('config') + ) + return http_get_datacenter_id(tenant_id, id_) + except (nfvo.NfvoException, db_base_Exception) as e: + logger.error("http_associate_datacenters_edit error {}: {}".format(e.http_code, str(e))) + bottle.abort(e.http_code, str(e)) + except Exception as e: + logger.error("Unexpected exception: ", exc_info=True) + bottle.abort(HTTP_Internal_Server_Error, type(e).__name__ + ": " + str(e)) @bottle.route(url_base + '//datacenters/', method='DELETE') def http_deassociate_datacenters(tenant_id, datacenter_id): diff --git a/nfvo.py b/nfvo.py index 14ec54f1..1a301509 100644 --- a/nfvo.py +++ b/nfvo.py @@ -39,15 +39,19 @@ import vimconn import logging import collections from db_base import db_base_Exception + import nfvo_db from threading import Lock from time import time +import openvim.ovim as Ovim global global_config global vimconn_imported global logger global default_volume_size default_volume_size = '5' #size in GB +global ovim +ovim = None vimconn_imported = {} # dictionary with VIM type as key, loaded module as value @@ -107,6 +111,31 @@ def start_service(mydb): global db, global_config db = nfvo_db.nfvo_db() db.connect(global_config['db_host'], global_config['db_user'], global_config['db_passwd'], global_config['db_name']) + global ovim + + # Initialize openvim for SDN control + # TODO: Avoid static configuration by adding new parameters to openmanod.cfg + # TODO: review ovim.py to delete not needed configuration + ovim_configuration = { + 'logger_name': 'openvim', + 'network_vlan_range_start': 1000, + 'network_vlan_range_end': 4096, + 'log_level_db': 'DEBUG', + 'db_name': 'mano_vim_db', + 'db_host': 'localhost', + 'db_user': 'vim', + 'db_passwd': 'vimpw', + 'database_version': '0.15', + 'bridge_ifaces': {}, + 'mode': 'normal', + 'of_controller_nets_with_same_vlan': True, + 'network_type': 'bridge', + #TODO: log_level_of should not be needed. To be modified in ovim + 'log_level_of': 'DEBUG' + } + ovim = Ovim.ovim(ovim_configuration) + ovim.start_service() + from_= 'tenants_datacenters as td join datacenters as d on td.datacenter_id=d.uuid join datacenter_tenants as dt on td.datacenter_tenant_id=dt.uuid' select_ = ('type','d.config as config','d.uuid as datacenter_id', 'vim_url', 'vim_url_admin', 'd.name as datacenter_name', 'dt.uuid as datacenter_tenant_id','dt.vim_tenant_name as vim_tenant_name','dt.vim_tenant_id as vim_tenant_id', @@ -148,14 +177,15 @@ def start_service(mydb): raise NfvoException("Error at VIM {}; {}: {}".format(vim["type"], type(e).__name__, str(e)), HTTP_Internal_Server_Error) thread_name = get_non_used_vim_name(vim['datacenter_name'], vim['vim_tenant_id'], vim['vim_tenant_name'], vim['vim_tenant_id']) new_thread = vim_thread.vim_thread(myvim, task_lock, thread_name, vim['datacenter_name'], - vim.get('datacenter_tenant_id'), db=db, db_lock=db_lock) + vim.get('datacenter_tenant_id'), db=db, db_lock=db_lock, ovim=ovim) new_thread.start() vim_threads["running"][thread_id] = new_thread except db_base_Exception as e: raise NfvoException(str(e) + " at nfvo.get_vim", e.http_code) - def stop_service(): + if ovim: + ovim.stop_service() for thread_id,thread in vim_threads["running"].items(): thread.insert_task(new_task("exit", None, store=False)) vim_threads["deleting"][thread_id] = thread @@ -2394,7 +2424,7 @@ def delete_instance(mydb, tenant_id, instance_id): else: # ok task = new_task("del-net", old_task["result"]) else: - task = new_task("del-net", net['vim_net_id'], store=False) + task = new_task("del-net", (net['vim_net_id'], net['sdn_net_id']), store=False) if task: myvim_thread.insert_task(task) except vimconn.vimconnNotFoundException as e: @@ -2731,7 +2761,10 @@ def edit_datacenter(mydb, datacenter_id_name, datacenter_descriptor): if new_config_dict[k]==None: to_delete.append(k) - config_dict = yaml.load(datacenter["config"]) + config_text = datacenter.get("config") + if not config_text: + config_text = '{}' + config_dict = yaml.load(config_text) config_dict.update(new_config_dict) #delete null fields for k in to_delete: @@ -2811,12 +2844,48 @@ def associate_datacenter_to_tenant(mydb, nfvo_tenant, datacenter, vim_tenant_id= # create thread datacenter_id, myvim = get_datacenter_by_name_uuid(mydb, tenant_dict['uuid'], datacenter_id) # reload data thread_name = get_non_used_vim_name(datacenter_name, datacenter_id, tenant_dict['name'], tenant_dict['uuid']) - new_thread = vim_thread.vim_thread(myvim, task_lock, thread_name, datacenter_name, db=db, db_lock=db_lock) + new_thread = vim_thread.vim_thread(myvim, task_lock, thread_name, datacenter_name, db=db, db_lock=db_lock, ovim=ovim) new_thread.start() thread_id = datacenter_id + "." + tenant_dict['uuid'] vim_threads["running"][thread_id] = new_thread return datacenter_id +def edit_datacenter_to_tenant(mydb, nfvo_tenant, datacenter_id, vim_tenant_id=None, vim_tenant_name=None, vim_username=None, vim_password=None, config=None): + #Obtain the data of this datacenter_tenant_id + vim_data = mydb.get_rows( + SELECT=("datacenter_tenants.vim_tenant_name", "datacenter_tenants.vim_tenant_id", "datacenter_tenants.user", + "datacenter_tenants.passwd", "datacenter_tenants.config"), + FROM="datacenter_tenants JOIN tenants_datacenters ON datacenter_tenants.uuid=tenants_datacenters.datacenter_tenant_id", + WHERE={"tenants_datacenters.nfvo_tenant_id": nfvo_tenant, + "tenants_datacenters.datacenter_id": datacenter_id}) + + logger.debug(str(vim_data)) + if len(vim_data) < 1: + raise NfvoException("Datacenter {} is not attached for tenant {}".format(datacenter_id, nfvo_tenant), HTTP_Conflict) + + v = vim_data[0] + if v['config']: + v['config'] = yaml.load(v['config']) + + if vim_tenant_id: + v['vim_tenant_id'] = vim_tenant_id + if vim_tenant_name: + v['vim_tenant_name'] = vim_tenant_name + if vim_username: + v['user'] = vim_username + if vim_password: + v['passwd'] = vim_password + if config: + if not v['config']: + v['config'] = {} + v['config'].update(config) + + logger.debug(str(v)) + deassociate_datacenter_to_tenant(mydb, nfvo_tenant, datacenter_id, vim_tenant_id=v['vim_tenant_id']) + associate_datacenter_to_tenant(mydb, nfvo_tenant, datacenter_id, vim_tenant_id=v['vim_tenant_id'], vim_tenant_name=v['vim_tenant_name'], + vim_username=v['user'], vim_password=v['passwd'], config=v['config']) + + return datacenter_id def deassociate_datacenter_to_tenant(mydb, tenant_id, datacenter, vim_tenant_id=None): #get datacenter info @@ -3054,3 +3123,122 @@ def vim_action_create(mydb, tenant_id, datacenter, item, descriptor): raise NfvoException("Not possible to create {} at VIM: {}".format(item, str(e)), e.http_code) return vim_action_get(mydb, tenant_id, datacenter, item, content) + +def sdn_controller_create(mydb, tenant_id, sdn_controller): + data = ovim.new_of_controller(sdn_controller) + logger.debug('New SDN controller created with uuid {}'.format(data)) + return data + +def sdn_controller_update(mydb, tenant_id, controller_id, sdn_controller): + data = ovim.edit_of_controller(controller_id, sdn_controller) + msg = 'SDN controller {} updated'.format(data) + logger.debug(msg) + return msg + +def sdn_controller_list(mydb, tenant_id, controller_id=None): + if controller_id == None: + data = ovim.get_of_controllers() + else: + data = ovim.show_of_controller(controller_id) + + msg = 'SDN controller list:\n {}'.format(data) + logger.debug(msg) + return data + +def sdn_controller_delete(mydb, tenant_id, controller_id): + select_ = ('uuid', 'config') + datacenters = mydb.get_rows(FROM='datacenters', SELECT=select_) + for datacenter in datacenters: + if datacenter['config']: + config = yaml.load(datacenter['config']) + if 'sdn-controller' in config and config['sdn-controller'] == controller_id: + raise NfvoException("SDN controller {} is in use by datacenter {}".format(controller_id, datacenter['uuid']), HTTP_Conflict) + + data = ovim.delete_of_controller(controller_id) + msg = 'SDN controller {} deleted'.format(data) + logger.debug(msg) + return msg + +def datacenter_sdn_port_mapping_set(mydb, tenant_id, datacenter_id, sdn_port_mapping): + controller = mydb.get_rows(FROM="datacenters", SELECT=("config",), WHERE={"uuid":datacenter_id}) + if len(controller) < 1: + raise NfvoException("Datacenter {} not present in the database".format(datacenter_id), HTTP_Not_Found) + + try: + sdn_controller_id = yaml.load(controller[0]["config"])["sdn-controller"] + except: + raise NfvoException("The datacenter {} has not an SDN controller associated".format(datacenter_id), HTTP_Bad_Request) + + sdn_controller = ovim.show_of_controller(sdn_controller_id) + switch_dpid = sdn_controller["dpid"] + + maps = list() + for compute_node in sdn_port_mapping: + #element = {"ofc_id": sdn_controller_id, "region": datacenter_id, "switch_dpid": switch_dpid} + element = dict() + element["compute_node"] = compute_node["compute_node"] + for port in compute_node["ports"]: + element["pci"] = port.get("pci") + element["switch_port"] = port.get("switch_port") + element["switch_mac"] = port.get("switch_mac") + if not element["pci"] or not (element["switch_port"] or element["switch_mac"]): + raise NfvoException ("The mapping must contain the 'pci' and at least one of the elements 'switch_port'" + " or 'switch_mac'", HTTP_Bad_Request) + maps.append(dict(element)) + + return ovim.set_of_port_mapping(maps, ofc_id=sdn_controller_id, switch_dpid=switch_dpid, region=datacenter_id) + +def datacenter_sdn_port_mapping_list(mydb, tenant_id, datacenter_id): + maps = ovim.get_of_port_mappings(db_filter={"region": datacenter_id}) + + result = { + "sdn-controller": None, + "datacenter-id": datacenter_id, + "dpid": None, + "ports_mapping": list() + } + + datacenter = mydb.get_table_by_uuid_name('datacenters', datacenter_id) + if datacenter['config']: + config = yaml.load(datacenter['config']) + if 'sdn-controller' in config: + controller_id = config['sdn-controller'] + sdn_controller = sdn_controller_list(mydb, tenant_id, controller_id) + result["sdn-controller"] = controller_id + result["dpid"] = sdn_controller["dpid"] + + if result["sdn-controller"] == None or result["dpid"] == None: + raise NfvoException("Not all SDN controller information for datacenter {} could be found: {}".format(datacenter_id, result), + HTTP_Internal_Server_Error) + + if len(maps) == 0: + return result + + ports_correspondence_dict = dict() + for link in maps: + if result["sdn-controller"] != link["ofc_id"]: + raise NfvoException("The sdn-controller specified for different port mappings differ", HTTP_Internal_Server_Error) + if result["dpid"] != link["switch_dpid"]: + raise NfvoException("The dpid specified for different port mappings differ", HTTP_Internal_Server_Error) + element = dict() + element["pci"] = link["pci"] + if link["switch_port"]: + element["switch_port"] = link["switch_port"] + if link["switch_mac"]: + element["switch_mac"] = link["switch_mac"] + + if not link["compute_node"] in ports_correspondence_dict: + content = dict() + content["compute_node"] = link["compute_node"] + content["ports"] = list() + ports_correspondence_dict[link["compute_node"]] = content + + ports_correspondence_dict[link["compute_node"]]["ports"].append(element) + + for key in sorted(ports_correspondence_dict): + result["ports_mapping"].append(ports_correspondence_dict[key]) + + return result + +def datacenter_sdn_port_mapping_delete(mydb, tenant_id, datacenter_id): + return ovim.clear_of_port_mapping(db_filter={"region":datacenter_id}) \ No newline at end of file diff --git a/nfvo_db.py b/nfvo_db.py index b5ec9a0d..d8305a74 100644 --- a/nfvo_db.py +++ b/nfvo_db.py @@ -912,7 +912,7 @@ class nfvo_db(db_base.db_base): #from_text = "instance_nets join instance_scenarios on instance_nets.instance_scenario_id=instance_scenarios.uuid " + \ # "join sce_nets on instance_scenarios.scenario_id=sce_nets.scenario_id" #where_text = "instance_nets.instance_scenario_id='"+ instance_dict['uuid'] + "'" - cmd = "SELECT uuid,vim_net_id,status,error_msg,vim_info,created, sce_net_id, net_id as vnf_net_id, datacenter_id, datacenter_tenant_id"\ + cmd = "SELECT uuid,vim_net_id,status,error_msg,vim_info,created, sce_net_id, net_id as vnf_net_id, datacenter_id, datacenter_tenant_id, sdn_net_id"\ " FROM instance_nets" \ " WHERE instance_scenario_id='{}' ORDER BY created_at".format(instance_dict['uuid']) self.logger.debug(cmd) diff --git a/openmano b/openmano index c34d831f..0e4287e7 100755 --- a/openmano +++ b/openmano @@ -26,10 +26,10 @@ ''' openmano client used to interact with openmano-server (openmanod) ''' -__author__="Alfonso Tierno, Gerardo Garcia" +__author__="Alfonso Tierno, Gerardo Garcia, Pablo Montes" __date__ ="$09-oct-2014 09:09:48$" -__version__="0.4.11-r517" -version_date="Jan 2017" +__version__="0.4.13-r519" +version_date="Mar 2017" from argcomplete.completers import FilesCompleter import os @@ -120,6 +120,7 @@ def _print_verbose(mano_response, verbose_level=0): return result if mano_response.status_code == 200: + uuid = None for content in content_list: if "uuid" in content: uuid = content['uuid'] @@ -931,6 +932,38 @@ def datacenter_attach(args): print "Try to specify a different name with --vim-tenant-name" return result + +def datacenter_edit_vim_tenant(args): + tenant = _get_tenant() + datacenter = _get_datacenter(args.name) + headers_req = {'Accept': 'application/json', 'content-type': 'application/json'} + + if not (args.vim_tenant_id or args.vim_tenant_name or args.user or args.password or args.config): + raise OpenmanoCLIError("Error. At least one parameter must be updated.") + + datacenter_dict = {} + if args.vim_tenant_id != None: + datacenter_dict['vim_tenant'] = args.vim_tenant_id + if args.vim_tenant_name != None: + datacenter_dict['vim_tenant_name'] = args.vim_tenant_name + if args.user != None: + datacenter_dict['vim_username'] = args.user + if args.password != None: + datacenter_dict['vim_password'] = args.password + if args.config != None: + datacenter_dict["config"] = _load_file_or_yaml(args.config) + payload_req = json.dumps({"datacenter": datacenter_dict}) + + # print payload_req + + URLrequest = "http://%s:%s/openmano/%s/datacenters/%s" % (mano_host, mano_port, tenant, datacenter) + logger.debug("openmano request: %s", payload_req) + mano_response = requests.put(URLrequest, headers=headers_req, data=payload_req) + logger.debug("openmano response: %s", mano_response.text) + result = _print_verbose(mano_response, args.verbose) + + return result + def datacenter_detach(args): if args.all: tenant = "any" @@ -960,7 +993,13 @@ def datacenter_create(args): if args.url!=None: datacenter_dict["vim_url_admin"] = args.url_admin if args.config!=None: - datacenter_dict["config"] = _load_file_or_yaml(args.config) + datacenter_dict["config"] = _load_file_or_yaml(args.config) + if args.sdn_controller!=None: + tenant = _get_tenant() + sdn_controller = _get_item_uuid("sdn_controllers", args.sdn_controller, tenant) + if not 'config' in datacenter_dict: + datacenter_dict['config'] = {} + datacenter_dict['config']['sdn-controller'] = sdn_controller payload_req = json.dumps( {"datacenter": datacenter_dict }) #print payload_req @@ -1007,6 +1046,181 @@ def datacenter_list(args): args.verbose += 1 return _print_verbose(mano_response, args.verbose) +def datacenter_sdn_port_mapping_set(args): + tenant = _get_tenant() + datacenter = _get_datacenter(args.name, tenant) + headers_req = {'Accept': 'application/json', 'content-type': 'application/json'} + + if not args.file: + raise OpenmanoCLIError( + "No yaml/json has been provided specifying the SDN port mapping") + + port_mapping = yaml.load(datacenter_sdn_port_mapping_list(args)) + if len(port_mapping["sdn_port_mapping"]["ports_mapping"]) > 0: + if not args.force: + r = raw_input("Datacenter %s already contains a port mapping. Overwrite? (y/N)? " % (datacenter)) + if not (len(r) > 0 and r[0].lower() == "y"): + return 0 + args.force = True + print datacenter_sdn_port_mapping_clear(args) + + sdn_port_mapping = _load_file_or_yaml(args.file) + payload_req = json.dumps({"sdn_port_mapping": sdn_port_mapping}) + + URLrequest = "http://%s:%s/openmano/%s/datacenters/%s/sdn_mapping" % (mano_host, mano_port, tenant, datacenter) + logger.debug("openmano request: %s", payload_req) + mano_response = requests.post(URLrequest, headers=headers_req, data=payload_req) + logger.debug("openmano response: %s", mano_response.text) + + if mano_response.status_code == 200: + return yaml.safe_dump(mano_response.json()) + else: + return mano_response.content + +def datacenter_sdn_port_mapping_list(args): + tenant = _get_tenant() + datacenter = _get_datacenter(args.name, tenant) + + URLrequest = "http://%s:%s/openmano/%s/datacenters/%s/sdn_mapping" % (mano_host, mano_port, tenant, datacenter) + mano_response = requests.get(URLrequest) + logger.debug("openmano response: %s", mano_response.text) + + if mano_response.status_code != 200: + return mano_response.content + + return yaml.safe_dump(mano_response.json()) + +def datacenter_sdn_port_mapping_clear(args): + tenant = _get_tenant() + datacenter = _get_datacenter(args.name, tenant) + + if not args.force: + r = raw_input("Clean SDN port mapping for datacenter %s (y/N)? " %(datacenter)) + if not (len(r)>0 and r[0].lower()=="y"): + return 0 + + URLrequest = "http://%s:%s/openmano/%s/datacenters/%s/sdn_mapping" % (mano_host, mano_port, tenant, datacenter) + mano_response = requests.delete(URLrequest) + logger.debug("openmano response: %s", mano_response.text) + + if mano_response.status_code != 200: + if "No port mapping for datacenter" in mano_response.content: + return "No port mapping for datacenter " + datacenter + " has been found" + return mano_response.content + + return yaml.safe_dump(mano_response.json()) + +def sdn_controller_create(args): + tenant = _get_tenant() + headers_req = {'Accept': 'application/json', 'content-type': 'application/json'} + + if not (args.ip and args.port and args.dpid and args.type): + raise OpenmanoCLIError("The following arguments are required: ip, port, dpid, type") + + controller_dict = {} + controller_dict['name'] = args.name + controller_dict['ip'] = args.ip + controller_dict['port'] = int(args.port) + controller_dict['dpid'] = args.dpid + controller_dict['type'] = args.type + if args.description != None: + controller_dict['description'] = args.description + if args.user != None: + controller_dict['user'] = args.user + if args.password != None: + controller_dict['password'] = args.password + + payload_req = json.dumps({"sdn_controller": controller_dict}) + + # print payload_req + + URLrequest = "http://%s:%s/openmano/%s/sdn_controllers" % (mano_host, mano_port, tenant) + logger.debug("openmano request: %s", payload_req) + mano_response = requests.post(URLrequest, headers=headers_req, data=payload_req) + logger.debug("openmano response: %s", mano_response.text) + result = _print_verbose(mano_response, args.verbose) + + return result + +def sdn_controller_edit(args): + tenant = _get_tenant() + controller_uuid = _get_item_uuid("sdn_controllers", args.name, tenant) + headers_req = {'Accept': 'application/json', 'content-type': 'application/json'} + + if not (args.new_name or args.ip or args.port or args.dpid or args.type): + raise OpenmanoCLIError("At least one parameter must be editd") + + if not args.force: + r = raw_input("Update SDN controller %s (y/N)? " %(args.name)) + if not (len(r)>0 and r[0].lower()=="y"): + return 0 + + controller_dict = {} + if args.new_name != None: + controller_dict['name'] = args.new_name + if args.ip != None: + controller_dict['ip'] = args.ip + if args.port != None: + controller_dict['port'] = args.port + if args.dpid != None: + controller_dict['dpid'] = args.dpid + if args.type != None: + controller_dict['type'] = args.type + if args.description != None: + controller_dict['description'] = args.description + if args.user != None: + controller_dict['user'] = args.user + if args.password != None: + controller_dict['password'] = args.password + + payload_req = json.dumps({"sdn_controller": controller_dict}) + + # print payload_req + + URLrequest = "http://%s:%s/openmano/%s/sdn_controllers/%s" % (mano_host, mano_port, tenant, controller_uuid) + logger.debug("openmano request: %s", payload_req) + mano_response = requests.put(URLrequest, headers=headers_req, data=payload_req) + logger.debug("openmano response: %s", mano_response.text) + result = _print_verbose(mano_response, args.verbose) + + return result + +def sdn_controller_list(args): + tenant = _get_tenant() + headers_req = {'Accept': 'application/json', 'content-type': 'application/json'} + + if args.name: + toshow = _get_item_uuid("sdn_controllers", args.name, tenant) + URLrequest = "http://%s:%s/openmano/%s/sdn_controllers/%s" %(mano_host, mano_port, tenant, toshow) + else: + URLrequest = "http://%s:%s/openmano/%s/sdn_controllers" %(mano_host, mano_port, tenant) + #print URLrequest + mano_response = requests.get(URLrequest) + logger.debug("openmano response: %s", mano_response.text ) + if args.verbose==None: + args.verbose=0 + if args.name!=None: + args.verbose += 1 + + result = json.dumps(mano_response.json(), indent=4) + return result + +def sdn_controller_delete(args): + tenant = _get_tenant() + controller_uuid = _get_item_uuid("sdn_controllers", args.name, tenant) + + if not args.force: + r = raw_input("Delete SDN controller %s (y/N)? " % (args.name)) + if not (len(r) > 0 and r[0].lower() == "y"): + return 0 + + URLrequest = "http://%s:%s/openmano/%s/sdn_controllers/%s" % (mano_host, mano_port, tenant, controller_uuid) + mano_response = requests.delete(URLrequest) + logger.debug("openmano response: %s", mano_response.text) + result = _print_verbose(mano_response, args.verbose) + + return result + def vim_action(args): #print "datacenter-net-action",args tenant = _get_tenant() @@ -1045,11 +1259,11 @@ def vim_action(args): create_dict[args.item]['name'] = args.name #if args.description: # create_dict[args.item]['description'] = args.description - if args.item=="vim-net": + if args.item=="network": if args.bind_net: create_dict[args.item]['bind_net'] = args.bind_net - if args.bind_type: - create_dict[args.item]['bind_type'] = args.bind_type + if args.type: + create_dict[args.item]['type'] = args.type if args.shared: create_dict[args.item]['shared'] = args.shared if "name" not in create_dict[args.item]: @@ -1201,6 +1415,53 @@ def element_edit(args): return _print_verbose(mano_response, args.verbose) +def datacenter_edit(args): + tenant = _get_tenant() + element = _get_item_uuid('datacenters', args.name, tenant) + headers_req = {'Accept': 'application/json', 'content-type': 'application/json'} + URLrequest = "http://%s:%s/openmano/datacenters/%s" % (mano_host, mano_port, element) + + has_arguments = False + if args.file != None: + has_arguments = True + payload = _load_file_or_yaml(args.file) + else: + payload = {} + + if args.sdn_controller != None: + has_arguments = True + if not 'config' in payload: + payload['config'] = {} + if not 'sdn-controller' in payload['config']: + payload['config']['sdn-controller'] = {} + if args.sdn_controller == 'null': + payload['config']['sdn-controller'] = None + else: + payload['config']['sdn-controller'] = _get_item_uuid("sdn_controllers", args.sdn_controller, tenant) + + if not has_arguments: + raise OpenmanoCLIError("At least one argument must be provided to modify the datacenter") + + if 'datacenter' not in payload: + payload = {'datacenter': payload} + payload_req = json.dumps(payload) + + # print payload_req + if not args.force or (args.name == None and args.filer == None): + r = raw_input(" Edit datacenter " + args.name + " (y/N)? ") + if len(r) > 0 and r[0].lower() == "y": + pass + else: + return 0 + logger.debug("openmano request: %s", payload_req) + mano_response = requests.put(URLrequest, headers=headers_req, data=payload_req) + logger.debug("openmano response: %s", mano_response.text) + if args.verbose == None: + args.verbose = 0 + if args.name != None: + args.verbose += 1 + return _print_verbose(mano_response, args.verbose) + global mano_host global mano_port global mano_tenant @@ -1329,7 +1590,7 @@ if __name__=="__main__": tenant_list_parser.add_argument("name", nargs='?', help="name or uuid of the tenant") tenant_list_parser.set_defaults(func=tenant_list) - item_list=('tenant','datacenter') #put tenant before so that help appear in order + item_list=('tenant') #put tenant before so that help appear in order for item in item_list: element_edit_parser = subparsers.add_parser(item+'-edit', parents=[parent_parser], help="edits one "+item) element_edit_parser.add_argument("name", help="name or uuid of the "+item) @@ -1344,6 +1605,7 @@ if __name__=="__main__": datacenter_create_parser.add_argument("--type", action="store", help="datacenter type: openstack or openvim (default)") datacenter_create_parser.add_argument("--config", action="store", help="aditional configuration in json/yaml format") datacenter_create_parser.add_argument("--description", action="store", help="description of the datacenter") + datacenter_create_parser.add_argument("--sdn-controller", action="store", help="Name or uuid of the SDN controller to be used", dest='sdn_controller') datacenter_create_parser.set_defaults(func=datacenter_create) datacenter_delete_parser = subparsers.add_parser('datacenter-delete', parents=[parent_parser], help="deletes a datacenter from the catalogue") @@ -1351,6 +1613,14 @@ if __name__=="__main__": datacenter_delete_parser.add_argument("-f", "--force", action="store_true", help="forces deletion without asking") datacenter_delete_parser.set_defaults(func=datacenter_delete) + datacenter_edit_parser = subparsers.add_parser('datacenter-edit', parents=[parent_parser], help="Edit datacenter") + datacenter_edit_parser.add_argument("name", help="name or uuid of the datacenter") + datacenter_edit_parser.add_argument("--file", help="json/yaml text or file with the changes").completer = FilesCompleter + datacenter_edit_parser.add_argument("--sdn-controller", action="store", + help="Name or uuid of the SDN controller to be used. Specify 'null' to clear entry", dest='sdn_controller') + datacenter_edit_parser.add_argument("-f", "--force", action="store_true", help="do not prompt for confirmation") + datacenter_edit_parser.set_defaults(func=datacenter_edit) + datacenter_list_parser = subparsers.add_parser('datacenter-list', parents=[parent_parser], help="lists information about a datacenter") datacenter_list_parser.add_argument("name", nargs='?', help="name or uuid of the datacenter") datacenter_list_parser.add_argument("-a", "--all", action="store_true", help="shows all datacenters, not only datacenters attached to tenant") @@ -1365,11 +1635,107 @@ if __name__=="__main__": datacenter_attach_parser.add_argument("--config", action="store", help="aditional configuration in json/yaml format") datacenter_attach_parser.set_defaults(func=datacenter_attach) + datacenter_edit_vim_tenant_parser = subparsers.add_parser('datacenter-edit-vim-tenant', parents=[parent_parser], + help="Edit the association of a datacenter to the operating tenant") + datacenter_edit_vim_tenant_parser.add_argument("name", help="name or uuid of the datacenter") + datacenter_edit_vim_tenant_parser.add_argument('--vim-tenant-id', action='store', + help="specify a datacenter tenant to use. A new one is created by default") + datacenter_edit_vim_tenant_parser.add_argument('--vim-tenant-name', action='store', help="specify a datacenter tenant name.") + datacenter_edit_vim_tenant_parser.add_argument("--user", action="store", help="user credentials for the datacenter") + datacenter_edit_vim_tenant_parser.add_argument("--password", action="store", help="password credentials for the datacenter") + datacenter_edit_vim_tenant_parser.add_argument("--config", action="store", + help="aditional configuration in json/yaml format") + datacenter_edit_vim_tenant_parser.set_defaults(func=datacenter_edit_vim_tenant) + datacenter_detach_parser = subparsers.add_parser('datacenter-detach', parents=[parent_parser], help="removes the association between a datacenter and the operating tenant") datacenter_detach_parser.add_argument("name", help="name or uuid of the datacenter") datacenter_detach_parser.add_argument("-a", "--all", action="store_true", help="removes all associations from this datacenter") datacenter_detach_parser.set_defaults(func=datacenter_detach) + #=======================datacenter_sdn_port_mapping_xxx section======================= + #datacenter_sdn_port_mapping_set + datacenter_sdn_port_mapping_set_parser = subparsers.add_parser('datacenter-sdn-port-mapping-set', + parents=[parent_parser], + help="Load a file with the mapping of physical ports " + "and the ports of the dataplaneswitch controlled " + "by a datacenter") + datacenter_sdn_port_mapping_set_parser.add_argument("name", action="store", help="specifies the datacenter") + datacenter_sdn_port_mapping_set_parser.add_argument("file", + help="json/yaml text or file with the port mapping").completer = FilesCompleter + datacenter_sdn_port_mapping_set_parser.add_argument("-f", "--force", action="store_true", + help="forces overwriting without asking") + datacenter_sdn_port_mapping_set_parser.set_defaults(func=datacenter_sdn_port_mapping_set) + + #datacenter_sdn_port_mapping_list + datacenter_sdn_port_mapping_list_parser = subparsers.add_parser('datacenter-sdn-port-mapping-list', + parents=[parent_parser], + help="Show the SDN port mapping in a datacenter") + datacenter_sdn_port_mapping_list_parser.add_argument("name", action="store", help="specifies the datacenter") + datacenter_sdn_port_mapping_list_parser.set_defaults(func=datacenter_sdn_port_mapping_list) + + # datacenter_sdn_port_mapping_clear + datacenter_sdn_port_mapping_clear_parser = subparsers.add_parser('datacenter-sdn-port-mapping-clear', + parents=[parent_parser], + help="Clean the the SDN port mapping in a datacenter") + datacenter_sdn_port_mapping_clear_parser.add_argument("name", action="store", + help="specifies the datacenter") + datacenter_sdn_port_mapping_clear_parser.add_argument("-f", "--force", action="store_true", + help="forces clearing without asking") + datacenter_sdn_port_mapping_clear_parser.set_defaults(func=datacenter_sdn_port_mapping_clear) + # ======================= + + # =======================sdn_controller_xxx section======================= + # sdn_controller_create + sdn_controller_create_parser = subparsers.add_parser('sdn-controller-create', parents=[parent_parser], + help="Creates an SDN controller entity within RO") + sdn_controller_create_parser.add_argument("name", help="name of the SDN controller") + sdn_controller_create_parser.add_argument("--description", action="store", help="description of the SDN controller") + sdn_controller_create_parser.add_argument("--ip", action="store", help="IP of the SDN controller") + sdn_controller_create_parser.add_argument("--port", action="store", help="Port of the SDN controller") + sdn_controller_create_parser.add_argument("--dpid", action="store", + help="DPID of the dataplane switch controlled by this SDN controller") + sdn_controller_create_parser.add_argument("--type", action="store", + help="Specify the SDN controller type. Valid types are 'opendaylight' and 'floodlight'") + sdn_controller_create_parser.add_argument("--user", action="store", help="user credentials for the SDN controller") + sdn_controller_create_parser.add_argument("--passwd", action="store", dest='password', + help="password credentials for the SDN controller") + sdn_controller_create_parser.set_defaults(func=sdn_controller_create) + + # sdn_controller_edit + sdn_controller_edit_parser = subparsers.add_parser('sdn-controller-edit', parents=[parent_parser], + help="Update one or more options of a SDN controller") + sdn_controller_edit_parser.add_argument("name", help="name or uuid of the SDN controller", ) + sdn_controller_edit_parser.add_argument("--name", action="store", help="Update the name of the SDN controller", + dest='new_name') + sdn_controller_edit_parser.add_argument("--description", action="store", help="description of the SDN controller") + sdn_controller_edit_parser.add_argument("--ip", action="store", help="IP of the SDN controller") + sdn_controller_edit_parser.add_argument("--port", action="store", help="Port of the SDN controller") + sdn_controller_edit_parser.add_argument("--dpid", action="store", + help="DPID of the dataplane switch controlled by this SDN controller") + sdn_controller_edit_parser.add_argument("--type", action="store", + help="Specify the SDN controller type. Valid types are 'opendaylight' and 'floodlight'") + sdn_controller_edit_parser.add_argument("--user", action="store", help="user credentials for the SDN controller") + sdn_controller_edit_parser.add_argument("--password", action="store", + help="password credentials for the SDN controller", dest='password') + sdn_controller_edit_parser.add_argument("-f", "--force", action="store_true", help="do not prompt for confirmation") + #TODO: include option --file + sdn_controller_edit_parser.set_defaults(func=sdn_controller_edit) + + #sdn_controller_list + sdn_controller_list_parser = subparsers.add_parser('sdn-controller-list', + parents=[parent_parser], + help="List the SDN controllers") + sdn_controller_list_parser.add_argument("name", nargs='?', help="name or uuid of the SDN controller") + sdn_controller_list_parser.set_defaults(func=sdn_controller_list) + + # sdn_controller_delete + sdn_controller_delete_parser = subparsers.add_parser('sdn-controller-delete', + parents=[parent_parser], + help="Delete the the SDN controller") + sdn_controller_delete_parser.add_argument("name", help="name or uuid of the SDN controller") + sdn_controller_delete_parser.add_argument("-f", "--force", action="store_true", help="forces deletion without asking") + sdn_controller_delete_parser.set_defaults(func=sdn_controller_delete) + # ======================= action_dict={'net-update': 'retrieves external networks from datacenter', 'net-edit': 'edits an external network', diff --git a/openmano_schemas.py b/openmano_schemas.py index 1ea64f63..5b1b9425 100644 --- a/openmano_schemas.py +++ b/openmano_schemas.py @@ -1095,3 +1095,75 @@ instance_scenario_action_schema = { #"maxProperties": 1, "additionalProperties": False } + +sdn_controller_properties={ + "name": name_schema, + "dpid": {"type":"string", "pattern":"^[0-9a-fA-F][02468aceACE](:[0-9a-fA-F]{2}){7}$"}, + "ip": ip_schema, + "port": port_schema, + "type": {"type": "string", "enum": ["opendaylight","floodlight","onos"]}, + "version": {"type" : "string", "minLength":1, "maxLength":12}, + "user": nameshort_schema, + "password": passwd_schema +} +sdn_controller_schema = { + "title":"sdn controller information schema", + "$schema": "http://json-schema.org/draft-04/schema#", + "type":"object", + "properties":{ + "sdn_controller":{ + "type":"object", + "properties":sdn_controller_properties, + "required": ["name", "port", 'ip', 'dpid', 'type'], + "additionalProperties": False + } + }, + "required": ["sdn_controller"], + "additionalProperties": False +} + +sdn_controller_edit_schema = { + "title":"sdn controller update information schema", + "$schema": "http://json-schema.org/draft-04/schema#", + "type":"object", + "properties":{ + "sdn_controller":{ + "type":"object", + "properties":sdn_controller_properties, + "additionalProperties": False + } + }, + "required": ["sdn_controller"], + "additionalProperties": False +} + +sdn_port_mapping_schema = { + "$schema": "http://json-schema.org/draft-04/schema#", + "title":"sdn port mapping information schema", + "type": "object", + "properties": { + "sdn_port_mapping": { + "type": "array", + "items": { + "type": "object", + "properties": { + "compute_node": nameshort_schema, + "ports": { + "type": "array", + "items": { + "type": "object", + "properties": { + "pci": pci_schema, + "switch_port": nameshort_schema, + "switch_mac": mac_schema + }, + "required": ["pci"] + } + } + }, + "required": ["compute_node", "ports"] + } + } + }, + "required": ["sdn_port_mapping"] +} \ No newline at end of file diff --git a/openmanoclient.py b/openmanoclient.py index c11f747e..19a430d4 100644 --- a/openmanoclient.py +++ b/openmanoclient.py @@ -25,10 +25,10 @@ ''' openmano python client used to interact with openmano-server ''' -__author__="Alfonso Tierno" +__author__="Alfonso Tierno, Pablo Montes" __date__ ="$09-Mar-2016 09:09:48$" -__version__="0.0.1-r467" -version_date="Mar 2016" +__version__="0.0.2-r468" +version_date="Feb 2017" import requests import json diff --git a/openmanod.py b/openmanod.py index 03901c3f..4ac475db 100755 --- a/openmanod.py +++ b/openmanod.py @@ -33,9 +33,9 @@ It loads the configuration file and launches the http_server thread that will li ''' __author__="Alfonso Tierno, Gerardo Garcia, Pablo Montes" __date__ ="$26-aug-2014 11:09:29$" -__version__="0.5.8-r518" -version_date="Jan 2017" -database_version="0.19" #expected database schema version +__version__="0.5.9-r519" +version_date="Mar 2017" +database_version="0.20" #expected database schema version import httpserver import time diff --git a/sdn/sdn_port_mapping.yaml b/sdn/sdn_port_mapping.yaml new file mode 100644 index 00000000..47da6e04 --- /dev/null +++ b/sdn/sdn_port_mapping.yaml @@ -0,0 +1,44 @@ +## +# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U. +# This file is part of openmano +# All Rights Reserved. +# +# 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. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact with: nfvlabs@tid.es +## +--- +#The mapping is composed of a list of compute nodes. Each compute nodes has two elements: +#"compute_node": name to identify the compute node within the datacenter +#"ports": list of ports mapped to a switch for that compute node. +#The information to identify the SDN controller and the dataplane switch is obtained from the datacenter information +- compute_node: "compute node 1" + ports: + #Each mapped port contains the following information: + #"pci": pci address of the port in the compute node. This is a mandatory parameter + #"switch_mac": MAC address of the corresponding port in the dataplane switch. + #"switch_port": Openflow name of the port in the dataplane switch. + #"switch_mac" or "switch_port" must be specified. Both of them could be specified + - pci: "0000:81:00.0" + switch_port: "port-2/1" + - pci: "0000:81:00.1" + switch_mac: "52:54:00:94:21:22" +- compute_node: "compute node 2" + ports: + - pci: "0000:81:00.0" + switch_port: "port-2/3" + switch_mac: "52:54:00:94:22:21" + - pci: "0000:81:00.1" + switch_port: "port-2/4" + switch_mac: "52:54:00:94:22:22" diff --git a/test/RO_tests/sr_iov/scenario_p2p_sriov.yaml b/test/RO_tests/sr_iov/scenario_p2p_sriov.yaml new file mode 100644 index 00000000..57cb2c84 --- /dev/null +++ b/test/RO_tests/sr_iov/scenario_p2p_sriov.yaml @@ -0,0 +1,41 @@ +## +# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U. +# This file is part of openmano +# All Rights Reserved. +# +# 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. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact with: nfvlabs@tid.es +## +--- +schema_version: 2 +scenario: + name: p2p_sriov + description: Network scenario consisting of two machines with a sr-iov interconnected between them + vnfs: + sriov1: # vnf/net name in the scenario + vnf_name: sriov # VNF name as introduced in OPENMANO DB + sriov2: # vnf/net name in the scenario + vnf_name: sriov # VNF name as introduced in OPENMANO DB + networks: + mgmt: # provide a name for this net or connection + external: true + interfaces: + - sriov1: eth0 # Node and its interface + - sriov2: eth0 # Node and its interface + dataplane: # provide a name for this net or connection + interfaces: + - sriov1: xe0 # Node and its interface + - sriov2: xe0 # Node and its interface + diff --git a/test/RO_tests/sr_iov/vnfd_1sriov.yaml b/test/RO_tests/sr_iov/vnfd_1sriov.yaml new file mode 100644 index 00000000..e424b027 --- /dev/null +++ b/test/RO_tests/sr_iov/vnfd_1sriov.yaml @@ -0,0 +1,53 @@ +## +# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U. +# This file is part of openmano +# All Rights Reserved. +# +# 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. +# +# For those usages not covered by the Apache License, Version 2.0 please +# contact with: nfvlabs@tid.es +## +--- +vnf: + name: sriov + description: Machine with EPA and a SR-IOV interface + external-connections: + - name: eth0 + type: bridge + VNFC: sriov-VM + local_iface_name: eth0 + description: management interface + - name: xe0 + type: data + VNFC: sriov-VM + local_iface_name: xe0 + description: Dataplane interface + VNFC: + - name: sriov-VM + description: Machine with EPA and a SR-IOV interface + image name: centos + disk: 20 + numas: + - threads: 1 # "cores", "paired-threads", "threads" + memory: 1 # GBytes + interfaces: + - name: xe0 + vpci: "0000:00:11.0" + dedicated: "no" # "yes"(passthrough), "no"(sriov with vlan tags), "yes:sriov"(sriovi, but exclusive and without vlan tag) + bandwidth: 1 Gbps + + bridge-ifaces: + - name: eth0 + vpci: "0000:00:0a.0" + diff --git a/vim_thread.py b/vim_thread.py index 780d6e5d..d89c4d14 100644 --- a/vim_thread.py +++ b/vim_thread.py @@ -25,7 +25,7 @@ This is thread that interact with the host and the libvirt to manage VM One thread will be launched per host ''' -__author__ = "Alfonso Tierno" +__author__ = "Alfonso Tierno, Pablo Montes" __date__ = "$10-feb-2017 12:07:15$" import threading @@ -34,6 +34,7 @@ import Queue import logging import vimconn from db_base import db_base_Exception +from openvim.ovim import ovimException # from logging import Logger @@ -46,7 +47,7 @@ def is_task_id(id): class vim_thread(threading.Thread): - def __init__(self, vimconn, task_lock, name=None, datacenter_name=None, datacenter_tenant_id=None, db=None, db_lock=None): + def __init__(self, vimconn, task_lock, name=None, datacenter_name=None, datacenter_tenant_id=None, db=None, db_lock=None, ovim=None): """Init a thread. Arguments: 'id' number of thead @@ -64,6 +65,7 @@ class vim_thread(threading.Thread): self.vim = vimconn self.datacenter_name = datacenter_name self.datacenter_tenant_id = datacenter_tenant_id + self.ovim = ovim if not name: self.name = vimconn["id"] + "." + vimconn["config"]["datacenter_tenant_id"] else: @@ -152,11 +154,30 @@ class vim_thread(threading.Thread): task_id = task["id"] params = task["params"] net_id = self.vim.new_network(*params) - try: - with self.db_lock: - self.db.update_rows("instance_nets", UPDATE={"vim_net_id": net_id}, WHERE={"vim_net_id": task_id}) - except db_base_Exception as e: - self.logger.error("Error updating database %s", str(e)) + + net_name = params[0] + net_type = params[1] + + network = None + sdn_controller = self.vim.config.get('sdn-controller') + if sdn_controller and (net_type == "data" or net_type == "ptp"): + network = {"name": net_name, "type": net_type} + + vim_net = self.vim.get_network(net_id) + if vim_net.get('network_type') != 'vlan': + raise vimconn.vimconnException(net_name + "defined as type " + net_type + " but the created network in vim is " + vim_net['provider:network_type']) + + network["vlan"] = vim_net.get('segmentation_id') + + sdn_net_id = None + with self.db_lock: + if network: + sdn_net_id = self.ovim.new_network(network) + self.db.update_rows("instance_nets", UPDATE={"vim_net_id": net_id, "sdn_net_id": sdn_net_id}, WHERE={"vim_net_id": task_id}) + + return True, net_id + except db_base_Exception as e: + self.logger.error("Error updating database %s", str(e)) return True, net_id except vimconn.vimconnException as e: self.logger.error("Error creating NET, task=%s: %s", str(task_id), str(e)) @@ -168,6 +189,9 @@ class vim_thread(threading.Thread): except db_base_Exception as e: self.logger.error("Error updating database %s", str(e)) return False, str(e) + except ovimException as e: + self.logger.error("Error creating NET in ovim, task=%s: %s", str(task_id), str(e)) + return False, str(e) def new_vm(self, task): try: @@ -227,7 +251,8 @@ class vim_thread(threading.Thread): return False, str(e) def del_net(self, task): - net_id = task["params"] + net_id = task["params"][0] + sdn_net_id = task["params"][1] if is_task_id(net_id): try: task_create = task["depends"][net_id] @@ -240,8 +265,15 @@ class vim_thread(threading.Thread): except Exception as e: return False, "Error trying to get task_id='{}':".format(net_id, str(e)) try: - return True, self.vim.delete_network(net_id) + result = self.vim.delete_network(net_id) + if sdn_net_id: + with self.db_lock: + self.ovim.delete_network(sdn_net_id) + return True, result except vimconn.vimconnException as e: return False, str(e) + except ovimException as e: + logging.error("Error deleting network from ovim. net_id: {}, sdn_net_id: {}".format(net_id, sdn_net_id)) + return False, str(e) diff --git a/vimconn.py b/vimconn.py index a9bd9be6..ed259e43 100644 --- a/vimconn.py +++ b/vimconn.py @@ -232,6 +232,8 @@ class vimconnector(): 'id': (mandatory) VIM network id 'name': (mandatory) VIM network name 'status': (mandatory) can be 'ACTIVE', 'INACTIVE', 'DOWN', 'BUILD', 'ERROR', 'VIM_ERROR', 'OTHER' + 'network_type': (optional) can be 'vxlan', 'vlan' or 'flat' + 'segmentation_id': (optional) in case network_type is vlan or vxlan this field contains the segmentation id 'error_msg': (optional) text that explains the ERROR status other VIM specific fields: (optional) whenever possible using the same naming of filter_dict param List can be empty if no network map the filter_dict. Raise an exception only upon VIM connectivity, diff --git a/vimconn_openstack.py b/vimconn_openstack.py index b501d9da..2c238db6 100644 --- a/vimconn_openstack.py +++ b/vimconn_openstack.py @@ -389,6 +389,8 @@ class vimconnector(vimconn.vimconnector): subnet = {"id": subnet_id, "fault": str(e)} subnets.append(subnet) net["subnets"] = subnets + net["network_type"] = net.get('provider:network_type') + net["segmentation_id"] = net.get('provider:segmentation_id') return net def delete_network(self, net_id): -- 2.25.1 From 51e553b54a20b9f1a70984f3b9ab6462d6a65316 Mon Sep 17 00:00:00 2001 From: Pablo Montes Moreno Date: Thu, 23 Mar 2017 16:39:12 +0100 Subject: [PATCH 10/16] Added information needed for dataplane connectivity in refresh_vms_status in openstack connector. Minor modification in get_network to avoid confusion with the naming Change-Id: Ia672a8570bd31361d3097b9c1f1b02ec3fa13362 Signed-off-by: Pablo Montes Moreno --- vim_thread.py | 4 ++-- vimconn_openstack.py | 18 +++++++++++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/vim_thread.py b/vim_thread.py index d89c4d14..eb740b4a 100644 --- a/vim_thread.py +++ b/vim_thread.py @@ -164,8 +164,8 @@ class vim_thread(threading.Thread): network = {"name": net_name, "type": net_type} vim_net = self.vim.get_network(net_id) - if vim_net.get('network_type') != 'vlan': - raise vimconn.vimconnException(net_name + "defined as type " + net_type + " but the created network in vim is " + vim_net['provider:network_type']) + if vim_net.get('encapsulation') != 'vlan': + raise vimconn.vimconnException(net_name + "defined as type " + net_type + " but the created network in vim is " + vim_net['encapsulation']) network["vlan"] = vim_net.get('segmentation_id') diff --git a/vimconn_openstack.py b/vimconn_openstack.py index 2c238db6..ceaa7620 100644 --- a/vimconn_openstack.py +++ b/vimconn_openstack.py @@ -389,7 +389,7 @@ class vimconnector(vimconn.vimconnector): subnet = {"id": subnet_id, "fault": str(e)} subnets.append(subnet) net["subnets"] = subnets - net["network_type"] = net.get('provider:network_type') + net["encapsulation"] = net.get('provider:network_type') net["segmentation_id"] = net.get('provider:segmentation_id') return net @@ -1094,6 +1094,9 @@ class vimconnector(vimconn.vimconnector): vim_net_id: #network id where this interface is connected vim_interface_id: #interface/port VIM id ip_address: #null, or text with IPv4, IPv6 address + physical_compute: #identification of compute node where PF,VF interface is allocated + physical_pci: #PCI address of the NIC that hosts the PF,VF + physical_vlan: #physical VLAN used for VF ''' vm_dict={} self.logger.debug("refresh_vms status: Getting tenant VM instance information from VIM") @@ -1126,6 +1129,19 @@ class vimconnector(vimconn.vimconnector): interface["mac_address"] = port.get("mac_address") interface["vim_net_id"] = port["network_id"] interface["vim_interface_id"] = port["id"] + interface["physical_compute"] = vm_vim['OS-EXT-SRV-ATTR:host'] + interface["physical_pci"] = None + # TODO: At the moment sr-iov pci addresses are converted to PF pci addresses by setting the slot to 0x00 + # TODO: This is just a workaround valid for niantinc. Find a better way to do so + if 'pci_slot' in port['binding:profile']: + pci = list(port['binding:profile']['pci_slot']) + pci[-4] = '0' + pci[-3] = '0' + interface["physical_pci"] = ''.join(pci) + interface["physical_vlan"] = None + network = self.neutron.show_network(port["network_id"]) + if network['network'].get('provider:network_type') == 'vlan': + interface["physical_vlan"] = network['network'].get('provider:segmentation_id') ips=[] #look for floating ip address floating_ip_dict = self.neutron.list_floatingips(port_id=port["id"]) -- 2.25.1 From ab60396beadada2a9ec9a00f3067a7df081a7e02 Mon Sep 17 00:00:00 2001 From: Pablo Montes Moreno Date: Fri, 24 Mar 2017 14:34:22 +0100 Subject: [PATCH 11/16] Disabled temporary SDN functionality while development is finished Change-Id: Iafc94a147910fafe5c0c25c28b2fe524203c18af Signed-off-by: Pablo Montes Moreno --- nfvo.py | 41 ++++++++++++++++++++++++----------------- vim_thread.py | 21 +++++++++++---------- 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/nfvo.py b/nfvo.py index 1a301509..7230a50b 100644 --- a/nfvo.py +++ b/nfvo.py @@ -43,7 +43,7 @@ from db_base import db_base_Exception import nfvo_db from threading import Lock from time import time -import openvim.ovim as Ovim +#import openvim.ovim as Ovim global global_config global vimconn_imported @@ -133,8 +133,8 @@ def start_service(mydb): #TODO: log_level_of should not be needed. To be modified in ovim 'log_level_of': 'DEBUG' } - ovim = Ovim.ovim(ovim_configuration) - ovim.start_service() + #ovim = Ovim.ovim(ovim_configuration) + #ovim.start_service() from_= 'tenants_datacenters as td join datacenters as d on td.datacenter_id=d.uuid join datacenter_tenants as dt on td.datacenter_tenant_id=dt.uuid' select_ = ('type','d.config as config','d.uuid as datacenter_id', 'vim_url', 'vim_url_admin', 'd.name as datacenter_name', @@ -3125,22 +3125,25 @@ def vim_action_create(mydb, tenant_id, datacenter, item, descriptor): return vim_action_get(mydb, tenant_id, datacenter, item, content) def sdn_controller_create(mydb, tenant_id, sdn_controller): - data = ovim.new_of_controller(sdn_controller) + #data = ovim.new_of_controller(sdn_controller) + data = [] logger.debug('New SDN controller created with uuid {}'.format(data)) return data def sdn_controller_update(mydb, tenant_id, controller_id, sdn_controller): - data = ovim.edit_of_controller(controller_id, sdn_controller) + #data = ovim.edit_of_controller(controller_id, sdn_controller) + data = [] msg = 'SDN controller {} updated'.format(data) logger.debug(msg) return msg def sdn_controller_list(mydb, tenant_id, controller_id=None): if controller_id == None: - data = ovim.get_of_controllers() + #data = ovim.get_of_controllers() + data = [] else: - data = ovim.show_of_controller(controller_id) - + #data = ovim.show_of_controller(controller_id) + data = {'dpid': None} msg = 'SDN controller list:\n {}'.format(data) logger.debug(msg) return data @@ -3154,7 +3157,8 @@ def sdn_controller_delete(mydb, tenant_id, controller_id): if 'sdn-controller' in config and config['sdn-controller'] == controller_id: raise NfvoException("SDN controller {} is in use by datacenter {}".format(controller_id, datacenter['uuid']), HTTP_Conflict) - data = ovim.delete_of_controller(controller_id) + #data = ovim.delete_of_controller(controller_id) + data = 0 msg = 'SDN controller {} deleted'.format(data) logger.debug(msg) return msg @@ -3169,8 +3173,8 @@ def datacenter_sdn_port_mapping_set(mydb, tenant_id, datacenter_id, sdn_port_map except: raise NfvoException("The datacenter {} has not an SDN controller associated".format(datacenter_id), HTTP_Bad_Request) - sdn_controller = ovim.show_of_controller(sdn_controller_id) - switch_dpid = sdn_controller["dpid"] + #sdn_controller = ovim.show_of_controller(sdn_controller_id) + #switch_dpid = sdn_controller["dpid"] maps = list() for compute_node in sdn_port_mapping: @@ -3186,10 +3190,12 @@ def datacenter_sdn_port_mapping_set(mydb, tenant_id, datacenter_id, sdn_port_map " or 'switch_mac'", HTTP_Bad_Request) maps.append(dict(element)) - return ovim.set_of_port_mapping(maps, ofc_id=sdn_controller_id, switch_dpid=switch_dpid, region=datacenter_id) + #return ovim.set_of_port_mapping(maps, ofc_id=sdn_controller_id, switch_dpid=switch_dpid, region=datacenter_id) + return [] def datacenter_sdn_port_mapping_list(mydb, tenant_id, datacenter_id): - maps = ovim.get_of_port_mappings(db_filter={"region": datacenter_id}) + #maps = ovim.get_of_port_mappings(db_filter={"region": datacenter_id}) + maps = [] result = { "sdn-controller": None, @@ -3207,9 +3213,9 @@ def datacenter_sdn_port_mapping_list(mydb, tenant_id, datacenter_id): result["sdn-controller"] = controller_id result["dpid"] = sdn_controller["dpid"] - if result["sdn-controller"] == None or result["dpid"] == None: - raise NfvoException("Not all SDN controller information for datacenter {} could be found: {}".format(datacenter_id, result), - HTTP_Internal_Server_Error) + # if result["sdn-controller"] == None or result["dpid"] == None: + # raise NfvoException("Not all SDN controller information for datacenter {} could be found: {}".format(datacenter_id, result), + # HTTP_Internal_Server_Error) if len(maps) == 0: return result @@ -3241,4 +3247,5 @@ def datacenter_sdn_port_mapping_list(mydb, tenant_id, datacenter_id): return result def datacenter_sdn_port_mapping_delete(mydb, tenant_id, datacenter_id): - return ovim.clear_of_port_mapping(db_filter={"region":datacenter_id}) \ No newline at end of file + #return ovim.clear_of_port_mapping(db_filter={"region":datacenter_id}) + return 0 diff --git a/vim_thread.py b/vim_thread.py index eb740b4a..676ba207 100644 --- a/vim_thread.py +++ b/vim_thread.py @@ -34,7 +34,7 @@ import Queue import logging import vimconn from db_base import db_base_Exception -from openvim.ovim import ovimException +#from openvim.ovim import ovimException # from logging import Logger @@ -159,7 +159,8 @@ class vim_thread(threading.Thread): net_type = params[1] network = None - sdn_controller = self.vim.config.get('sdn-controller') + #sdn_controller = self.vim.config.get('sdn-controller') + sdn_controller = None if sdn_controller and (net_type == "data" or net_type == "ptp"): network = {"name": net_name, "type": net_type} @@ -189,9 +190,9 @@ class vim_thread(threading.Thread): except db_base_Exception as e: self.logger.error("Error updating database %s", str(e)) return False, str(e) - except ovimException as e: - self.logger.error("Error creating NET in ovim, task=%s: %s", str(task_id), str(e)) - return False, str(e) + # except ovimException as e: + # self.logger.error("Error creating NET in ovim, task=%s: %s", str(task_id), str(e)) + # return False, str(e) def new_vm(self, task): try: @@ -252,7 +253,8 @@ class vim_thread(threading.Thread): def del_net(self, task): net_id = task["params"][0] - sdn_net_id = task["params"][1] + #sdn_net_id = task["params"][1] + sdn_net_id = None if is_task_id(net_id): try: task_create = task["depends"][net_id] @@ -272,8 +274,7 @@ class vim_thread(threading.Thread): return True, result except vimconn.vimconnException as e: return False, str(e) - except ovimException as e: - logging.error("Error deleting network from ovim. net_id: {}, sdn_net_id: {}".format(net_id, sdn_net_id)) - return False, str(e) - + # except ovimException as e: + # logging.error("Error deleting network from ovim. net_id: {}, sdn_net_id: {}".format(net_id, sdn_net_id)) + # return False, str(e) -- 2.25.1 From c5651795c9b7fc6782fa666a27351c2aae52c08f Mon Sep 17 00:00:00 2001 From: tierno Date: Mon, 27 Mar 2017 10:50:43 +0200 Subject: [PATCH 12/16] fix issue at stopping service when it has not been started Change-Id: Ie56b940d5a5b4e7d38fa85e9921ea39a66ec0469 Signed-off-by: tierno --- nfvo.py | 6 +++++- openmanod.py | 2 -- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/nfvo.py b/nfvo.py index 7230a50b..0b7e6d0d 100644 --- a/nfvo.py +++ b/nfvo.py @@ -52,7 +52,7 @@ global default_volume_size default_volume_size = '5' #size in GB global ovim ovim = None - +global_config = None vimconn_imported = {} # dictionary with VIM type as key, loaded module as value vim_threads = {"running":{}, "deleting": {}, "names": []} # threads running for attached-VIMs @@ -184,12 +184,16 @@ def start_service(mydb): raise NfvoException(str(e) + " at nfvo.get_vim", e.http_code) def stop_service(): + global ovim, global_config if ovim: ovim.stop_service() for thread_id,thread in vim_threads["running"].items(): thread.insert_task(new_task("exit", None, store=False)) vim_threads["deleting"][thread_id] = thread vim_threads["running"] = {} + if global_config and global_config.get("console_thread"): + for thread in global_config["console_thread"]: + thread.terminate = True def get_flavorlist(mydb, vnf_id, nfvo_tenant=None): diff --git a/openmanod.py b/openmanod.py index 4ac475db..ee9b8621 100755 --- a/openmanod.py +++ b/openmanod.py @@ -316,6 +316,4 @@ if __name__=="__main__": nfvo.stop_service() if httpthread: httpthread.join(1) - for thread in global_config["console_thread"]: - thread.terminate = True -- 2.25.1 From de691231c590b70ba58b553c852bafbcd04fac90 Mon Sep 17 00:00:00 2001 From: kasar Date: Sat, 25 Mar 2017 03:37:31 -0700 Subject: [PATCH 13/16] 1. Addressed review comments. 2. Added new code for numa affinity to latest master branch(Ignore previous committed code in change 1324) Change-Id: I08264929aeb4ec0cc40e7106b0377dd37350d371 Signed-off-by: kasar --- vimconn_vmware.py | 237 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 188 insertions(+), 49 deletions(-) diff --git a/vimconn_vmware.py b/vimconn_vmware.py index 9e4e7608..a2a2ba46 100644 --- a/vimconn_vmware.py +++ b/vimconn_vmware.py @@ -63,6 +63,7 @@ import hashlib import socket import struct import netaddr +import random # global variable for vcd connector type STANDALONE = 'standalone' @@ -71,13 +72,9 @@ STANDALONE = 'standalone' FLAVOR_RAM_KEY = 'ram' FLAVOR_VCPUS_KEY = 'vcpus' FLAVOR_DISK_KEY = 'disk' -DEFAULT_IP_PROFILE = {'gateway_address':"192.168.1.1", - 'dhcp_count':50, - 'subnet_address':"192.168.1.0/24", +DEFAULT_IP_PROFILE = {'dhcp_count':50, 'dhcp_enabled':True, - 'dhcp_start_address':"192.168.1.3", - 'ip_version':"IPv4", - 'dns_address':"192.168.1.2" + 'ip_version':"IPv4" } # global variable for wait time INTERVAL_TIME = 5 @@ -1397,6 +1394,13 @@ class vimconnector(vimconn.vimconnector): if result : self.logger.debug("Modified Disk size of VM {} ".format(vmname_andid)) + if numas: + # Assigning numa affinity setting + for numa in numas: + if 'paired-threads-id' in numa: + paired_threads_id = numa['paired-threads-id'] + self.set_numa_affinity(vapp_uuid, paired_threads_id) + # add NICs & connect to networks in netlist try: self.logger.info("Request to connect VM to a network: {}".format(net_list)) @@ -1715,39 +1719,6 @@ class vimconnector(vimconn.vimconnector): self.logger.debug("Client requesting refresh vm status for {} ".format(vm_list)) - mac_ip_addr={} - rheaders = {'Content-Type': 'application/xml'} - iso_edges = ['edge-2','edge-3','edge-6','edge-7','edge-8','edge-9','edge-10'] - - try: - for edge in iso_edges: - nsx_api_url = '/api/4.0/edges/'+ edge +'/dhcp/leaseInfo' - self.logger.debug("refresh_vms_status: NSX Manager url: {}".format(nsx_api_url)) - - resp = requests.get(self.nsx_manager + nsx_api_url, - auth = (self.nsx_user, self.nsx_password), - verify = False, headers = rheaders) - - if resp.status_code == requests.codes.ok: - dhcp_leases = XmlElementTree.fromstring(resp.text) - for child in dhcp_leases: - if child.tag == 'dhcpLeaseInfo': - dhcpLeaseInfo = child - for leaseInfo in dhcpLeaseInfo: - for elem in leaseInfo: - if (elem.tag)=='macAddress': - mac_addr = elem.text - if (elem.tag)=='ipAddress': - ip_addr = elem.text - if (mac_addr) is not None: - mac_ip_addr[mac_addr]= ip_addr - self.logger.debug("NSX Manager DHCP Lease info: mac_ip_addr : {}".format(mac_ip_addr)) - else: - self.logger.debug("Error occurred while getting DHCP lease info from NSX Manager: {}".format(resp.content)) - except KeyError: - self.logger.debug("Error in response from NSX Manager {}".format(KeyError.message)) - self.logger.debug(traceback.format_exc()) - vca = self.connect() if not vca: raise vimconn.vimconnConnectionException("self.connect() is failed.") @@ -1757,6 +1728,7 @@ class vimconnector(vimconn.vimconnector): raise vimconn.vimconnException("Failed to get a reference of VDC for a tenant {}".format(self.tenant_name)) vms_dict = {} + nsx_edge_list = [] for vmuuid in vm_list: vmname = self.get_namebyvappid(vca, vdc, vmuuid) if vmname is not None: @@ -1778,12 +1750,19 @@ class vimconnector(vimconn.vimconnector): for vm_network in vapp_network: if vm_network['name'] == vmname: #Assign IP Address based on MAC Address in NSX DHCP lease info - for mac_adres,ip_adres in mac_ip_addr.iteritems(): - if mac_adres == vm_network['mac']: - vm_network['ip']=ip_adres + if vm_network['ip'] is None: + if not nsx_edge_list: + nsx_edge_list = self.get_edge_details() + if nsx_edge_list is None: + raise vimconn.vimconnException("refresh_vms_status:"\ + "Failed to get edge details from NSX Manager") + if vm_network['mac'] is not None: + vm_network['ip'] = self.get_ipaddr_from_NSXedge(nsx_edge_list, vm_network['mac']) + + vm_net_id = self.get_network_id_by_name(vm_network['network_name']) interface = {"mac_address": vm_network['mac'], - "vim_net_id": self.get_network_id_by_name(vm_network['network_name']), - "vim_interface_id": self.get_network_id_by_name(vm_network['network_name']), + "vim_net_id": vm_net_id, + "vim_interface_id": vm_net_id, 'ip_address': vm_network['ip']} # interface['vim_info'] = yaml.safe_dump(vm_network) vm_dict["interfaces"].append(interface) @@ -1795,6 +1774,110 @@ class vimconnector(vimconn.vimconnector): return vms_dict + + def get_edge_details(self): + """Get the NSX edge list from NSX Manager + Returns list of NSX edges + """ + edge_list = [] + rheaders = {'Content-Type': 'application/xml'} + nsx_api_url = '/api/4.0/edges' + + self.logger.debug("Get edge details from NSX Manager {} {}".format(self.nsx_manager, nsx_api_url)) + + try: + resp = requests.get(self.nsx_manager + nsx_api_url, + auth = (self.nsx_user, self.nsx_password), + verify = False, headers = rheaders) + if resp.status_code == requests.codes.ok: + paged_Edge_List = XmlElementTree.fromstring(resp.text) + for edge_pages in paged_Edge_List: + if edge_pages.tag == 'edgePage': + for edge_summary in edge_pages: + if edge_summary.tag == 'pagingInfo': + for element in edge_summary: + if element.tag == 'totalCount' and element.text == '0': + raise vimconn.vimconnException("get_edge_details: No NSX edges details found: {}" + .format(self.nsx_manager)) + + if edge_summary.tag == 'edgeSummary': + for element in edge_summary: + if element.tag == 'id': + edge_list.append(element.text) + else: + raise vimconn.vimconnException("get_edge_details: No NSX edge details found: {}" + .format(self.nsx_manager)) + + if not edge_list: + raise vimconn.vimconnException("get_edge_details: "\ + "No NSX edge details found: {}" + .format(self.nsx_manager)) + else: + self.logger.debug("get_edge_details: Found NSX edges {}".format(edge_list)) + return edge_list + else: + self.logger.debug("get_edge_details: " + "Failed to get NSX edge details from NSX Manager: {}" + .format(resp.content)) + return None + + except Exception as exp: + self.logger.debug("get_edge_details: "\ + "Failed to get NSX edge details from NSX Manager: {}" + .format(exp)) + raise vimconn.vimconnException("get_edge_details: "\ + "Failed to get NSX edge details from NSX Manager: {}" + .format(exp)) + + + def get_ipaddr_from_NSXedge(self, nsx_edges, mac_address): + """Get IP address details from NSX edges, using the MAC address + PARAMS: nsx_edges : List of NSX edges + mac_address : Find IP address corresponding to this MAC address + Returns: IP address corrresponding to the provided MAC address + """ + + ip_addr = None + rheaders = {'Content-Type': 'application/xml'} + + self.logger.debug("get_ipaddr_from_NSXedge: Finding IP addr from NSX edge") + + try: + for edge in nsx_edges: + nsx_api_url = '/api/4.0/edges/'+ edge +'/dhcp/leaseInfo' + + resp = requests.get(self.nsx_manager + nsx_api_url, + auth = (self.nsx_user, self.nsx_password), + verify = False, headers = rheaders) + + if resp.status_code == requests.codes.ok: + dhcp_leases = XmlElementTree.fromstring(resp.text) + for child in dhcp_leases: + if child.tag == 'dhcpLeaseInfo': + dhcpLeaseInfo = child + for leaseInfo in dhcpLeaseInfo: + for elem in leaseInfo: + if (elem.tag)=='macAddress': + edge_mac_addr = elem.text + if (elem.tag)=='ipAddress': + ip_addr = elem.text + if edge_mac_addr is not None: + if edge_mac_addr == mac_address: + self.logger.debug("Found ip addr {} for mac {} at NSX edge {}" + .format(ip_addr, mac_address,edge)) + return ip_addr + else: + self.logger.debug("get_ipaddr_from_NSXedge: "\ + "Error occurred while getting DHCP lease info from NSX Manager: {}" + .format(resp.content)) + + self.logger.debug("get_ipaddr_from_NSXedge: No IP addr found in any NSX edge") + return None + + except XmlElementTree.ParseError as Err: + self.logger.debug("ParseError in response from NSX Manager {}".format(Err.message), exc_info=True) + + def action_vminstance(self, vm__vim_uuid=None, action_dict=None): """Send and action over a VM instance from VIM Returns the vm_id if the action was successfully sent to the VIM""" @@ -2519,20 +2602,25 @@ class vimconnector(vimconn.vimconnector): #Configure IP profile of the network ip_profile = ip_profile if ip_profile is not None else DEFAULT_IP_PROFILE + if 'subnet_address' not in ip_profile or ip_profile['subnet_address'] is None: + subnet_rand = random.randint(0, 255) + ip_base = "192.168.{}.".format(subnet_rand) + ip_profile['subnet_address'] = ip_base + "0/24" + else: + ip_base = ip_profile['subnet_address'].rsplit('.',1)[0] + '.' + if 'gateway_address' not in ip_profile or ip_profile['gateway_address'] is None: - ip_profile['gateway_address']=DEFAULT_IP_PROFILE['gateway_address'] + ip_profile['gateway_address']=ip_base + "1" if 'dhcp_count' not in ip_profile or ip_profile['dhcp_count'] is None: ip_profile['dhcp_count']=DEFAULT_IP_PROFILE['dhcp_count'] - if 'subnet_address' not in ip_profile or ip_profile['subnet_address'] is None: - ip_profile['subnet_address']=DEFAULT_IP_PROFILE['subnet_address'] if 'dhcp_enabled' not in ip_profile or ip_profile['dhcp_enabled'] is None: ip_profile['dhcp_enabled']=DEFAULT_IP_PROFILE['dhcp_enabled'] if 'dhcp_start_address' not in ip_profile or ip_profile['dhcp_start_address'] is None: - ip_profile['dhcp_start_address']=DEFAULT_IP_PROFILE['dhcp_start_address'] + ip_profile['dhcp_start_address']=ip_base + "3" if 'ip_version' not in ip_profile or ip_profile['ip_version'] is None: ip_profile['ip_version']=DEFAULT_IP_PROFILE['ip_version'] if 'dns_address' not in ip_profile or ip_profile['dns_address'] is None: - ip_profile['dns_address']=DEFAULT_IP_PROFILE['dns_address'] + ip_profile['dns_address']=ip_base + "2" gateway_address=ip_profile['gateway_address'] dhcp_count=int(ip_profile['dhcp_count']) @@ -3664,3 +3752,54 @@ class vimconnector(vimconn.vimconnector): self.logger.error("add_network_adapter_to_vms() : exception occurred "\ "while adding Network adapter") raise vimconn.vimconnException(message=exp) + + + def set_numa_affinity(self, vmuuid, paired_threads_id): + """ + Method to assign numa affinity in vm configuration parammeters + Args : + vmuuid - vm uuid + paired_threads_id - one or more virtual processor + numbers + Returns: + return if True + """ + try: + vm_moref_id , vm_vcenter_host , vm_vcenter_username, vm_vcenter_port = self.get_vcenter_info_rest(vmuuid) + if vm_moref_id and vm_vcenter_host and vm_vcenter_username: + context = None + if hasattr(ssl, '_create_unverified_context'): + context = ssl._create_unverified_context() + vcenter_conect = SmartConnect(host=vm_vcenter_host, user=vm_vcenter_username, + pwd=self.passwd, port=int(vm_vcenter_port), + sslContext=context) + atexit.register(Disconnect, vcenter_conect) + content = vcenter_conect.RetrieveContent() + + host_obj, vm_obj = self.get_vm_obj(content ,vm_moref_id) + if vm_obj: + config_spec = vim.vm.ConfigSpec() + config_spec.extraConfig = [] + opt = vim.option.OptionValue() + opt.key = 'numa.nodeAffinity' + opt.value = str(paired_threads_id) + config_spec.extraConfig.append(opt) + task = vm_obj.ReconfigVM_Task(config_spec) + if task: + result = self.wait_for_vcenter_task(task, vcenter_conect) + extra_config = vm_obj.config.extraConfig + flag = False + for opts in extra_config: + if 'numa.nodeAffinity' in opts.key: + flag = True + self.logger.info("set_numa_affinity: Sucessfully assign numa affinity "\ + "value {} for vm {}".format(opt.value, vm_obj)) + if flag: + return + else: + self.logger.error("set_numa_affinity: Failed to assign numa affinity") + except Exception as exp: + self.logger.error("set_numa_affinity : exception occurred while setting numa affinity "\ + "for VM {} : {}".format(vm_obj, vm_moref_id)) + raise vimconn.vimconnException("set_numa_affinity : Error {} failed to assign numa "\ + "affinity".format(exp)) -- 2.25.1 From c27b046d15b4950f7b1d0bc725e850c18dbf9d46 Mon Sep 17 00:00:00 2001 From: garciadeblas Date: Wed, 22 Mar 2017 18:57:47 +0100 Subject: [PATCH 14/16] httpserver: http_post_datacenters: minor typo in the comment about function goal Change-Id: I12dadfa8e91796e2a59a6aa1b1e0244079f2f555 Signed-off-by: garciadeblas --- httpserver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httpserver.py b/httpserver.py index f412aa17..4841a98b 100644 --- a/httpserver.py +++ b/httpserver.py @@ -508,7 +508,7 @@ def http_get_datacenter_id(tenant_id, datacenter_id): @bottle.route(url_base + '/datacenters', method='POST') def http_post_datacenters(): - '''insert a tenant into the catalogue. ''' + '''insert a datacenter into the catalogue. ''' #parse input data logger.debug('FROM %s %s %s', bottle.request.remote_addr, bottle.request.method, bottle.request.url) http_content,_ = format_in( datacenter_schema ) -- 2.25.1 From dc1f02ede32b8ba1ba91c163d7dc7ff4fef91a6a Mon Sep 17 00:00:00 2001 From: kasar Date: Sat, 25 Mar 2017 07:20:30 -0700 Subject: [PATCH 15/16] Fixed the review comments for cloud-init and floating_ip_address Change-Id: Ie673d897a46f1df6dc9afdc3eee6579fe3f2811b Signed-off-by: kasar --- vimconn_vmware.py | 134 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 124 insertions(+), 10 deletions(-) diff --git a/vimconn_vmware.py b/vimconn_vmware.py index a2a2ba46..10111c1f 100644 --- a/vimconn_vmware.py +++ b/vimconn_vmware.py @@ -1446,15 +1446,21 @@ class vimconnector(vimconn.vimconnector): self.add_network_adapter_to_vms(vapp, nets[0].name, primary_nic_index, nicIndex, + net, nic_type=nic_type) else: self.logger.info("new_vminstance(): adding network adapter "\ "to a network {}".format(nets[0].name)) self.add_network_adapter_to_vms(vapp, nets[0].name, primary_nic_index, - nicIndex) + nicIndex, + net) nicIndex += 1 + # cloud-init for ssh-key injection + if cloud_config: + self.cloud_init(vapp,cloud_config) + # deploy and power on vm self.logger.debug("new_vminstance(): Deploying vApp {} ".format(name)) deploytask = vapp.deploy(powerOn=False) @@ -3618,7 +3624,7 @@ class vimconnector(vimconn.vimconnector): " for VM : {}".format(exp)) raise vimconn.vimconnException(message=exp) - def add_network_adapter_to_vms(self, vapp, network_name, primary_nic_index, nicIndex, nic_type=None): + def add_network_adapter_to_vms(self, vapp, network_name, primary_nic_index, nicIndex, net, nic_type=None): """ Method to add network adapter type to vm Args : @@ -3634,6 +3640,10 @@ class vimconnector(vimconn.vimconnector): raise vimconn.vimconnConnectionException("Failed to connect vCloud director") try: + floating_ip = False + if 'floating_ip' in net: floating_ip = net['floating_ip'] + allocation_mode = "POOL" if floating_ip else "DHCP" + if not nic_type: for vms in vapp._get_vms(): vm_id = (vms.id).split(':')[-1] @@ -3658,15 +3668,19 @@ class vimconnector(vimconn.vimconnector): {} true - DHCP - """.format(primary_nic_index, network_name, nicIndex) + {} + """.format(primary_nic_index, network_name, nicIndex, + allocation_mode) + data = data.replace('\n','\n{}\n'.format(item)) else: new_item = """ {} true - DHCP - """.format(network_name, nicIndex) + {} + """.format(network_name, nicIndex, + allocation_mode) + data = data.replace('\n','\n{}\n'.format(new_item)) headers = vca.vcloud_session.get_vcloud_headers() @@ -3713,17 +3727,21 @@ class vimconnector(vimconn.vimconnector): {} true - DHCP + {} {} - """.format(primary_nic_index, network_name, nicIndex, nic_type) + """.format(primary_nic_index, network_name, nicIndex, + allocation_mode, nic_type) + data = data.replace('\n','\n{}\n'.format(item)) else: new_item = """ {} true - DHCP + {} {} - """.format(network_name, nicIndex, nic_type) + """.format(network_name, nicIndex, + allocation_mode, nic_type) + data = data.replace('\n','\n{}\n'.format(new_item)) headers = vca.vcloud_session.get_vcloud_headers() @@ -3803,3 +3821,99 @@ class vimconnector(vimconn.vimconnector): "for VM {} : {}".format(vm_obj, vm_moref_id)) raise vimconn.vimconnException("set_numa_affinity : Error {} failed to assign numa "\ "affinity".format(exp)) + + + def cloud_init(self, vapp, cloud_config): + """ + Method to inject ssh-key + vapp - vapp object + cloud_config a dictionary with: + 'key-pairs': (optional) list of strings with the public key to be inserted to the default user + 'users': (optional) list of users to be inserted, each item is a dict with: + 'name': (mandatory) user name, + 'key-pairs': (optional) list of strings with the public key to be inserted to the user + 'user-data': (optional) string is a text script to be passed directly to cloud-init + 'config-files': (optional). List of files to be transferred. Each item is a dict with: + 'dest': (mandatory) string with the destination absolute path + 'encoding': (optional, by default text). Can be one of: + 'b64', 'base64', 'gz', 'gz+b64', 'gz+base64', 'gzip+b64', 'gzip+base64' + 'content' (mandatory): string with the content of the file + 'permissions': (optional) string with file permissions, typically octal notation '0644' + 'owner': (optional) file owner, string with the format 'owner:group' + 'boot-data-drive': boolean to indicate if user-data must be passed using a boot drive (hard disk + """ + vca = self.connect() + if not vca: + raise vimconn.vimconnConnectionException("Failed to connect vCloud director") + + try: + if isinstance(cloud_config, dict): + key_pairs = [] + userdata = [] + if "key-pairs" in cloud_config: + key_pairs = cloud_config["key-pairs"] + + if "users" in cloud_config: + userdata = cloud_config["users"] + + for key in key_pairs: + for user in userdata: + if 'name' in user: user_name = user['name'] + if 'key-pairs' in user and len(user['key-pairs']) > 0: + for user_key in user['key-pairs']: + customize_script = """ + #!/bin/bash + echo performing customization tasks with param $1 at `date "+DATE: %Y-%m-%d - TIME: %H:%M:%S"` >> /root/customization.log + if [ "$1" = "precustomization" ];then + echo performing precustomization tasks on `date "+DATE: %Y-%m-%d - TIME: %H:%M:%S"` >> /root/customization.log + if [ ! -d /root/.ssh ];then + mkdir /root/.ssh + chown root:root /root/.ssh + chmod 700 /root/.ssh + touch /root/.ssh/authorized_keys + chown root:root /root/.ssh/authorized_keys + chmod 600 /root/.ssh/authorized_keys + # make centos with selinux happy + which restorecon && restorecon -Rv /root/.ssh + echo '{key}' >> /root/.ssh/authorized_keys + else + touch /root/.ssh/authorized_keys + chown root:root /root/.ssh/authorized_keys + chmod 600 /root/.ssh/authorized_keys + echo '{key}' >> /root/.ssh/authorized_keys + fi + if [ -d /home/{user_name} ];then + if [ ! -d /home/{user_name}/.ssh ];then + mkdir /home/{user_name}/.ssh + chown {user_name}:{user_name} /home/{user_name}/.ssh + chmod 700 /home/{user_name}/.ssh + touch /home/{user_name}/.ssh/authorized_keys + chown {user_name}:{user_name} /home/{user_name}/.ssh/authorized_keys + chmod 600 /home/{user_name}/.ssh/authorized_keys + # make centos with selinux happy + which restorecon && restorecon -Rv /home/{user_name}/.ssh + echo '{user_key}' >> /home/{user_name}/.ssh/authorized_keys + else + touch /home/{user_name}/.ssh/authorized_keys + chown {user_name}:{user_name} /home/{user_name}/.ssh/authorized_keys + chmod 600 /home/{user_name}/.ssh/authorized_keys + echo '{user_key}' >> /home/{user_name}/.ssh/authorized_keys + fi + fi + fi""".format(key=key, user_name=user_name, user_key=user_key) + + for vm in vapp._get_vms(): + vm_name = vm.name + task = vapp.customize_guest_os(vm_name, customization_script=customize_script) + if isinstance(task, GenericTask): + vca.block_until_completed(task) + self.logger.info("cloud_init : customized guest os task "\ + "completed for VM {}".format(vm_name)) + else: + self.logger.error("cloud_init : task for customized guest os"\ + "failed for VM {}".format(vm_name)) + except Exception as exp: + self.logger.error("cloud_init : exception occurred while injecting "\ + "ssh-key") + raise vimconn.vimconnException("cloud_init : Error {} failed to inject "\ + "ssh-key".format(exp)) -- 2.25.1 From d2963627c31938740cd8d398abd139f5679405b5 Mon Sep 17 00:00:00 2001 From: kasar Date: Fri, 31 Mar 2017 05:53:17 -0700 Subject: [PATCH 16/16] Added code for ip_address parameter Change-Id: I937f39c8be154fd57b000c4f24ff8e8cd4ecb034 Signed-off-by: kasar --- vimconn_vmware.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/vimconn_vmware.py b/vimconn_vmware.py index 10111c1f..384da623 100644 --- a/vimconn_vmware.py +++ b/vimconn_vmware.py @@ -3640,9 +3640,19 @@ class vimconnector(vimconn.vimconnector): raise vimconn.vimconnConnectionException("Failed to connect vCloud director") try: + ip_address = None floating_ip = False if 'floating_ip' in net: floating_ip = net['floating_ip'] - allocation_mode = "POOL" if floating_ip else "DHCP" + + # Stub for ip_address feature + if 'ip_address' in net: ip_address = net['ip_address'] + + if floating_ip: + allocation_mode = "POOL" + elif ip_address: + allocation_mode = "MANUAL" + else: + allocation_mode = "DHCP" if not nic_type: for vms in vapp._get_vms(): @@ -3671,6 +3681,10 @@ class vimconnector(vimconn.vimconnector): {} """.format(primary_nic_index, network_name, nicIndex, allocation_mode) + # Stub for ip_address feature + if ip_address: + ip_tag = '{}'.format(ip_address) + item = item.replace('\n','\n{}\n'.format(ip_tag)) data = data.replace('\n','\n{}\n'.format(item)) else: @@ -3680,6 +3694,10 @@ class vimconnector(vimconn.vimconnector): {} """.format(network_name, nicIndex, allocation_mode) + # Stub for ip_address feature + if ip_address: + ip_tag = '{}'.format(ip_address) + new_item = new_item.replace('\n','\n{}\n'.format(ip_tag)) data = data.replace('\n','\n{}\n'.format(new_item)) @@ -3731,6 +3749,10 @@ class vimconnector(vimconn.vimconnector): {} """.format(primary_nic_index, network_name, nicIndex, allocation_mode, nic_type) + # Stub for ip_address feature + if ip_address: + ip_tag = '{}'.format(ip_address) + item = item.replace('\n','\n{}\n'.format(ip_tag)) data = data.replace('\n','\n{}\n'.format(item)) else: @@ -3741,6 +3763,10 @@ class vimconnector(vimconn.vimconnector): {} """.format(network_name, nicIndex, allocation_mode, nic_type) + # Stub for ip_address feature + if ip_address: + ip_tag = '{}'.format(ip_address) + new_item = new_item.replace('\n','\n{}\n'.format(ip_tag)) data = data.replace('\n','\n{}\n'.format(new_item)) -- 2.25.1