diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index d70f452..0000000 --- a/.coveragerc +++ /dev/null @@ -1,7 +0,0 @@ -[run] -branch = True -source = networking-arista -omit = networking-arista/tests/*,networking-arista/openstack/* - -[report] -ignore_errors = True diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 8a3c704..0000000 --- a/.gitignore +++ /dev/null @@ -1,53 +0,0 @@ -*.py[cod] - -# C extensions -*.so - -# Packages -*.egg -*.egg-info -dist -build -eggs -parts -bin -var -sdist -develop-eggs -.installed.cfg -lib -lib64 - -# Installer logs -pip-log.txt - -# Unit test / coverage reports -.coverage -.tox -nosetests.xml -.testrepository -.venv - -# Translations -*.mo - -# Mr Developer -.mr.developer.cfg -.project -.pydevproject - -# Complexity -output/*.html -output/*/index.html - -# Sphinx -doc/build - -# pbr generates these -AUTHORS -ChangeLog - -# Editors -*~ -.*.swp -.*sw? diff --git a/.gitreview b/.gitreview deleted file mode 100644 index c83b66c..0000000 --- a/.gitreview +++ /dev/null @@ -1,4 +0,0 @@ -[gerrit] -host=review.openstack.org -port=29418 -project=openstack/networking-arista.git diff --git a/.mailmap b/.mailmap deleted file mode 100644 index ade5a58..0000000 --- a/.mailmap +++ /dev/null @@ -1,6 +0,0 @@ -# Format is: -# -# -Sukhdev Kapur -Shashank Hegde -Andre Pech diff --git a/.testr.conf b/.testr.conf deleted file mode 100644 index 4b24f61..0000000 --- a/.testr.conf +++ /dev/null @@ -1,8 +0,0 @@ -[DEFAULT] -test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ - OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \ - OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \ - OS_LOG_CAPTURE=1 \ - ${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION -test_id_option=--load-list $IDFILE -test_list_option=--list diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst deleted file mode 100644 index 670528b..0000000 --- a/CONTRIBUTING.rst +++ /dev/null @@ -1,16 +0,0 @@ -If you would like to contribute to the development of OpenStack, -you must follow the steps in this page: - - http://docs.openstack.org/infra/manual/developers.html - -Once those steps have been completed, changes to OpenStack -should be submitted for review via the Gerrit tool, following -the workflow documented at: - - http://docs.openstack.org/infra/manual/developers.html#development-workflow - -Pull requests submitted through GitHub will be ignored. - -Bugs should be filed on Launchpad, not GitHub: - - https://bugs.launchpad.net/networking-arista diff --git a/HACKING.rst b/HACKING.rst deleted file mode 100644 index acaa283..0000000 --- a/HACKING.rst +++ /dev/null @@ -1,4 +0,0 @@ -networking-arista Style Commandments -=============================================== - -Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/ diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 68c771a..0000000 --- a/LICENSE +++ /dev/null @@ -1,176 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index c978a52..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,6 +0,0 @@ -include AUTHORS -include ChangeLog -exclude .gitignore -exclude .gitreview - -global-exclude *.pyc diff --git a/README b/README new file mode 100644 index 0000000..8fcd2b2 --- /dev/null +++ b/README @@ -0,0 +1,14 @@ +This project is no longer maintained. + +The contents of this repository are still available in the Git +source code management system. To see the contents of this +repository before it reached its end of life, please check out the +previous commit with "git checkout HEAD^1". + +For ongoing work on maintaining OpenStack packages in the Debian +distribution, please see the Debian OpenStack packaging team at +https://wiki.debian.org/OpenStack/. + +For any further questions, please email +openstack-dev@lists.openstack.org or join #openstack-dev on +Freenode. diff --git a/README.rst b/README.rst deleted file mode 100644 index 6325007..0000000 --- a/README.rst +++ /dev/null @@ -1,13 +0,0 @@ -=============================== -networking-arista -=============================== - -Arista Networking drivers - -* Free software: Apache license -* Source: http://git.openstack.org/cgit/openstack/networking-arista - -Features --------- - -* TODO diff --git a/babel.cfg b/babel.cfg deleted file mode 100644 index 15cd6cb..0000000 --- a/babel.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[python: **.py] - diff --git a/devstack/plugin.sh b/devstack/plugin.sh deleted file mode 100644 index 91265fe..0000000 --- a/devstack/plugin.sh +++ /dev/null @@ -1,97 +0,0 @@ -# -*- mode: shell-script -*- - -function install_lldp() { - echo_summary "Installing LLDP" - install_package lldpd - restart_service lldpd -} - -function install_arista_driver() { - echo_summary "Installing Arista Driver" - setup_develop $ARISTA_DIR -} - -function configure_arista() { - echo_summary "Configuring Neutron for Arista Driver" - cp $ARISTA_ML2_CONF_SAMPLE $ARISTA_ML2_CONF_FILE - - iniset $ARISTA_ML2_CONF_FILE ml2_arista eapi_host $ARISTA_EAPI_HOST - iniset $ARISTA_ML2_CONF_FILE ml2_arista eapi_username $ARISTA_EAPI_USERNAME - iniset $ARISTA_ML2_CONF_FILE ml2_arista eapi_password $ARISTA_EAPI_PASSWORD - iniset $ARISTA_ML2_CONF_FILE ml2_arista api_type $ARISTA_API_TYPE - iniset $ARISTA_ML2_CONF_FILE ml2_arista region_name $ARISTA_REGION_NAME - - if [ -n "${ARISTA_USE_FQDN+x}" ]; then - iniset $ARISTA_ML2_CONF_FILE ml2_arista use_fqdn $ARISTA_USE_FQDN - fi - - if [ -n "${ARISTA_ML2_SYNC_INTERVAL+x}" ]; then - iniset $ARISTA_ML2_CONF_FILE ml2_arista sync_interval $ARISTA_ML2_SYNC_INTERVAL - fi - if [ -n "${ARISTA_SEC_GROUP_SUPPORT+x}" ]; then - iniset $ARISTA_ML2_CONF_FILE ml2_arista sec_group_support $ARISTA_SEC_GROUP_SUPPORT - fi - if [ -n "${ARISTA_SWITCH_INFO+x}" ]; then - iniset $ARISTA_ML2_CONF_FILE ml2_arista switch_info $ARISTA_SWITCH_INFO - fi - - if [ -n "${ARISTA_PRIMARY_L3_HOST+x}" ]; then - iniset $ARISTA_ML2_CONF_FILE l3_arista primary_l3_host $ARISTA_PRIMARY_L3_HOST - fi - if [ -n "${ARISTA_PRIMARY_L3_HOST_USERNAME+x}" ]; then - iniset $ARISTA_ML2_CONF_FILE l3_arista primary_l3_host_username $ARISTA_PRIMARY_L3_HOST_USERNAME - fi - if [ -n "${ARISTA_PRIMARY_L3_HOST_PASSWORD+x}" ]; then - iniset $ARISTA_ML2_CONF_FILE l3_arista primary_l3_host_password $ARISTA_PRIMARY_L3_HOST_PASSWORD - fi - if [ -n "${ARISTA_SECONDARY_L3_HOST+x}" ]; then - iniset $ARISTA_ML2_CONF_FILE l3_arista secondary_l3_host $ARISTA_SECONDARY_L3_HOST - fi - if [ -n "${ARISTA_SECONDARY_L3_HOST_USERNAME+x}" ]; then - iniset $ARISTA_ML2_CONF_FILE l3_arista secondary_l3_host_username $ARISTA_SECONDARY_L3_HOST_USERNAME - fi - if [ -n "${ARISTA_SECONDARY_L3_HOST_PASSWORD+x}" ]; then - iniset $ARISTA_ML2_CONF_FILE l3_arista secondary_l3_host_password $ARISTA_SECONDARY_L3_HOST_PASSWORD - fi - if [ -n "${ARISTA_MLAG_CONFIG+x}" ]; then - iniset $ARISTA_ML2_CONF_FILE l3_arista mlag_config $ARISTA_MLAG_CONFIG - fi - if [ -n "${ARISTA_USE_VRF+x}" ]; then - iniset $ARISTA_ML2_CONF_FILE l3_arista use_vrf $ARISTA_USE_VRF - fi - if [ -n "${ARISTA_L3_SYNC_INTERVAL+x}" ]; then - iniset $ARISTA_ML2_CONF_FILE l3_arista l3_sync_interval $ARISTA_L3_SYNC_INTERVAL - fi - - if [ -n "${ARISTA_TYPE_DRIVER_SYNC_INTERVAL+x}" ]; then - iniset $ARISTA_ML2_CONF_FILE arista_type_driver sync_interval $ARISTA_TYPE_DRIVER_SYNC_INTERVAL - fi - neutron_server_config_add $ARISTA_ML2_CONF_FILE -} - -if [[ "$1" == "stack" && "$2" == "pre-install" ]]; then - if is_service_enabled "q-agt"; then - install_lldp - fi - -elif [[ "$1" == "stack" && "$2" == "install" ]]; then - install_arista_driver - -elif [[ "$1" == "stack" && "$2" == "post-config" ]]; then - configure_arista - -elif [[ "$1" == "stack" && "$2" == "extra" ]]; then - # no-op - : -fi - -if [[ "$1" == "unstack" ]]; then - # no-op - : -fi - -if [[ "$1" == "clean" ]]; then - # no-op - : -fi - diff --git a/devstack/settings b/devstack/settings deleted file mode 100644 index c2dfccf..0000000 --- a/devstack/settings +++ /dev/null @@ -1,11 +0,0 @@ -if ! [[ "$Q_ML2_PLUGIN_MECHANISM_DRIVERS" =~ "arista" ]]; then - Q_ML2_PLUGIN_MECHANISM_DRIVERS="$Q_ML2_PLUGIN_MECHANISM_DRIVERS,arista" -fi - -ARISTA_DIR=${ARISTA_DIR:-$DEST/networking-arista} - -ARISTA_ML2_CONF_SAMPLE=$ARISTA_DIR/etc/ml2_conf_arista.ini -ARISTA_ML2_CONF_FILE=${ARISTA_ML2_CONF_FILE:-"$NEUTRON_CONF_DIR/ml2_conf_arista.ini"} -ARISTA_API_TYPE=${ARISTA_API_TYPE:-"EAPI"} -ARISTA_REGION_NAME=${ARISTA_REGION_NAME:-"$REGION_NAME"} - diff --git a/doc/source/conf.py b/doc/source/conf.py deleted file mode 100755 index f62d5fc..0000000 --- a/doc/source/conf.py +++ /dev/null @@ -1,75 +0,0 @@ -# -*- coding: utf-8 -*- -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -import sys - -sys.path.insert(0, os.path.abspath('../..')) -# -- General configuration ---------------------------------------------------- - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = [ - 'sphinx.ext.autodoc', - #'sphinx.ext.intersphinx', - 'oslosphinx' -] - -# autodoc generation is a bit aggressive and a nuisance when doing heavy -# text edit cycles. -# execute "export SPHINX_DEBUG=1" in your terminal to disable - -# The suffix of source filenames. -source_suffix = '.rst' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'networking-arista' -copyright = u'2013, OpenStack Foundation' - -# If true, '()' will be appended to :func: etc. cross-reference text. -add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -add_module_names = True - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# -- Options for HTML output -------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. Major themes that come with -# Sphinx are currently 'default' and 'sphinxdoc'. -# html_theme_path = ["."] -# html_theme = '_theme' -# html_static_path = ['static'] - -# Output file base name for HTML help builder. -htmlhelp_basename = '%sdoc' % project - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass -# [howto/manual]). -latex_documents = [ - ('index', - '%s.tex' % project, - u'%s Documentation' % project, - u'OpenStack Foundation', 'manual'), -] - -# Example configuration for intersphinx: refer to the Python standard library. -#intersphinx_mapping = {'http://docs.python.org/': None} diff --git a/doc/source/contributing.rst b/doc/source/contributing.rst deleted file mode 100644 index 1728a61..0000000 --- a/doc/source/contributing.rst +++ /dev/null @@ -1,4 +0,0 @@ -============ -Contributing -============ -.. include:: ../../CONTRIBUTING.rst diff --git a/doc/source/index.rst b/doc/source/index.rst deleted file mode 100644 index 453a92a..0000000 --- a/doc/source/index.rst +++ /dev/null @@ -1,25 +0,0 @@ -.. networking-arista documentation master file, created by - sphinx-quickstart on Tue Jul 9 22:26:36 2013. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to networking-arista's documentation! -======================================================== - -Contents: - -.. toctree:: - :maxdepth: 2 - - readme - installation - usage - contributing - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - diff --git a/doc/source/installation.rst b/doc/source/installation.rst deleted file mode 100644 index 65b3f85..0000000 --- a/doc/source/installation.rst +++ /dev/null @@ -1,12 +0,0 @@ -============ -Installation -============ - -At the command line:: - - $ pip install networking-arista - -Or, if you have virtualenvwrapper installed:: - - $ mkvirtualenv networking-arista - $ pip install networking-arista diff --git a/doc/source/readme.rst b/doc/source/readme.rst deleted file mode 100644 index a6210d3..0000000 --- a/doc/source/readme.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../../README.rst diff --git a/doc/source/usage.rst b/doc/source/usage.rst deleted file mode 100644 index 2c4d58d..0000000 --- a/doc/source/usage.rst +++ /dev/null @@ -1,7 +0,0 @@ -======== -Usage -======== - -To use networking-arista in a project:: - - import networking-arista diff --git a/etc/ml2_conf_arista.ini b/etc/ml2_conf_arista.ini deleted file mode 100644 index 141d71c..0000000 --- a/etc/ml2_conf_arista.ini +++ /dev/null @@ -1,160 +0,0 @@ -# Defines configuration options specific for Arista ML2 Mechanism driver - -[ml2_arista] -# (StrOpt) Comma separated list of IP addresses for all CVX instances in -# the high availabilty CVX cluster. This is a required field with -# a minimum of one address (if CVX is deployed in a non-redundant -# (standalone) manner). If not set, all communications to Arista -# EOS will fail. -# -# eapi_host = -# Example: eapi_host = 192.168.0.1, 192.168.11.1, 192.168.22.1 -# -# (StrOpt) EOS command API username. This is required field. -# if not set, all communications to Arista EOS will fail. -# -# eapi_username = -# Example: eapi_username = admin -# -# (StrOpt) EOS command API password. This is required field. -# if not set, all communications to Arista EOS will fail. -# -# eapi_password = -# Example: eapi_password = my_password -# -# (StrOpt) Defines if hostnames are sent to Arista EOS as FQDNs -# ("node1.domain.com") or as short names ("node1"). This is -# optional. If not set, a value of "True" is assumed. -# -# use_fqdn = -# Example: use_fqdn = True -# -# (IntOpt) Sync interval in seconds between Neutron plugin and EOS. -# This field defines how often the synchronization is performed. -# This is an optional field. If not set, a value of 30 seconds -# is assumed. -# -# sync_interval = -# Example: sync_interval = 30 -# -# (StrOpt) Defines Region Name that is assigned to this OpenStack Controller. -# This is useful when multiple OpenStack/Neutron controllers are -# managing the same Arista HW clusters. Note that this name must -# match with the region name registered (or known) to keystone -# service. Authentication with Keysotne is performed by EOS. -# This is optional. If not set, a value of "RegionOne" is assumed. -# -# region_name = -# Example: region_name = RegionOne -# -# (BoolOpt) Specifies if the Security Groups need to be deployed for baremetal -# deployments. If this flag is set to "True", this means switch_info -# (see below) must be defined. If this flag is not defined, it is -# assumed to be False. -# -# sec_group_support = -# Example: sec_group_support = True -# -# (ListOpt) This is a comma separated list of Arista switches where -# security groups (i.e. ACLs) need to be applied. Each string has -# three values separated by ":" in the following format. -# ::,:: -# This is required if sec_group_support is set to "True" -# -# switch_info = -# Example: switch_info = 172.13.23.55:admin:admin,172.13.23.56:admin:admin -# -# (StrOpt) Tells the plugin to use a sepcific API interfaces to communicate -# with CVX. Valid options are: -# EAPI - Use EOS' extensible API. -# JSON - Use EOS' JSON/REST API. -# api_type = -# Example: api_type = EAPI -# -# (ListOpt) This is a comma separated list of physical networks which are -# managed by Arista switches. This list will be used in -# by the Arista ML2 plugin to make the decision if it can -# participate on binding or updating a port. -# -# managed_physnets = -# Example: managed_physnets = arista_network -# -# (BoolOpt) Specifies whether the Arista ML2 plugin should bind ports to vxlan -# fabric segments and dynamically allocate vlan segments based on -# the host to connect the port to the vxlan fabric. -# -# manage_fabric = -# Example: manage_fabric = False - - -[l3_arista] - -# (StrOpt) primary host IP address. This is required field. If not set, all -# communications to Arista EOS will fail. This is the host where -# primary router is created. -# -# primary_l3_host = -# Example: primary_l3_host = 192.168.10.10 -# -# (StrOpt) Primary host username. This is required field. -# if not set, all communications to Arista EOS will fail. -# -# primary_l3_host_username = -# Example: primary_l3_username = admin -# -# (StrOpt) Primary host password. This is required field. -# if not set, all communications to Arista EOS will fail. -# -# primary_l3_host_password = -# Example: primary_l3_password = my_password -# -# (StrOpt) IP address of the second Arista switch paired as -# MLAG (Multi-chassis Link Aggregation) with the first. -# This is optional field, however, if mlag_config flag is set, -# then this is a required field. If not set, all -# communications to Arista EOS will fail. If mlag_config is set -# to False, then this field is ignored -# -# secondary_l3_host = -# Example: secondary_l3_host = 192.168.10.20 -# -# (IntOpt) Connection timeout interval in seconds. This interval -# defines how long an EAPI request from the driver to ' -# EOS waits before timing out. If not set, a value of 10 -# seconds is assumed. -# -# conn_timeout = -# Example: conn_timeout = 10 -# -# (BoolOpt) Defines if Arista switches are configured in MLAG mode -# If yes, all L3 configuration is pushed to both switches -# automatically. If this flag is set, ensure that secondary_l3_host -# is set to the second switch's IP. -# This flag is Optional. If not set, a value of "False" is assumed. -# -# mlag_config = -# Example: mlag_config = True -# -# (BoolOpt) Defines if the router is created in default VRF or a -# a specific VRF. This is optional. -# If not set, a value of "False" is assumed. -# -# Example: use_vrf = True -# -# (IntOpt) Sync interval in seconds between Neutron plugin and EOS. -# This field defines how often the synchronization is performed. -# This is an optional field. If not set, a value of 180 seconds -# is assumed. -# -# l3_sync_interval = -# Example: l3_sync_interval = 60 - -[arista_type_driver] - -# (IntOpt) VLAN Sync interval in seconds between the type driver and EOS. -# This interval defines how often the VLAN synchronization is -# performed. This is an optional field. If not set, a value of -# 10 seconds is assumed. -# -# sync_interval = -# Example: sync_interval = 10 diff --git a/etc/policy.json b/etc/policy.json deleted file mode 100644 index 4c7f003..0000000 --- a/etc/policy.json +++ /dev/null @@ -1,143 +0,0 @@ -{ - "context_is_admin": "role:admin", - "admin_or_owner": "rule:context_is_admin or tenant_id:%(tenant_id)s", - "context_is_advsvc": "role:advsvc", - "admin_or_network_owner": "rule:context_is_admin or tenant_id:%(network:tenant_id)s", - "admin_only": "rule:context_is_admin", - "regular_user": "", - "shared": "field:networks:shared=True", - "shared_firewalls": "field:firewalls:shared=True", - "external": "field:networks:router:external=True", - "default": "rule:admin_or_owner", - - "create_subnet": "rule:admin_or_network_owner", - "get_subnet": "rule:admin_or_owner or rule:shared", - "update_subnet": "rule:admin_or_network_owner", - "delete_subnet": "rule:admin_or_network_owner", - - "create_network": "", - "get_network": "rule:admin_or_owner or rule:shared or rule:external or rule:context_is_advsvc", - "get_network:router:external": "rule:regular_user", - "get_network:segments": "rule:admin_only", - "get_network:provider:network_type": "rule:admin_only", - "get_network:provider:physical_network": "rule:admin_only", - "get_network:provider:segmentation_id": "rule:admin_only", - "get_network:queue_id": "rule:admin_only", - "create_network:shared": "rule:admin_only", - "create_network:router:external": "rule:admin_only", - "create_network:segments": "rule:admin_only", - "create_network:provider:network_type": "rule:admin_only", - "create_network:provider:physical_network": "rule:admin_only", - "create_network:provider:segmentation_id": "rule:admin_only", - "update_network": "rule:admin_or_owner", - "update_network:segments": "rule:admin_only", - "update_network:shared": "rule:admin_only", - "update_network:provider:network_type": "rule:admin_only", - "update_network:provider:physical_network": "rule:admin_only", - "update_network:provider:segmentation_id": "rule:admin_only", - "update_network:router:external": "rule:admin_only", - "delete_network": "rule:admin_or_owner", - - "create_port": "", - "create_port:mac_address": "rule:admin_or_network_owner or rule:context_is_advsvc", - "create_port:fixed_ips": "rule:admin_or_network_owner or rule:context_is_advsvc", - "create_port:port_security_enabled": "rule:admin_or_network_owner or rule:context_is_advsvc", - "create_port:binding:host_id": "rule:admin_only", - "create_port:binding:profile": "rule:admin_only", - "create_port:mac_learning_enabled": "rule:admin_or_network_owner or rule:context_is_advsvc", - "get_port": "rule:admin_or_owner or rule:context_is_advsvc", - "get_port:queue_id": "rule:admin_only", - "get_port:binding:vif_type": "rule:admin_only", - "get_port:binding:vif_details": "rule:admin_only", - "get_port:binding:host_id": "rule:admin_only", - "get_port:binding:profile": "rule:admin_only", - "update_port": "rule:admin_or_owner or rule:context_is_advsvc", - "update_port:fixed_ips": "rule:admin_or_network_owner or rule:context_is_advsvc", - "update_port:port_security_enabled": "rule:admin_or_network_owner or rule:context_is_advsvc", - "update_port:binding:host_id": "rule:admin_only", - "update_port:binding:profile": "rule:admin_only", - "update_port:mac_learning_enabled": "rule:admin_or_network_owner or rule:context_is_advsvc", - "delete_port": "rule:admin_or_owner or rule:context_is_advsvc", - - "get_router:ha": "rule:admin_only", - "create_router": "rule:regular_user", - "create_router:external_gateway_info:enable_snat": "rule:admin_only", - "create_router:distributed": "rule:admin_only", - "create_router:ha": "rule:admin_only", - "get_router": "rule:admin_or_owner", - "get_router:distributed": "rule:admin_only", - "update_router:external_gateway_info:enable_snat": "rule:admin_only", - "update_router:distributed": "rule:admin_only", - "update_router:ha": "rule:admin_only", - "delete_router": "rule:admin_or_owner", - - "add_router_interface": "rule:admin_or_owner", - "remove_router_interface": "rule:admin_or_owner", - - "create_router:external_gateway_info:external_fixed_ips": "rule:admin_only", - "update_router:external_gateway_info:external_fixed_ips": "rule:admin_only", - - "create_firewall": "", - "get_firewall": "rule:admin_or_owner", - "create_firewall:shared": "rule:admin_only", - "get_firewall:shared": "rule:admin_only", - "update_firewall": "rule:admin_or_owner", - "update_firewall:shared": "rule:admin_only", - "delete_firewall": "rule:admin_or_owner", - - "create_firewall_policy": "", - "get_firewall_policy": "rule:admin_or_owner or rule:shared_firewalls", - "create_firewall_policy:shared": "rule:admin_or_owner", - "update_firewall_policy": "rule:admin_or_owner", - "delete_firewall_policy": "rule:admin_or_owner", - - "create_firewall_rule": "", - "get_firewall_rule": "rule:admin_or_owner or rule:shared_firewalls", - "update_firewall_rule": "rule:admin_or_owner", - "delete_firewall_rule": "rule:admin_or_owner", - - "create_qos_queue": "rule:admin_only", - "get_qos_queue": "rule:admin_only", - - "update_agent": "rule:admin_only", - "delete_agent": "rule:admin_only", - "get_agent": "rule:admin_only", - - "create_dhcp-network": "rule:admin_only", - "delete_dhcp-network": "rule:admin_only", - "get_dhcp-networks": "rule:admin_only", - "create_l3-router": "rule:admin_only", - "delete_l3-router": "rule:admin_only", - "get_l3-routers": "rule:admin_only", - "get_dhcp-agents": "rule:admin_only", - "get_l3-agents": "rule:admin_only", - "get_loadbalancer-agent": "rule:admin_only", - "get_loadbalancer-pools": "rule:admin_only", - - "create_floatingip": "rule:regular_user", - "create_floatingip:floating_ip_address": "rule:admin_only", - "update_floatingip": "rule:admin_or_owner", - "delete_floatingip": "rule:admin_or_owner", - "get_floatingip": "rule:admin_or_owner", - - "create_network_profile": "rule:admin_only", - "update_network_profile": "rule:admin_only", - "delete_network_profile": "rule:admin_only", - "get_network_profiles": "", - "get_network_profile": "", - "update_policy_profiles": "rule:admin_only", - "get_policy_profiles": "", - "get_policy_profile": "", - - "create_metering_label": "rule:admin_only", - "delete_metering_label": "rule:admin_only", - "get_metering_label": "rule:admin_only", - - "create_metering_label_rule": "rule:admin_only", - "delete_metering_label_rule": "rule:admin_only", - "get_metering_label_rule": "rule:admin_only", - - "get_service_provider": "rule:regular_user", - "get_lsn": "rule:admin_only", - "create_lsn": "rule:admin_only" -} diff --git a/networking_arista/__init__.py b/networking_arista/__init__.py deleted file mode 100644 index ecc1a68..0000000 --- a/networking_arista/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- - -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import gettext - -import pbr.version -import six - - -__version__ = pbr.version.VersionInfo( - 'networking_arista').version_string() - -if six.PY2: - gettext.install('networking_arista', unicode=1) -else: - gettext.install('networking_arista') diff --git a/networking_arista/_i18n.py b/networking_arista/_i18n.py deleted file mode 100644 index fe0f5f1..0000000 --- a/networking_arista/_i18n.py +++ /dev/null @@ -1,42 +0,0 @@ -# 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. - -import oslo_i18n - -DOMAIN = "networking_arista" - -_translators = oslo_i18n.TranslatorFactory(domain=DOMAIN) - -# The primary translation function using the well-known name "_" -_ = _translators.primary - -# The contextual translation function using the name "_C" -_C = _translators.contextual_form - -# The plural translation function using the name "_P" -_P = _translators.plural_form - -# Translators for log levels. -# -# The abbreviated names are meant to reflect the usual use of a short -# name like '_'. The "L" is for "log" and the other letter comes from -# the level. -_LI = _translators.log_info -_LW = _translators.log_warning -_LE = _translators.log_error -_LC = _translators.log_critical - - -def get_available_languages(): - return oslo_i18n.get_available_languages(DOMAIN) diff --git a/networking_arista/common/__init__.py b/networking_arista/common/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/networking_arista/common/api.py b/networking_arista/common/api.py deleted file mode 100644 index bf56aa9..0000000 --- a/networking_arista/common/api.py +++ /dev/null @@ -1,133 +0,0 @@ -# Copyright (c) 2017 Arista Networks, Inc -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import json - -from oslo_log import log as logging -from oslo_utils import excutils -import requests -from requests import exceptions as requests_exc -from six.moves.urllib import parse - -from networking_arista._i18n import _LI, _LW -from networking_arista.common import exceptions as arista_exc - -LOG = logging.getLogger(__name__) - -# EAPI error message -ERR_CVX_NOT_LEADER = 'only available on cluster leader' - - -class EAPIClient(object): - def __init__(self, host, username=None, password=None, verify=False, - timeout=None): - self.host = host - self.timeout = timeout - self.url = self._make_url(host) - self.session = requests.Session() - self.session.headers['Content-Type'] = 'application/json' - self.session.headers['Accept'] = 'application/json' - self.session.verify = verify - if username and password: - self.session.auth = (username, password) - - @staticmethod - def _make_url(host, scheme='https'): - return parse.urlunsplit( - (scheme, host, '/command-api', '', '') - ) - - def execute(self, commands, commands_to_log=None): - params = { - 'timestamps': False, - 'format': 'json', - 'version': 1, - 'cmds': commands - } - - data = { - 'id': 'Networking Arista Driver', - 'method': 'runCmds', - 'jsonrpc': '2.0', - 'params': params - } - - if commands_to_log: - log_data = dict(data) - log_data['params'] = dict(params) - log_data['params']['cmds'] = commands_to_log - else: - log_data = data - - LOG.info( - _LI('EAPI request %(ip)s contains %(data)s'), - {'ip': self.host, 'data': json.dumps(log_data)} - ) - - # request handling - try: - error = None - response = self.session.post( - self.url, - data=json.dumps(data), - timeout=self.timeout - ) - except requests_exc.ConnectionError: - error = _LW('Error while trying to connect to %(ip)s') - except requests_exc.ConnectTimeout: - error = _LW('Timed out while trying to connect to %(ip)s') - except requests_exc.Timeout: - error = _LW('Timed out during an EAPI request to %(ip)s') - except requests_exc.InvalidURL: - error = _LW('Ingoring attempt to connect to invalid URL at %(ip)s') - except Exception as e: - with excutils.save_and_reraise_exception(): - LOG.warning( - _LW('Error during processing the EAPI request %(error)s'), - {'error': e} - ) - finally: - if error: - msg = error % {'ip': self.host} - # stop processing since we've encountered request error - LOG.warning(msg) - raise arista_exc.AristaRpcError(msg=msg) - - # response handling - try: - resp_data = response.json() - return resp_data['result'] - except ValueError as e: - LOG.info(_LI('Ignoring invalid JSON response')) - except KeyError: - if 'error' in resp_data and resp_data['error']['code'] == 1002: - for d in resp_data['error']['data']: - if not isinstance(d, dict): - continue - elif ERR_CVX_NOT_LEADER in d.get('errors', {})[0]: - LOG.info( - _LI('%(ip)s is not the CVX leader'), - {'ip': self.host} - ) - return - msg = _LI('Unexpected EAPI error') - LOG.info(msg) - raise arista_exc.AristaRpcError(msg=msg) - except Exception as e: - with excutils.save_and_reraise_exception(): - LOG.warning( - _LW('Error during processing the EAPI response %(error)s'), - {'error': e} - ) diff --git a/networking_arista/common/config.py b/networking_arista/common/config.py deleted file mode 100644 index 1675e30..0000000 --- a/networking_arista/common/config.py +++ /dev/null @@ -1,194 +0,0 @@ -# Copyright (c) 2013 OpenStack Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -from oslo_config import cfg - -from networking_arista._i18n import _ - - -# Arista ML2 Mechanism driver specific configuration knobs. -# -# Following are user configurable options for Arista ML2 Mechanism -# driver. The eapi_username, eapi_password, and eapi_host are -# required options. Region Name must be the same that is used by -# Keystone service. This option is available to support multiple -# OpenStack/Neutron controllers. - -ARISTA_DRIVER_OPTS = [ - cfg.StrOpt('eapi_username', - default='', - help=_('Username for Arista EOS. This is required field. ' - 'If not set, all communications to Arista EOS ' - 'will fail.')), - cfg.StrOpt('eapi_password', - default='', - secret=True, # do not expose value in the logs - help=_('Password for Arista EOS. This is required field. ' - 'If not set, all communications to Arista EOS ' - 'will fail.')), - cfg.StrOpt('eapi_host', - default='', - help=_('Arista EOS IP address. This is required field. ' - 'If not set, all communications to Arista EOS ' - 'will fail.')), - cfg.BoolOpt('use_fqdn', - default=True, - help=_('Defines if hostnames are sent to Arista EOS as FQDNs ' - '("node1.domain.com") or as short names ("node1"). ' - 'This is optional. If not set, a value of "True" ' - 'is assumed.')), - cfg.IntOpt('sync_interval', - default=30, - help=_('Sync interval in seconds between Neutron plugin and ' - 'EOS. This interval defines how often the ' - 'synchronization is performed. This is an optional ' - 'field. If not set, a value of 30 seconds is ' - 'assumed.')), - cfg.IntOpt('conn_timeout', - default=10, - help=_('Connection timeout interval in seconds. This interval ' - 'defines how long an EAPI request from the driver to ' - 'EOS waits before timing out. If not set, a value of 10 ' - 'seconds is assumed.')), - cfg.StrOpt('region_name', - default='RegionOne', - help=_('Defines Region Name that is assigned to this OpenStack ' - 'Controller. This is useful when multiple ' - 'OpenStack/Neutron controllers are managing the same ' - 'Arista HW clusters. Note that this name must match ' - 'with the region name registered (or known) to keystone ' - 'service. Authentication with Keysotne is performed by ' - 'EOS. This is optional. If not set, a value of ' - '"RegionOne" is assumed.')), - cfg.BoolOpt('sec_group_support', - default=False, - help=_('Specifies if the Security Groups needs to deployed ' - 'for baremetal deployments. If this flag is set to ' - 'True, this means switch_info(see below) must be ' - 'defined. If this flag is not defined, it is assumed ' - 'to be False')), - cfg.ListOpt('switch_info', - default=[], - help=_('This is a comma separated list of Arista switches ' - 'where security groups (i.e. ACLs) need to be ' - 'applied. Each string has three values separated ' - 'by : in the follow format ' - '::, ...... ' - 'For Example: 172.13.23.55:admin:admin, ' - '172.13.23.56:admin:admin, .... ' - 'This is required if sec_group_support is set to ' - '"True"')), - cfg.StrOpt('api_type', - default='JSON', - help=_('Tells the plugin to use a sepcific API interfaces ' - 'to communicate with CVX. Valid options are:' - 'EAPI - Use EOS\' extensible API.' - 'JSON - Use EOS\' JSON/REST API.')), - cfg.ListOpt('managed_physnets', - default=[], - help=_('This is a comma separated list of physical networks ' - 'which are managed by Arista switches.' - 'This list will be used by the Arista ML2 plugin' - 'to make the decision if it can participate in binding' - 'or updating a port.' - 'For Example: ' - 'managed_physnets = arista_network')), - cfg.BoolOpt('manage_fabric', - default=False, - help=_('Specifies whether the Arista ML2 plugin should bind ' - 'ports to vxlan fabric segments and dynamically ' - 'allocate vlan segments based on the host to connect ' - 'the port to the vxlan fabric')), -] - - -""" Arista L3 Service Plugin specific configuration knobs. - -Following are user configurable options for Arista L3 plugin -driver. The eapi_username, eapi_password, and eapi_host are -required options. -""" - -ARISTA_L3_PLUGIN = [ - cfg.StrOpt('primary_l3_host_username', - default='', - help=_('Username for Arista EOS. This is required field. ' - 'If not set, all communications to Arista EOS ' - 'will fail')), - cfg.StrOpt('primary_l3_host_password', - default='', - secret=True, # do not expose value in the logs - help=_('Password for Arista EOS. This is required field. ' - 'If not set, all communications to Arista EOS ' - 'will fail')), - cfg.StrOpt('primary_l3_host', - default='', - help=_('Arista EOS IP address. This is required field. ' - 'If not set, all communications to Arista EOS ' - 'will fail')), - cfg.StrOpt('secondary_l3_host', - default='', - help=_('Arista EOS IP address for second Switch MLAGed with ' - 'the first one. This an optional field, however, if ' - 'mlag_config flag is set, then this is required. ' - 'If not set, all communications to Arista EOS ' - 'will fail')), - cfg.IntOpt('conn_timeout', - default=10, - help=_('Connection timeout interval in seconds. This interval ' - 'defines how long an EAPI request from the driver to ' - 'EOS waits before timing out. If not set, a value of 10 ' - 'seconds is assumed.')), - cfg.BoolOpt('mlag_config', - default=False, - help=_('This flag is used indicate if Arista Switches are ' - 'configured in MLAG mode. If yes, all L3 config ' - 'is pushed to both the switches automatically. ' - 'If this flag is set to True, ensure to specify IP ' - 'addresses of both switches. ' - 'This is optional. If not set, a value of "False" ' - 'is assumed.')), - cfg.BoolOpt('use_vrf', - default=False, - help=_('A "True" value for this flag indicates to create a ' - 'router in VRF. If not set, all routers are created ' - 'in default VRF. ' - 'This is optional. If not set, a value of "False" ' - 'is assumed.')), - cfg.IntOpt('l3_sync_interval', - default=180, - help=_('Sync interval in seconds between L3 Service plugin ' - 'and EOS. This interval defines how often the ' - 'synchronization is performed. This is an optional ' - 'field. If not set, a value of 180 seconds is assumed')) -] - - -ARISTA_TYPE_DRIVER_OPTS = [ - cfg.IntOpt('sync_interval', - default=10, - help=_('VLAN Sync interval in seconds between Neutron plugin ' - 'and EOS. This interval defines how often the VLAN ' - 'synchronization is performed. This is an optional ' - 'field. If not set, a value of 10 seconds is ' - 'assumed.')), -] - -cfg.CONF.register_opts(ARISTA_L3_PLUGIN, "l3_arista") - -cfg.CONF.register_opts(ARISTA_DRIVER_OPTS, "ml2_arista") - -cfg.CONF.register_opts(ARISTA_TYPE_DRIVER_OPTS, "arista_type_driver") diff --git a/networking_arista/common/db.py b/networking_arista/common/db.py deleted file mode 100644 index b78d647..0000000 --- a/networking_arista/common/db.py +++ /dev/null @@ -1,83 +0,0 @@ -# Copyright (c) 2013 OpenStack Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from neutron_lib.db import constants as db_const -from neutron_lib.db import model_base -import sqlalchemy as sa - -UUID_LEN = 36 -STR_LEN = 255 - - -class HasTenant(object): - """Tenant mixin, add to subclasses that have a tenant.""" - - tenant_id = sa.Column(sa.String(db_const.PROJECT_ID_FIELD_SIZE), - index=True) - - -class AristaProvisionedNets(model_base.BASEV2, model_base.HasId, - HasTenant): - """Stores networks provisioned on Arista EOS. - - Saves the segmentation ID for each network that is provisioned - on EOS. This information is used during synchronization between - Neutron and EOS. - """ - __tablename__ = 'arista_provisioned_nets' - - network_id = sa.Column(sa.String(UUID_LEN)) - segmentation_id = sa.Column(sa.Integer) - - def eos_network_representation(self, segmentation_type): - return {u'networkId': self.network_id, - u'segmentationTypeId': self.segmentation_id, - u'segmentationType': segmentation_type, - u'tenantId': self.tenant_id, - u'segmentId': self.id, - } - - -class AristaProvisionedVms(model_base.BASEV2, model_base.HasId, - HasTenant): - """Stores VMs provisioned on Arista EOS. - - All VMs launched on physical hosts connected to Arista - Switches are remembered - """ - __tablename__ = 'arista_provisioned_vms' - - vm_id = sa.Column(sa.String(STR_LEN)) - host_id = sa.Column(sa.String(STR_LEN)) - port_id = sa.Column(sa.String(UUID_LEN)) - network_id = sa.Column(sa.String(UUID_LEN)) - - def eos_port_representation(self): - return {u'portId': self.port_id, - u'deviceId': self.vm_id, - u'hosts': [self.host_id], - u'networkId': self.network_id} - - -class AristaProvisionedTenants(model_base.BASEV2, model_base.HasId, - HasTenant): - """Stores Tenants provisioned on Arista EOS. - - Tenants list is maintained for sync between Neutron and EOS. - """ - __tablename__ = 'arista_provisioned_tenants' - - def eos_tenant_representation(self): - return {u'tenantId': self.tenant_id} diff --git a/networking_arista/common/db_lib.py b/networking_arista/common/db_lib.py deleted file mode 100644 index b23ab90..0000000 --- a/networking_arista/common/db_lib.py +++ /dev/null @@ -1,595 +0,0 @@ -# Copyright (c) 2013 OpenStack Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from neutron_lib import constants as n_const -from neutron_lib import context as nctx -from neutron_lib.plugins.ml2 import api as driver_api - -import neutron.db.api as db -from neutron.db import db_base_plugin_v2 -from neutron.db import securitygroups_db as sec_db -from neutron.db import segments_db -from neutron.plugins.ml2 import models as ml2_models - -from networking_arista.common import db as db_models - -VLAN_SEGMENTATION = 'vlan' - - -def remember_tenant(tenant_id): - """Stores a tenant information in repository. - - :param tenant_id: globally unique neutron tenant identifier - """ - session = db.get_writer_session() - with session.begin(): - tenant = (session.query(db_models.AristaProvisionedTenants). - filter_by(tenant_id=tenant_id).first()) - if not tenant: - tenant = db_models.AristaProvisionedTenants(tenant_id=tenant_id) - session.add(tenant) - - -def forget_tenant(tenant_id): - """Removes a tenant information from repository. - - :param tenant_id: globally unique neutron tenant identifier - """ - session = db.get_writer_session() - with session.begin(): - (session.query(db_models.AristaProvisionedTenants). - filter_by(tenant_id=tenant_id). - delete()) - - -def get_all_tenants(): - """Returns a list of all tenants stored in repository.""" - session = db.get_reader_session() - with session.begin(): - return session.query(db_models.AristaProvisionedTenants).all() - - -def num_provisioned_tenants(): - """Returns number of tenants stored in repository.""" - session = db.get_reader_session() - with session.begin(): - return session.query(db_models.AristaProvisionedTenants).count() - - -def remember_vm(vm_id, host_id, port_id, network_id, tenant_id): - """Stores all relevant information about a VM in repository. - - :param vm_id: globally unique identifier for VM instance - :param host_id: ID of the host where the VM is placed - :param port_id: globally unique port ID that connects VM to network - :param network_id: globally unique neutron network identifier - :param tenant_id: globally unique neutron tenant identifier - """ - session = db.get_writer_session() - with session.begin(): - vm = db_models.AristaProvisionedVms( - vm_id=vm_id, - host_id=host_id, - port_id=port_id, - network_id=network_id, - tenant_id=tenant_id) - session.add(vm) - - -def forget_all_ports_for_network(net_id): - """Removes all ports for a given network fron repository. - - :param net_id: globally unique network ID - """ - session = db.get_writer_session() - with session.begin(): - (session.query(db_models.AristaProvisionedVms). - filter_by(network_id=net_id).delete()) - - -def update_port(vm_id, host_id, port_id, network_id, tenant_id): - """Updates the port details in the database. - - :param vm_id: globally unique identifier for VM instance - :param host_id: ID of the new host where the VM is placed - :param port_id: globally unique port ID that connects VM to network - :param network_id: globally unique neutron network identifier - :param tenant_id: globally unique neutron tenant identifier - """ - session = db.get_writer_session() - with session.begin(): - port = session.query(db_models.AristaProvisionedVms).filter_by( - port_id=port_id).first() - if port: - # Update the VM's host id - port.host_id = host_id - port.vm_id = vm_id - port.network_id = network_id - port.tenant_id = tenant_id - - -def forget_port(port_id, host_id): - """Deletes the port from the database - - :param port_id: globally unique port ID that connects VM to network - :param host_id: host to which the port is bound to - """ - session = db.get_writer_session() - with session.begin(): - session.query(db_models.AristaProvisionedVms).filter_by( - port_id=port_id, - host_id=host_id).delete() - - -def remember_network_segment(tenant_id, - network_id, segmentation_id, segment_id): - """Stores all relevant information about a Network in repository. - - :param tenant_id: globally unique neutron tenant identifier - :param network_id: globally unique neutron network identifier - :param segmentation_id: segmentation id that is assigned to the network - :param segment_id: globally unique neutron network segment identifier - """ - session = db.get_writer_session() - with session.begin(): - net = db_models.AristaProvisionedNets( - tenant_id=tenant_id, - id=segment_id, - network_id=network_id, - segmentation_id=segmentation_id) - session.add(net) - - -def forget_network_segment(tenant_id, network_id, segment_id=None): - """Deletes all relevant information about a Network from repository. - - :param tenant_id: globally unique neutron tenant identifier - :param network_id: globally unique neutron network identifier - :param segment_id: globally unique neutron network segment identifier - """ - filters = { - 'tenant_id': tenant_id, - 'network_id': network_id - } - if segment_id: - filters['id'] = segment_id - - session = db.get_writer_session() - with session.begin(): - (session.query(db_models.AristaProvisionedNets). - filter_by(**filters).delete()) - - -def get_segmentation_id(tenant_id, network_id): - """Returns Segmentation ID (VLAN) associated with a network. - - :param tenant_id: globally unique neutron tenant identifier - :param network_id: globally unique neutron network identifier - """ - session = db.get_reader_session() - with session.begin(): - net = (session.query(db_models.AristaProvisionedNets). - filter_by(tenant_id=tenant_id, - network_id=network_id).first()) - return net.segmentation_id if net else None - - -def is_vm_provisioned(vm_id, host_id, port_id, - network_id, tenant_id): - """Checks if a VM is already known to EOS - - :returns: True, if yes; False otherwise. - :param vm_id: globally unique identifier for VM instance - :param host_id: ID of the host where the VM is placed - :param port_id: globally unique port ID that connects VM to network - :param network_id: globally unique neutron network identifier - :param tenant_id: globally unique neutron tenant identifier - """ - session = db.get_reader_session() - with session.begin(): - num_vm = (session.query(db_models.AristaProvisionedVms). - filter_by(tenant_id=tenant_id, - vm_id=vm_id, - port_id=port_id, - network_id=network_id, - host_id=host_id).count()) - return num_vm > 0 - - -def is_port_provisioned(port_id, host_id=None): - """Checks if a port is already known to EOS - - :returns: True, if yes; False otherwise. - :param port_id: globally unique port ID that connects VM to network - :param host_id: host to which the port is bound to - """ - - filters = { - 'port_id': port_id - } - if host_id: - filters['host_id'] = host_id - - session = db.get_reader_session() - with session.begin(): - num_ports = (session.query(db_models.AristaProvisionedVms). - filter_by(**filters).count()) - return num_ports > 0 - - -def is_network_provisioned(tenant_id, network_id, segmentation_id=None, - segment_id=None): - """Checks if a networks is already known to EOS - - :returns: True, if yes; False otherwise. - :param tenant_id: globally unique neutron tenant identifier - :param network_id: globally unique neutron network identifier - :param segment_id: globally unique neutron network segment identifier - """ - session = db.get_reader_session() - with session.begin(): - filters = {'tenant_id': tenant_id, - 'network_id': network_id} - if segmentation_id: - filters['segmentation_id'] = segmentation_id - if segment_id: - filters['id'] = segment_id - - num_nets = (session.query(db_models.AristaProvisionedNets). - filter_by(**filters).count()) - - return num_nets > 0 - - -def is_tenant_provisioned(tenant_id): - """Checks if a tenant is already known to EOS - - :returns: True, if yes; False otherwise. - :param tenant_id: globally unique neutron tenant identifier - """ - session = db.get_reader_session() - with session.begin(): - num_tenants = (session.query(db_models.AristaProvisionedTenants). - filter_by(tenant_id=tenant_id).count()) - return num_tenants > 0 - - -def num_nets_provisioned(tenant_id): - """Returns number of networks for a given tennat. - - :param tenant_id: globally unique neutron tenant identifier - """ - session = db.get_reader_session() - with session.begin(): - return (session.query(db_models.AristaProvisionedNets). - filter_by(tenant_id=tenant_id).count()) - - -def num_vms_provisioned(tenant_id): - """Returns number of VMs for a given tennat. - - :param tenant_id: globally unique neutron tenant identifier - """ - session = db.get_reader_session() - with session.begin(): - return (session.query(db_models.AristaProvisionedVms). - filter_by(tenant_id=tenant_id).count()) - - -def get_networks(tenant_id): - """Returns all networks for a given tenant in EOS-compatible format. - - See AristaRPCWrapper.get_network_list() for return value format. - :param tenant_id: globally unique neutron tenant identifier - """ - session = db.get_reader_session() - with session.begin(): - model = db_models.AristaProvisionedNets - # hack for pep8 E711: comparison to None should be - # 'if cond is not None' - none = None - all_nets = [] - if tenant_id != 'any': - all_nets = (session.query(model). - filter(model.tenant_id == tenant_id, - model.segmentation_id != none)) - else: - all_nets = (session.query(model). - filter(model.segmentation_id != none)) - - res = dict( - (net.network_id, net.eos_network_representation( - VLAN_SEGMENTATION)) - for net in all_nets - ) - return res - - -def get_vms(tenant_id): - """Returns all VMs for a given tenant in EOS-compatible format. - - :param tenant_id: globally unique neutron tenant identifier - """ - session = db.get_reader_session() - with session.begin(): - model = db_models.AristaProvisionedVms - # hack for pep8 E711: comparison to None should be - # 'if cond is not None' - none = None - all_ports = (session.query(model). - filter(model.tenant_id == tenant_id, - model.host_id != none, - model.vm_id != none, - model.network_id != none, - model.port_id != none)) - ports = {} - for port in all_ports: - if port.port_id not in ports: - ports[port.port_id] = port.eos_port_representation() - else: - ports[port.port_id]['hosts'].append(port.host_id) - - vm_dict = dict() - - def eos_vm_representation(port): - return {u'vmId': port['deviceId'], - u'baremetal_instance': False, - u'ports': [port]} - - for port in ports.values(): - deviceId = port['deviceId'] - if deviceId in vm_dict: - vm_dict[deviceId]['ports'].append(port) - else: - vm_dict[deviceId] = eos_vm_representation(port) - return vm_dict - - -def are_ports_attached_to_network(net_id): - """Checks if a given network is used by any port, excluding dhcp port. - - :param net_id: globally unique network ID - """ - session = db.get_reader_session() - with session.begin(): - model = db_models.AristaProvisionedVms - return session.query(model).filter_by(network_id=net_id).filter( - ~model.vm_id.startswith('dhcp')).count() > 0 - - -def get_ports(tenant_id=None): - """Returns all ports of VMs in EOS-compatible format. - - :param tenant_id: globally unique neutron tenant identifier - """ - session = db.get_reader_session() - with session.begin(): - model = db_models.AristaProvisionedVms - # hack for pep8 E711: comparison to None should be - # 'if cond is not None' - none = None - if tenant_id: - all_ports = (session.query(model). - filter(model.tenant_id == tenant_id, - model.host_id != none, - model.vm_id != none, - model.network_id != none, - model.port_id != none)) - else: - all_ports = (session.query(model). - filter(model.tenant_id != none, - model.host_id != none, - model.vm_id != none, - model.network_id != none, - model.port_id != none)) - ports = {} - for port in all_ports: - if port.port_id not in ports: - ports[port.port_id] = port.eos_port_representation() - ports[port.port_id]['hosts'].append(port.host_id) - - return ports - - -def get_tenants(): - """Returns list of all tenants in EOS-compatible format.""" - session = db.get_reader_session() - with session.begin(): - model = db_models.AristaProvisionedTenants - all_tenants = session.query(model) - res = dict( - (tenant.tenant_id, tenant.eos_tenant_representation()) - for tenant in all_tenants - ) - return res - - -def _make_port_dict(record): - """Make a dict from the BM profile DB record.""" - return {'port_id': record.port_id, - 'host_id': record.host, - 'vnic_type': record.vnic_type, - 'profile': record.profile} - - -def get_all_baremetal_ports(): - """Returns a list of all ports that belong to baremetal hosts.""" - session = db.get_reader_session() - with session.begin(): - querry = session.query(ml2_models.PortBinding) - bm_ports = querry.filter_by(vnic_type='baremetal').all() - - return {bm_port.port_id: _make_port_dict(bm_port) - for bm_port in bm_ports} - - -def get_all_portbindings(): - """Returns a list of all ports bindings.""" - session = db.get_session() - with session.begin(): - query = session.query(ml2_models.PortBinding) - ports = query.all() - - return {port.port_id: _make_port_dict(port) - for port in ports} - - -def get_port_binding_level(filters): - """Returns entries from PortBindingLevel based on the specified filters.""" - session = db.get_reader_session() - with session.begin(): - return (session.query(ml2_models.PortBindingLevel). - filter_by(**filters).all()) - - -class NeutronNets(db_base_plugin_v2.NeutronDbPluginV2, - sec_db.SecurityGroupDbMixin): - """Access to Neutron DB. - - Provides access to the Neutron Data bases for all provisioned - networks as well ports. This data is used during the synchronization - of DB between ML2 Mechanism Driver and Arista EOS - Names of the networks and ports are not stroed in Arista repository - They are pulled from Neutron DB. - """ - - def __init__(self): - self.admin_ctx = nctx.get_admin_context() - - def get_network_name(self, tenant_id, network_id): - network = self._get_network(tenant_id, network_id) - network_name = None - if network: - network_name = network[0]['name'] - return network_name - - def get_all_networks_for_tenant(self, tenant_id): - filters = {'tenant_id': [tenant_id]} - return super(NeutronNets, - self).get_networks(self.admin_ctx, filters=filters) or [] - - def get_all_networks(self): - return super(NeutronNets, self).get_networks(self.admin_ctx) or [] - - def get_all_ports_for_tenant(self, tenant_id): - filters = {'tenant_id': [tenant_id]} - return super(NeutronNets, - self).get_ports(self.admin_ctx, filters=filters) or [] - - def get_shared_network_owner_id(self, network_id): - filters = {'id': [network_id]} - nets = self.get_networks(self.admin_ctx, filters=filters) or [] - segments = segments_db.get_network_segments(self.admin_ctx, - network_id) - if not nets or not segments: - return - if (nets[0]['shared'] and - segments[0][driver_api.NETWORK_TYPE] == n_const.TYPE_VLAN): - return nets[0]['tenant_id'] - - def get_network_segments(self, network_id, dynamic=False, context=None): - context = context if context is not None else self.admin_ctx - segments = segments_db.get_network_segments(context, network_id, - filter_dynamic=dynamic) - if dynamic: - for segment in segments: - segment['is_dynamic'] = True - return segments - - def get_all_network_segments(self, network_id, context=None): - segments = self.get_network_segments(network_id, context=context) - segments += self.get_network_segments(network_id, dynamic=True, - context=context) - return segments - - def get_segment_by_id(self, context, segment_id): - return segments_db.get_segment_by_id(context, - segment_id) - - def get_network_from_net_id(self, network_id, context=None): - filters = {'id': [network_id]} - ctxt = context if context else self.admin_ctx - return super(NeutronNets, - self).get_networks(ctxt, filters=filters) or [] - - def _get_network(self, tenant_id, network_id): - filters = {'tenant_id': [tenant_id], - 'id': [network_id]} - return super(NeutronNets, - self).get_networks(self.admin_ctx, filters=filters) or [] - - def get_subnet_info(self, subnet_id): - return self.get_subnet(subnet_id) - - def get_subnet_ip_version(self, subnet_id): - subnet = self.get_subnet(subnet_id) - return subnet['ip_version'] if 'ip_version' in subnet else None - - def get_subnet_gateway_ip(self, subnet_id): - subnet = self.get_subnet(subnet_id) - return subnet['gateway_ip'] if 'gateway_ip' in subnet else None - - def get_subnet_cidr(self, subnet_id): - subnet = self.get_subnet(subnet_id) - return subnet['cidr'] if 'cidr' in subnet else None - - def get_network_id(self, subnet_id): - subnet = self.get_subnet(subnet_id) - return subnet['network_id'] if 'network_id' in subnet else None - - def get_network_id_from_port_id(self, port_id): - port = self.get_port(port_id) - return port['network_id'] if 'network_id' in port else None - - def get_subnet(self, subnet_id): - return super(NeutronNets, - self).get_subnet(self.admin_ctx, subnet_id) or {} - - def get_port(self, port_id): - return super(NeutronNets, - self).get_port(self.admin_ctx, port_id) or {} - - def get_all_security_gp_to_port_bindings(self): - return super(NeutronNets, self)._get_port_security_group_bindings( - self.admin_ctx) or [] - - def get_security_gp_to_port_bindings(self, sec_gp_id): - filters = {'security_group_id': [sec_gp_id]} - return super(NeutronNets, self)._get_port_security_group_bindings( - self.admin_ctx, filters=filters) or [] - - def get_security_group(self, sec_gp_id): - return super(NeutronNets, - self).get_security_group(self.admin_ctx, sec_gp_id) or [] - - def get_security_groups(self): - sgs = super(NeutronNets, - self).get_security_groups(self.admin_ctx) or [] - sgs_all = {} - if sgs: - for s in sgs: - sgs_all[s['id']] = s - return sgs_all - - def get_security_group_rule(self, sec_gpr_id): - return super(NeutronNets, - self).get_security_group_rule(self.admin_ctx, - sec_gpr_id) or [] - - def validate_network_rbac_policy_change(self, resource, event, trigger, - context, object_type, policy, - **kwargs): - return super(NeutronNets, self).validate_network_rbac_policy_change( - resource, event, trigger, context, object_type, policy, kwargs) diff --git a/networking_arista/common/exceptions.py b/networking_arista/common/exceptions.py deleted file mode 100644 index 99ed207..0000000 --- a/networking_arista/common/exceptions.py +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright (c) 2013 OpenStack Foundation -# -# 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. - - -"""Exceptions used by Arista ML2 Mechanism Driver.""" - -from neutron_lib import exceptions - -from networking_arista._i18n import _ - - -class AristaRpcError(exceptions.NeutronException): - message = _('%(msg)s') - - -class AristaConfigError(exceptions.NeutronException): - message = _('%(msg)s') - - -class AristaServicePluginRpcError(exceptions.NeutronException): - message = _('%(msg)s') - - -class AristaServicePluginConfigError(exceptions.NeutronException): - message = _('%(msg)s') - - -class AristaSecurityGroupError(exceptions.NeutronException): - message = _('%(msg)s') - - -class VlanUnavailable(exceptions.NeutronException): - """An exception indicating VLAN creation failed because it's not available. - - A specialization of the NeutronException indicating network creation failed - because a specified VLAN is unavailable on the physical network. - - :param vlan_id: The VLAN ID. - :param physical_network: The physical network. - """ - message = _("Unable to create the network. " - "The VLAN %(vlan_id)s on physical network " - "%(physical_network)s is not available.") diff --git a/networking_arista/db/README b/networking_arista/db/README deleted file mode 100644 index 42ffac0..0000000 --- a/networking_arista/db/README +++ /dev/null @@ -1 +0,0 @@ -Alembic database migration scripts for the networking-arista package. diff --git a/networking_arista/db/__init__.py b/networking_arista/db/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/networking_arista/db/migration/README b/networking_arista/db/migration/README deleted file mode 100644 index 42ffac0..0000000 --- a/networking_arista/db/migration/README +++ /dev/null @@ -1 +0,0 @@ -Alembic database migration scripts for the networking-arista package. diff --git a/networking_arista/db/migration/__init__.py b/networking_arista/db/migration/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/networking_arista/db/migration/alembic_migrations/README b/networking_arista/db/migration/alembic_migrations/README deleted file mode 100644 index 98e4f9c..0000000 --- a/networking_arista/db/migration/alembic_migrations/README +++ /dev/null @@ -1 +0,0 @@ -Generic single-database configuration. \ No newline at end of file diff --git a/networking_arista/db/migration/alembic_migrations/__init__.py b/networking_arista/db/migration/alembic_migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/networking_arista/db/migration/alembic_migrations/env.py b/networking_arista/db/migration/alembic_migrations/env.py deleted file mode 100644 index 6f650fa..0000000 --- a/networking_arista/db/migration/alembic_migrations/env.py +++ /dev/null @@ -1,123 +0,0 @@ -# Copyright (c) 2015 Arista Networks, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from logging.config import fileConfig - -from alembic import context -from neutron_lib.db import model_base -from oslo_config import cfg -from oslo_db.sqlalchemy import session -import sqlalchemy as sa -from sqlalchemy import event - -from neutron.db.migration.alembic_migrations import external -from neutron.db.migration.models import head # noqa - -# this is the Alembic Config object, which provides -# access to the values within the .ini file in use. -config = context.config -neutron_config = config.neutron_config - -# Interpret the config file for Python logging. -# This line sets up loggers basically. -fileConfig(config.config_file_name) - -# add your model's MetaData object here -# for 'autogenerate' support -# from myapp import mymodel -# target_metadata = mymodel.Base.metadata -target_metadata = model_base.BASEV2.metadata - -MYSQL_ENGINE = None -ARISTA_VERSION_TABLE = 'arista_alembic_version' - - -def set_mysql_engine(): - try: - mysql_engine = neutron_config.command.mysql_engine - except cfg.NoSuchOptError: - mysql_engine = None - - global MYSQL_ENGINE - MYSQL_ENGINE = (mysql_engine or - model_base.BASEV2.__table_args__['mysql_engine']) - - -def include_object(object, name, type_, reflected, compare_to): - if type_ == 'table' and name in external.TABLES: - return False - else: - return True - - -def run_migrations_offline(): - """Run migrations in 'offline' mode. - - This configures the context with just a URL or an Engine. - - Calls to context.execute() here emit the given string to the - script output. - - """ - set_mysql_engine() - - kwargs = dict() - if neutron_config.database.connection: - kwargs['url'] = neutron_config.database.connection - else: - kwargs['dialect_name'] = neutron_config.database.engine - kwargs['include_object'] = include_object - kwargs['version_table'] = ARISTA_VERSION_TABLE - context.configure(**kwargs) - - with context.begin_transaction(): - context.run_migrations() - - -@event.listens_for(sa.Table, 'after_parent_attach') -def set_storage_engine(target, parent): - if MYSQL_ENGINE: - target.kwargs['mysql_engine'] = MYSQL_ENGINE - - -def run_migrations_online(): - """Run migrations in 'online' mode. - - In this scenario we need to create an Engine - and associate a connection with the context. - - """ - set_mysql_engine() - engine = session.create_engine(neutron_config.database.connection) - - connection = engine.connect() - context.configure( - connection=connection, - target_metadata=target_metadata, - include_object=include_object, - version_table=ARISTA_VERSION_TABLE, - ) - - try: - with context.begin_transaction(): - context.run_migrations() - finally: - connection.close() - engine.dispose() - - -if context.is_offline_mode(): - run_migrations_offline() -else: - run_migrations_online() diff --git a/networking_arista/db/migration/alembic_migrations/script.py.mako b/networking_arista/db/migration/alembic_migrations/script.py.mako deleted file mode 100644 index 8e09322..0000000 --- a/networking_arista/db/migration/alembic_migrations/script.py.mako +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright ${create_date.year} Arista Networks, Inc. -# -# 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. -# - -"""${message} - -Revision ID: ${up_revision} -Revises: ${down_revision | comma,n} -Create Date: ${create_date} - -""" - -# revision identifiers, used by Alembic. -revision = ${repr(up_revision)} -down_revision = ${repr(down_revision)} -branch_labels = ${repr(branch_labels)} -depends_on = ${repr(depends_on)} - -from alembic import op -import sqlalchemy as sa -${imports if imports else ""} - - -def upgrade(): - ${upgrades if upgrades else "pass"} diff --git a/networking_arista/db/migration/alembic_migrations/versions/296b4e0236e0_initial_db_version.py b/networking_arista/db/migration/alembic_migrations/versions/296b4e0236e0_initial_db_version.py deleted file mode 100644 index ac83959..0000000 --- a/networking_arista/db/migration/alembic_migrations/versions/296b4e0236e0_initial_db_version.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright (c) 2015 Arista Networks, Inc. -# -# 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. -# - -"""Initial db version - -Revision ID: 296b4e0236e0 -Create Date: 2015-10-23 14:37:49.594974 - -""" - -# revision identifiers, used by Alembic. -revision = '296b4e0236e0' -down_revision = None - - -def upgrade(): - pass diff --git a/networking_arista/db/migration/alembic_migrations/versions/__init__.py b/networking_arista/db/migration/alembic_migrations/versions/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/networking_arista/db/migration/alembic_migrations/versions/liberty/contract/47036dc8697a_initial_db_version_contract.py b/networking_arista/db/migration/alembic_migrations/versions/liberty/contract/47036dc8697a_initial_db_version_contract.py deleted file mode 100644 index b001626..0000000 --- a/networking_arista/db/migration/alembic_migrations/versions/liberty/contract/47036dc8697a_initial_db_version_contract.py +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright (c) 2015 Arista Networks, Inc. -# -# 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. -# - -"""Initial db version - -Revision ID: 47036dc8697a -Create Date: 2015-10-23 14:37:49.594974 - -""" - -from neutron.db.migration import cli - -# revision identifiers, used by Alembic. -revision = '47036dc8697a' -down_revision = '296b4e0236e0' -branch_labels = (cli.CONTRACT_BRANCH,) - - -def upgrade(): - pass diff --git a/networking_arista/db/migration/alembic_migrations/versions/liberty/expand/1c6993ce7db0_initial_db_version_expand.py b/networking_arista/db/migration/alembic_migrations/versions/liberty/expand/1c6993ce7db0_initial_db_version_expand.py deleted file mode 100644 index 90d956e..0000000 --- a/networking_arista/db/migration/alembic_migrations/versions/liberty/expand/1c6993ce7db0_initial_db_version_expand.py +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright (c) 2015 Arista Networks, Inc. -# -# 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. -# - -"""Initial db version - -Revision ID: 1c6993ce7db0 -Create Date: 2015-10-23 14:37:49.594974 - -""" - -from neutron.db.migration import cli - -# revision identifiers, used by Alembic. -revision = '1c6993ce7db0' -down_revision = '296b4e0236e0' -branch_labels = (cli.EXPAND_BRANCH,) - - -def upgrade(): - pass diff --git a/networking_arista/l3Plugin/__init__.py b/networking_arista/l3Plugin/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/networking_arista/l3Plugin/arista_l3_driver.py b/networking_arista/l3Plugin/arista_l3_driver.py deleted file mode 100644 index 649be74..0000000 --- a/networking_arista/l3Plugin/arista_l3_driver.py +++ /dev/null @@ -1,457 +0,0 @@ -# Copyright 2014 Arista Networks, Inc. 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. - -import hashlib -import socket -import struct - -from oslo_config import cfg -from oslo_log import log as logging - -from networking_arista._i18n import _, _LI -from networking_arista.common import api -from networking_arista.common import exceptions as arista_exc - -LOG = logging.getLogger(__name__) -cfg.CONF.import_group('l3_arista', 'networking_arista.common.config') - -EOS_UNREACHABLE_MSG = _('Unable to reach EOS') -DEFAULT_VLAN = 1 -MLAG_SWITCHES = 2 -VIRTUAL_ROUTER_MAC = '00:11:22:33:44:55' -IPV4_BITS = 32 -IPV6_BITS = 128 - -# This string-format-at-a-distance confuses pylint :( -# pylint: disable=too-many-format-args -router_in_vrf = { - 'router': {'create': ['vrf definition {0}', - 'rd {1}', - 'exit'], - 'delete': ['no vrf definition {0}']}, - - 'interface': {'add': ['ip routing vrf {1}', - 'vlan {0}', - 'exit', - 'interface vlan {0}', - 'vrf forwarding {1}', - 'ip address {2}'], - 'remove': ['no interface vlan {0}']}} - -router_in_default_vrf = { - 'router': {'create': [], # Place holder for now. - 'delete': []}, # Place holder for now. - - 'interface': {'add': ['ip routing', - 'vlan {0}', - 'exit', - 'interface vlan {0}', - 'ip address {2}'], - 'remove': ['no interface vlan {0}']}} - -router_in_default_vrf_v6 = { - 'router': {'create': [], - 'delete': []}, - - 'interface': {'add': ['ipv6 unicast-routing', - 'vlan {0}', - 'exit', - 'interface vlan {0}', - 'ipv6 enable', - 'ipv6 address {2}'], - 'remove': ['no interface vlan {0}']}} - -additional_cmds_for_mlag = { - 'router': {'create': ['ip virtual-router mac-address {0}'], - 'delete': []}, - - 'interface': {'add': ['ip virtual-router address {0}'], - 'remove': []}} - -additional_cmds_for_mlag_v6 = { - 'router': {'create': [], - 'delete': []}, - - 'interface': {'add': ['ipv6 virtual-router address {0}'], - 'remove': []}} - - -class AristaL3Driver(object): - """Wraps Arista JSON RPC. - - All communications between Neutron and EOS are over JSON RPC. - EOS - operating system used on Arista hardware - Command API - JSON RPC API provided by Arista EOS - """ - def __init__(self): - self._servers = [] - self._hosts = [] - self._interfaceDict = None - self._validate_config() - host = cfg.CONF.l3_arista.primary_l3_host - self._hosts.append(host) - self._servers.append(self._make_eapi_client(host)) - self._mlag_configured = cfg.CONF.l3_arista.mlag_config - self._use_vrf = cfg.CONF.l3_arista.use_vrf - if self._mlag_configured: - host = cfg.CONF.l3_arista.secondary_l3_host - self._hosts.append(host) - self._servers.append(self._make_eapi_client(host)) - self._additionalRouterCmdsDict = additional_cmds_for_mlag['router'] - self._additionalInterfaceCmdsDict = ( - additional_cmds_for_mlag['interface']) - if self._use_vrf: - self.routerDict = router_in_vrf['router'] - self._interfaceDict = router_in_vrf['interface'] - else: - self.routerDict = router_in_default_vrf['router'] - self._interfaceDict = router_in_default_vrf['interface'] - - @staticmethod - def _make_eapi_client(host): - return api.EAPIClient( - host, - username=cfg.CONF.l3_arista.primary_l3_host_username, - password=cfg.CONF.l3_arista.primary_l3_host_password, - verify=False, - timeout=cfg.CONF.l3_arista.conn_timeout - ) - - def _validate_config(self): - if cfg.CONF.l3_arista.get('primary_l3_host') == '': - msg = _('Required option primary_l3_host is not set') - LOG.error(msg) - raise arista_exc.AristaServicePluginConfigError(msg=msg) - if cfg.CONF.l3_arista.get('mlag_config'): - if cfg.CONF.l3_arista.get('use_vrf'): - # This is invalid/unsupported configuration - msg = _('VRFs are not supported MLAG config mode') - LOG.error(msg) - raise arista_exc.AristaServicePluginConfigError(msg=msg) - if cfg.CONF.l3_arista.get('secondary_l3_host') == '': - msg = _('Required option secondary_l3_host is not set') - LOG.error(msg) - raise arista_exc.AristaServicePluginConfigError(msg=msg) - if cfg.CONF.l3_arista.get('primary_l3_host_username') == '': - msg = _('Required option primary_l3_host_username is not set') - LOG.error(msg) - raise arista_exc.AristaServicePluginConfigError(msg=msg) - - def create_router_on_eos(self, router_name, rdm, server): - """Creates a router on Arista HW Device. - - :param router_name: globally unique identifier for router/VRF - :param rdm: A value generated by hashing router name - :param server: Server endpoint on the Arista switch to be configured - """ - cmds = [] - rd = "%s:%s" % (rdm, rdm) - - for c in self.routerDict['create']: - cmds.append(c.format(router_name, rd)) - - if self._mlag_configured: - mac = VIRTUAL_ROUTER_MAC - for c in self._additionalRouterCmdsDict['create']: - cmds.append(c.format(mac)) - - self._run_openstack_l3_cmds(cmds, server) - - def delete_router_from_eos(self, router_name, server): - """Deletes a router from Arista HW Device. - - :param router_name: globally unique identifier for router/VRF - :param server: Server endpoint on the Arista switch to be configured - """ - cmds = [] - for c in self.routerDict['delete']: - cmds.append(c.format(router_name)) - if self._mlag_configured: - for c in self._additionalRouterCmdsDict['delete']: - cmds.append(c) - - self._run_openstack_l3_cmds(cmds, server) - - def _select_dicts(self, ipv): - if self._use_vrf: - self._interfaceDict = router_in_vrf['interface'] - else: - if ipv == 6: - # for IPv6 use IPv6 commmands - self._interfaceDict = router_in_default_vrf_v6['interface'] - self._additionalInterfaceCmdsDict = ( - additional_cmds_for_mlag_v6['interface']) - else: - self._interfaceDict = router_in_default_vrf['interface'] - self._additionalInterfaceCmdsDict = ( - additional_cmds_for_mlag['interface']) - - def add_interface_to_router(self, segment_id, - router_name, gip, router_ip, mask, server): - """Adds an interface to existing HW router on Arista HW device. - - :param segment_id: VLAN Id associated with interface that is added - :param router_name: globally unique identifier for router/VRF - :param gip: Gateway IP associated with the subnet - :param router_ip: IP address of the router - :param mask: subnet mask to be used - :param server: Server endpoint on the Arista switch to be configured - """ - - if not segment_id: - segment_id = DEFAULT_VLAN - cmds = [] - for c in self._interfaceDict['add']: - if self._mlag_configured: - # In VARP config, use router ID else, use gateway IP address. - ip = router_ip - else: - ip = gip + '/' + mask - cmds.append(c.format(segment_id, router_name, ip)) - if self._mlag_configured: - for c in self._additionalInterfaceCmdsDict['add']: - cmds.append(c.format(gip)) - - self._run_openstack_l3_cmds(cmds, server) - - def delete_interface_from_router(self, segment_id, router_name, server): - """Deletes an interface from existing HW router on Arista HW device. - - :param segment_id: VLAN Id associated with interface that is added - :param router_name: globally unique identifier for router/VRF - :param server: Server endpoint on the Arista switch to be configured - """ - - if not segment_id: - segment_id = DEFAULT_VLAN - cmds = [] - for c in self._interfaceDict['remove']: - cmds.append(c.format(segment_id)) - - self._run_openstack_l3_cmds(cmds, server) - - def create_router(self, context, tenant_id, router): - """Creates a router on Arista Switch. - - Deals with multiple configurations - such as Router per VRF, - a router in default VRF, Virtual Router in MLAG configurations - """ - if router: - router_name = self._arista_router_name(tenant_id, router['name']) - - hashed = hashlib.sha256(router_name.encode('utf-8')) - rdm = str(int(hashed.hexdigest(), 16) % 65536) - - mlag_peer_failed = False - for s in self._servers: - try: - self.create_router_on_eos(router_name, rdm, s) - mlag_peer_failed = False - except Exception: - if self._mlag_configured and not mlag_peer_failed: - # In paied switch, it is OK to fail on one switch - mlag_peer_failed = True - else: - msg = (_('Failed to create router %s on EOS') % - router_name) - LOG.exception(msg) - raise arista_exc.AristaServicePluginRpcError(msg=msg) - - def delete_router(self, context, tenant_id, router_id, router): - """Deletes a router from Arista Switch.""" - - if router: - router_name = self._arista_router_name(tenant_id, router['name']) - mlag_peer_failed = False - for s in self._servers: - try: - self.delete_router_from_eos(router_name, s) - mlag_peer_failed = False - except Exception: - if self._mlag_configured and not mlag_peer_failed: - # In paied switch, it is OK to fail on one switch - mlag_peer_failed = True - else: - msg = (_('Failed to create router %s on EOS') % - router_name) - LOG.exception(msg) - raise arista_exc.AristaServicePluginRpcError(msg=msg) - - def update_router(self, context, router_id, original_router, new_router): - """Updates a router which is already created on Arista Switch. - - TODO: (Sukhdev) - to be implemented in next release. - """ - pass - - def add_router_interface(self, context, router_info): - """Adds an interface to a router created on Arista HW router. - - This deals with both IPv6 and IPv4 configurations. - """ - if router_info: - self._select_dicts(router_info['ip_version']) - cidr = router_info['cidr'] - subnet_mask = cidr.split('/')[1] - router_name = self._arista_router_name(router_info['tenant_id'], - router_info['name']) - if self._mlag_configured: - # For MLAG, we send a specific IP address as opposed to cidr - # For now, we are using x.x.x.253 and x.x.x.254 as virtual IP - mlag_peer_failed = False - for i, server in enumerate(self._servers): - # Get appropriate virtual IP address for this router - router_ip = self._get_router_ip(cidr, i, - router_info['ip_version']) - try: - self.add_interface_to_router(router_info['seg_id'], - router_name, - router_info['gip'], - router_ip, subnet_mask, - server) - mlag_peer_failed = False - except Exception: - if not mlag_peer_failed: - mlag_peer_failed = True - else: - msg = (_('Failed to add interface to router ' - '%s on EOS') % router_name) - LOG.exception(msg) - raise arista_exc.AristaServicePluginRpcError( - msg=msg) - - else: - for s in self._servers: - self.add_interface_to_router(router_info['seg_id'], - router_name, - router_info['gip'], - None, subnet_mask, s) - - def remove_router_interface(self, context, router_info): - """Removes previously configured interface from router on Arista HW. - - This deals with both IPv6 and IPv4 configurations. - """ - if router_info: - router_name = self._arista_router_name(router_info['tenant_id'], - router_info['name']) - mlag_peer_failed = False - for s in self._servers: - try: - self.delete_interface_from_router(router_info['seg_id'], - router_name, s) - if self._mlag_configured: - mlag_peer_failed = False - except Exception: - if self._mlag_configured and not mlag_peer_failed: - mlag_peer_failed = True - else: - msg = (_('Failed to add interface to router ' - '%s on EOS') % router_name) - LOG.exception(msg) - raise arista_exc.AristaServicePluginRpcError(msg=msg) - - def _run_openstack_l3_cmds(self, commands, server): - """Execute/sends a CAPI (Command API) command to EOS. - - In this method, list of commands is appended with prefix and - postfix commands - to make is understandble by EOS. - - :param commands : List of command to be executed on EOS. - :param server: Server endpoint on the Arista switch to be configured - """ - command_start = ['enable', 'configure'] - command_end = ['exit'] - full_command = command_start + commands + command_end - - LOG.info(_LI('Executing command on Arista EOS: %s'), full_command) - - try: - # this returns array of return values for every command in - # full_command list - ret = server.execute(full_command) - LOG.info(_LI('Results of execution on Arista EOS: %s'), ret) - - except Exception: - msg = (_('Error occurred while trying to execute ' - 'commands %(cmd)s on EOS %(host)s') % - {'cmd': full_command, 'host': server}) - LOG.exception(msg) - raise arista_exc.AristaServicePluginRpcError(msg=msg) - - def _arista_router_name(self, tenant_id, name): - """Generate an arista specific name for this router. - - Use a unique name so that OpenStack created routers/SVIs - can be distinguishged from the user created routers/SVIs - on Arista HW. - """ - return 'OS' + '-' + tenant_id + '-' + name - - def _get_binary_from_ipv4(self, ip_addr): - """Converts IPv4 address to binary form.""" - - return struct.unpack("!L", socket.inet_pton(socket.AF_INET, - ip_addr))[0] - - def _get_binary_from_ipv6(self, ip_addr): - """Converts IPv6 address to binary form.""" - - hi, lo = struct.unpack("!QQ", socket.inet_pton(socket.AF_INET6, - ip_addr)) - return (hi << 64) | lo - - def _get_ipv4_from_binary(self, bin_addr): - """Converts binary address to Ipv4 format.""" - - return socket.inet_ntop(socket.AF_INET, struct.pack("!L", bin_addr)) - - def _get_ipv6_from_binary(self, bin_addr): - """Converts binary address to Ipv6 format.""" - - hi = bin_addr >> 64 - lo = bin_addr & 0xFFFFFFFF - return socket.inet_ntop(socket.AF_INET6, struct.pack("!QQ", hi, lo)) - - def _get_router_ip(self, cidr, ip_count, ip_ver): - """For a given IP subnet and IP version type, generate IP for router. - - This method takes the network address (cidr) and selects an - IP address that should be assigned to virtual router running - on multiple switches. It uses upper addresses in a subnet address - as IP for the router. Each instace of the router, on each switch, - requires uniqe IP address. For example in IPv4 case, on a 255 - subnet, it will pick X.X.X.254 as first addess, X.X.X.253 for next, - and so on. - """ - start_ip = MLAG_SWITCHES + ip_count - network_addr, prefix = cidr.split('/') - if ip_ver == 4: - bits = IPV4_BITS - ip = self._get_binary_from_ipv4(network_addr) - elif ip_ver == 6: - bits = IPV6_BITS - ip = self._get_binary_from_ipv6(network_addr) - - mask = (pow(2, bits) - 1) << (bits - int(prefix)) - - network_addr = ip & mask - - router_ip = pow(2, bits - int(prefix)) - start_ip - - router_ip = network_addr | router_ip - if ip_ver == 4: - return self._get_ipv4_from_binary(router_ip) + '/' + prefix - else: - return self._get_ipv6_from_binary(router_ip) + '/' + prefix diff --git a/networking_arista/l3Plugin/l3_arista.py b/networking_arista/l3Plugin/l3_arista.py deleted file mode 100644 index 30a62ca..0000000 --- a/networking_arista/l3Plugin/l3_arista.py +++ /dev/null @@ -1,277 +0,0 @@ -# Copyright 2014 Arista Networks, Inc. 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. - -import copy -import threading - -from neutron_lib import constants as n_const -from neutron_lib import context as nctx -from neutron_lib.plugins import constants as plugin_constants -from oslo_config import cfg -from oslo_log import helpers as log_helpers -from oslo_log import log as logging -from oslo_utils import excutils - -from neutron.api.rpc.agentnotifiers import l3_rpc_agent_api -from neutron.api.rpc.handlers import l3_rpc -from neutron.common import rpc as n_rpc -from neutron.common import topics -from neutron.db import db_base_plugin_v2 -from neutron.db import extraroute_db -from neutron.db import l3_agentschedulers_db -from neutron.db import l3_gwmode_db -from neutron.plugins.ml2.driver_context import NetworkContext # noqa - -from networking_arista._i18n import _LE, _LI -from networking_arista.common import db_lib -from networking_arista.l3Plugin import arista_l3_driver - -LOG = logging.getLogger(__name__) - - -class AristaL3ServicePlugin(db_base_plugin_v2.NeutronDbPluginV2, - extraroute_db.ExtraRoute_db_mixin, - l3_gwmode_db.L3_NAT_db_mixin, - l3_agentschedulers_db.L3AgentSchedulerDbMixin): - - """Implements L3 Router service plugin for Arista hardware. - - Creates routers in Arista hardware, manages them, adds/deletes interfaces - to the routes. - """ - - supported_extension_aliases = ["router", "ext-gw-mode", - "extraroute"] - - def __init__(self, driver=None): - - self.driver = driver or arista_l3_driver.AristaL3Driver() - self.ndb = db_lib.NeutronNets() - self.setup_rpc() - self.sync_timeout = cfg.CONF.l3_arista.l3_sync_interval - self.sync_lock = threading.Lock() - self._synchronization_thread() - - def setup_rpc(self): - # RPC support - self.topic = topics.L3PLUGIN - self.conn = n_rpc.create_connection() - self.agent_notifiers.update( - {n_const.AGENT_TYPE_L3: l3_rpc_agent_api.L3AgentNotifyAPI()}) - self.endpoints = [l3_rpc.L3RpcCallback()] - self.conn.create_consumer(self.topic, self.endpoints, - fanout=False) - self.conn.consume_in_threads() - - def get_plugin_type(self): - return plugin_constants.L3 - - def get_plugin_description(self): - """Returns string description of the plugin.""" - return ("Arista L3 Router Service Plugin for Arista Hardware " - "based routing") - - def _synchronization_thread(self): - with self.sync_lock: - self.synchronize() - - self.timer = threading.Timer(self.sync_timeout, - self._synchronization_thread) - self.timer.start() - - def stop_synchronization_thread(self): - if self.timer: - self.timer.cancel() - self.timer = None - - @log_helpers.log_method_call - def create_router(self, context, router): - """Create a new router entry in DB, and create it Arista HW.""" - - tenant_id = router['router']['tenant_id'] - - # Add router to the DB - new_router = super(AristaL3ServicePlugin, self).create_router( - context, - router) - # create router on the Arista Hw - try: - self.driver.create_router(context, tenant_id, new_router) - return new_router - except Exception: - with excutils.save_and_reraise_exception(): - LOG.error(_LE("Error creating router on Arista HW router=%s "), - new_router) - super(AristaL3ServicePlugin, self).delete_router( - context, new_router['id']) - - @log_helpers.log_method_call - def update_router(self, context, router_id, router): - """Update an existing router in DB, and update it in Arista HW.""" - - # Read existing router record from DB - original_router = super(AristaL3ServicePlugin, self).get_router( - context, router_id) - # Update router DB - new_router = super(AristaL3ServicePlugin, self).update_router( - context, router_id, router) - - # Modify router on the Arista Hw - try: - self.driver.update_router(context, router_id, - original_router, new_router) - return new_router - except Exception: - LOG.error(_LE("Error updating router on Arista HW router=%s "), - new_router) - - @log_helpers.log_method_call - def delete_router(self, context, router_id): - """Delete an existing router from Arista HW as well as from the DB.""" - - router = super(AristaL3ServicePlugin, self).get_router(context, - router_id) - tenant_id = router['tenant_id'] - - # Delete router on the Arista Hw - try: - self.driver.delete_router(context, tenant_id, router_id, router) - except Exception as e: - LOG.error(_LE("Error deleting router on Arista HW " - "router %(r)s exception=%(e)s"), - {'r': router, 'e': e}) - - super(AristaL3ServicePlugin, self).delete_router(context, router_id) - - @log_helpers.log_method_call - def add_router_interface(self, context, router_id, interface_info): - """Add a subnet of a network to an existing router.""" - - new_router = super(AristaL3ServicePlugin, self).add_router_interface( - context, router_id, interface_info) - - # Get network info for the subnet that is being added to the router. - # Check if the interface information is by port-id or subnet-id - add_by_port, add_by_sub = self._validate_interface_info(interface_info) - if add_by_sub: - subnet = self.get_subnet(context, interface_info['subnet_id']) - elif add_by_port: - port = self.get_port(context, interface_info['port_id']) - subnet_id = port['fixed_ips'][0]['subnet_id'] - subnet = self.get_subnet(context, subnet_id) - network_id = subnet['network_id'] - - # To create SVI's in Arista HW, the segmentation Id is required - # for this network. - ml2_db = NetworkContext(self, context, {'id': network_id}) - seg_id = ml2_db.network_segments[0]['segmentation_id'] - - # Package all the info needed for Hw programming - router = super(AristaL3ServicePlugin, self).get_router(context, - router_id) - router_info = copy.deepcopy(new_router) - router_info['seg_id'] = seg_id - router_info['name'] = router['name'] - router_info['cidr'] = subnet['cidr'] - router_info['gip'] = subnet['gateway_ip'] - router_info['ip_version'] = subnet['ip_version'] - - try: - self.driver.add_router_interface(context, router_info) - return new_router - except Exception: - with excutils.save_and_reraise_exception(): - LOG.error(_LE("Error Adding subnet %(subnet)s to " - "router %(router_id)s on Arista HW"), - {'subnet': subnet, 'router_id': router_id}) - super(AristaL3ServicePlugin, self).remove_router_interface( - context, - router_id, - interface_info) - - @log_helpers.log_method_call - def remove_router_interface(self, context, router_id, interface_info): - """Remove a subnet of a network from an existing router.""" - - new_router = ( - super(AristaL3ServicePlugin, self).remove_router_interface( - context, router_id, interface_info)) - - # Get network information of the subnet that is being removed - subnet = self.get_subnet(context, new_router['subnet_id']) - network_id = subnet['network_id'] - - # For SVI removal from Arista HW, segmentation ID is needed - ml2_db = NetworkContext(self, context, {'id': network_id}) - seg_id = ml2_db.network_segments[0]['segmentation_id'] - - router = super(AristaL3ServicePlugin, self).get_router(context, - router_id) - router_info = copy.deepcopy(new_router) - router_info['seg_id'] = seg_id - router_info['name'] = router['name'] - - try: - self.driver.remove_router_interface(context, router_info) - return new_router - except Exception as exc: - LOG.error(_LE("Error removing interface %(interface)s from " - "router %(router_id)s on Arista HW" - "Exception =(exc)s"), - {'interface': interface_info, 'router_id': router_id, - 'exc': exc}) - - def synchronize(self): - """Synchronizes Router DB from Neturon DB with EOS. - - Walks through the Neturon Db and ensures that all the routers - created in Netuton DB match with EOS. After creating appropriate - routers, it ensures to add interfaces as well. - Uses idempotent properties of EOS configuration, which means - same commands can be repeated. - """ - LOG.info(_LI('Syncing Neutron Router DB <-> EOS')) - ctx = nctx.get_admin_context() - - routers = super(AristaL3ServicePlugin, self).get_routers(ctx) - for r in routers: - tenant_id = r['tenant_id'] - ports = self.ndb.get_all_ports_for_tenant(tenant_id) - - try: - self.driver.create_router(self, tenant_id, r) - - except Exception: - continue - - # Figure out which interfaces are added to this router - for p in ports: - if p['device_id'] == r['id']: - net_id = p['network_id'] - subnet_id = p['fixed_ips'][0]['subnet_id'] - subnet = self.ndb.get_subnet_info(subnet_id) - ml2_db = NetworkContext(self, ctx, {'id': net_id}) - seg_id = ml2_db.network_segments[0]['segmentation_id'] - - r['seg_id'] = seg_id - r['cidr'] = subnet['cidr'] - r['gip'] = subnet['gateway_ip'] - r['ip_version'] = subnet['ip_version'] - - try: - self.driver.add_router_interface(self, r) - except Exception: - LOG.error(_LE("Error Adding interface %(subnet_id)s " - "to router %(router_id)s on Arista HW"), - {'subnet_id': subnet_id, 'router_id': r}) diff --git a/networking_arista/ml2/__init__.py b/networking_arista/ml2/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/networking_arista/ml2/arista_ml2.py b/networking_arista/ml2/arista_ml2.py deleted file mode 100644 index 13cb745..0000000 --- a/networking_arista/ml2/arista_ml2.py +++ /dev/null @@ -1,2297 +0,0 @@ -# Copyright (c) 2014 OpenStack Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import abc -import base64 -import json -import os -import socket - -from neutron_lib.api.definitions import portbindings -from neutron_lib import constants as n_const -from neutron_lib.db import api as db_api -from oslo_config import cfg -from oslo_log import log as logging -from oslo_utils import excutils -import requests -from six import add_metaclass - -from neutron.db.models.plugins.ml2 import vlanallocation - -from networking_arista._i18n import _, _LI, _LW, _LE -from networking_arista.common import db_lib -from networking_arista.common import exceptions as arista_exc -from networking_arista.ml2 import arista_sec_gp - -LOG = logging.getLogger(__name__) - -EOS_UNREACHABLE_MSG = _('Unable to reach EOS') -DEFAULT_VLAN = 1 - -# Insert a heartbeat command every 100 commands -HEARTBEAT_INTERVAL = 100 - -# Commands dict keys -CMD_SYNC_HEARTBEAT = 'SYNC_HEARTBEAT' -CMD_REGION_SYNC = 'REGION_SYNC' -CMD_INSTANCE = 'INSTANCE' - -# EAPI error messages of interest -ERR_CVX_NOT_LEADER = 'only available on cluster leader' -ERR_DVR_NOT_SUPPORTED = 'EOS version on CVX does not support DVR' -BAREMETAL_NOT_SUPPORTED = 'EOS version on CVX does not support Baremetal' - -# Flat network constant -NETWORK_TYPE_FLAT = 'flat' - - -class InstanceType(object): - BAREMETAL = 'baremetal' - DHCP = 'dhcp' - ROUTER = 'router' - VM = 'vm' - - VIRTUAL_INSTANCE_TYPES = [DHCP, ROUTER, VM] - BAREMETAL_INSTANCE_TYPES = [BAREMETAL] - - -@add_metaclass(abc.ABCMeta) -class AristaRPCWrapperBase(object): - """Wraps Arista JSON RPC. - - All communications between Neutron and EOS are over JSON RPC. - EOS - operating system used on Arista hardware - Command API - JSON RPC API provided by Arista EOS - """ - def __init__(self, neutron_db): - self._ndb = neutron_db - self._validate_config() - self._server_ip = None - self.keystone_conf = cfg.CONF.keystone_authtoken - self.region = cfg.CONF.ml2_arista.region_name - self.sync_interval = cfg.CONF.ml2_arista.sync_interval - self.conn_timeout = cfg.CONF.ml2_arista.conn_timeout - self.eapi_hosts = cfg.CONF.ml2_arista.eapi_host.split(',') - self.security_group_driver = arista_sec_gp.AristaSecGroupSwitchDriver( - self._ndb) - - # We denote mlag_pair physnets as peer1_peer2 in the physnet name, the - # following builds a mapping of peer name to physnet name for use - # during port binding - self.mlag_pairs = {} - session = db_api.get_reader_session() - with session.begin(): - physnets = session.query( - vlanallocation.VlanAllocation.physical_network - ).distinct().all() - for (physnet,) in physnets: - if '_' in physnet: - peers = physnet.split('_') - self.mlag_pairs[peers[0]] = physnet - self.mlag_pairs[peers[1]] = physnet - - # Indication of CVX availabililty in the driver. - self._cvx_available = True - - # Reference to SyncService object which is set in AristaDriver - self.sync_service = None - - def _validate_config(self): - if cfg.CONF.ml2_arista.get('eapi_host') == '': - msg = _('Required option eapi_host is not set') - LOG.error(msg) - raise arista_exc.AristaConfigError(msg=msg) - if cfg.CONF.ml2_arista.get('eapi_username') == '': - msg = _('Required option eapi_username is not set') - LOG.error(msg) - raise arista_exc.AristaConfigError(msg=msg) - - def _keystone_url(self): - if self.keystone_conf.auth_uri: - auth_uri = self.keystone_conf.auth_uri.rstrip('/') - else: - auth_uri = ( - '%(protocol)s://%(host)s:%(port)s' % - {'protocol': self.keystone_conf.auth_protocol, - 'host': self.keystone_conf.auth_host, - 'port': self.keystone_conf.auth_port}) - return '%s/v3/' % auth_uri - - def _api_username(self): - return cfg.CONF.ml2_arista.eapi_username - - def _api_password(self): - return cfg.CONF.ml2_arista.eapi_password - - def _get_random_name(self, length=10): - """Returns a base64 encoded name.""" - return base64.b64encode(os.urandom(10)).translate(None, '=+/') - - def _get_cvx_hosts(self): - cvx = [] - if self._server_ip: - # If we know the master's IP, let's start with that - cvx.append(self._server_ip) - - for h in self.eapi_hosts: - if h.strip() not in cvx: - cvx.append(h.strip()) - - return cvx - - def set_cvx_unavailable(self): - self._cvx_available = False - if self.sync_service: - self.sync_service.force_sync() - - def set_cvx_available(self): - self._cvx_available = True - - def cvx_available(self): - return self._cvx_available - - def check_cvx_availability(self): - try: - if self._get_eos_master(): - self.set_cvx_available() - return True - except Exception as exc: - LOG.warning(_LW('%s when getting CVX master'), exc) - - self.set_cvx_unavailable() - return False - - def delete_tenant(self, tenant_id): - """Deletes a given tenant and all its networks and VMs from EOS. - - :param tenant_id: globally unique neutron tenant identifier - """ - self.delete_tenant_bulk([tenant_id]) - - def clear_region_updated_time(self): - # TODO(shashank): Remove this once the call is removed from the ML2 - # driver. - pass - - def create_network(self, tenant_id, network): - """Creates a single network on Arista hardware - - :param tenant_id: globally unique neutron tenant identifier - :param network: dict containing network_id, network_name and - segmentation_id - """ - self.create_network_bulk(tenant_id, [network]) - - def delete_network(self, tenant_id, network_id, network_segments): - """Deletes a specified network for a given tenant - - :param tenant_id: globally unique neutron tenant identifier - :param network_id: globally unique neutron network identifier - :param network_segments: segments associated with the network - """ - segments_info = [] - segments_info.extend({'id': segment['id'], 'network_id': network_id} - for segment in network_segments) - self.delete_network_segments(tenant_id, segments_info) - self.delete_network_bulk(tenant_id, [network_id]) - - def delete_vm(self, tenant_id, vm_id): - """Deletes a VM from EOS for a given tenant - - :param tenant_id : globally unique neutron tenant identifier - :param vm_id : id of a VM that needs to be deleted. - """ - self.delete_vm_bulk(tenant_id, [vm_id]) - - @abc.abstractmethod - def plug_port_into_network(self, device_id, host_id, port_id, - net_id, tenant_id, port_name, device_owner, - sg, orig_sg, vnic_type, segments=None, - switch_bindings=None): - """Generic routine plug a port of a VM instace into network. - - :param device_id: globally unique identifier for the device - :param host: ID of the host where the port is placed - :param port_id: globally unique port ID that connects port to network - :param network_id: globally unique neutron network identifier - :param tenant_id: globally unique neutron tenant identifier - :param port_name: Name of the port - for display purposes - :param device_owner: Device owner - e.g. compute or network:dhcp - :param sg: current security group for the port - :param orig_sg: original security group for the port - :param vnic_type: VNIC type for the port - :param segments: list of network segments the port is bound to - :param switch_bindings: List of switch_bindings - """ - - @abc.abstractmethod - def unplug_port_from_network(self, device_id, device_owner, hostname, - port_id, network_id, tenant_id, sg, vnic_type, - switch_bindings=None): - """Removes a port from the device - - :param device_id: globally unique identifier for the device - :param host: ID of the host where the device is placed - :param port_id: globally unique port ID that connects device to network - :param network_id: globally unique neutron network identifier - :param tenant_id: globally unique neutron tenant identifier - """ - - def _clean_acls(self, sg, failed_switch, switches_to_clean): - """This is a helper function to clean up ACLs on switches. - - This called from within an exception - when apply_acl fails. - Therefore, ensure that exception is raised after the cleanup - is done. - :param sg: Security Group to be removed - :param failed_switch: IP of the switch where ACL failed - :param switches_to_clean: List of switches containing link info - """ - if not switches_to_clean: - # This means the no switch needs cleaning - so, simply raise the - # the exception and bail out - msg = (_("Failed to apply ACL %(sg)s on switch %(switch)s") % - {'sg': sg, 'switch': failed_switch}) - LOG.error(msg) - - for s in switches_to_clean: - try: - # Port is being updated to remove security groups - self.security_group_driver.remove_acl(sg, - s['switch_id'], - s['port_id'], - s['switch_info']) - except Exception: - msg = (_("Failed to remove ACL %(sg)s on switch %(switch)%") % - {'sg': sg, 'switch': s['switch_info']}) - LOG.warning(msg) - raise arista_exc.AristaSecurityGroupError(msg=msg) - - def create_acl(self, sg): - """Creates an ACL on Arista Switch. - - Deals with multiple configurations - such as multiple switches - """ - self.security_group_driver.create_acl(sg) - - def delete_acl(self, sg): - """Deletes an ACL from Arista Switch. - - Deals with multiple configurations - such as multiple switches - """ - self.security_group_driver.delete_acl(sg) - - def create_acl_rule(self, sgr): - """Creates an ACL on Arista Switch. - - For a given Security Group (ACL), it adds additional rule - Deals with multiple configurations - such as multiple switches - """ - self.security_group_driver.create_acl_rule(sgr) - - def delete_acl_rule(self, sgr): - """Deletes an ACL rule on Arista Switch. - - For a given Security Group (ACL), it removes a rule - Deals with multiple configurations - such as multiple switches - """ - self.security_group_driver.delete_acl_rule(sgr) - - def perform_sync_of_sg(self): - """Perform sync of the security groups between ML2 and EOS. - - This is unconditional sync to ensure that all security - ACLs are pushed to all the switches, in case of switch - or neutron reboot - """ - self.security_group_driver.perform_sync_of_sg() - - @abc.abstractmethod - def sync_supported(self): - """Whether the EOS version supports sync. - - Returns True if sync is supported, false otherwise. - """ - - @abc.abstractmethod - def bm_and_dvr_supported(self): - """Whether EOS supports Ironic and DVR. - - Returns True if supported, false otherwise. - """ - - @abc.abstractmethod - def register_with_eos(self, sync=False): - """This is the registration request with EOS. - - This the initial handshake between Neutron and EOS. - critical end-point information is registered with EOS. - - :param sync: This flags indicates that the region is being synced. - """ - - @abc.abstractmethod - def check_supported_features(self): - """Checks whether the CLI commands are valid. - - This method tries to execute the commands on EOS and if it succeedes - the command is stored. - """ - - @abc.abstractmethod - def get_region_updated_time(self): - """Return the timestamp of the last update. - - This method returns the time at which any entities in the region - were updated. - """ - - @abc.abstractmethod - def delete_this_region(self): - """Deleted the region data from EOS.""" - - @abc.abstractmethod - def sync_start(self): - """Let EOS know that a sync in being initiated.""" - - @abc.abstractmethod - def sync_end(self): - """Let EOS know that sync is complete.""" - - @abc.abstractmethod - def get_tenants(self): - """Returns dict of all tenants known by EOS. - - :returns: dictionary containing the networks per tenant - and VMs allocated per tenant - """ - - @abc.abstractmethod - def delete_tenant_bulk(self, tenant_list, sync=False): - """Sends a bulk request to delete the tenants. - - :param tenant_list: list of globaly unique neutron tenant ids which - need to be deleted. - :param sync: This flags indicates that the region is being synced. - """ - - @abc.abstractmethod - def create_network_bulk(self, tenant_id, network_list, sync=False): - """Creates a network on Arista Hardware - - :param tenant_id: globally unique neutron tenant identifier - :param network_list: list of dicts containing network_id, network_name - and segmentation_id - :param sync: This flags indicates that the region is being synced. - """ - - @abc.abstractmethod - def create_network_segments(self, tenant_id, network_id, - network_name, segments): - """Creates a network on Arista Hardware - - Note: This method is not used at the moment. create_network() - is used instead. This will be used once the support for - multiple segments is added in Neutron. - - :param tenant_id: globally unique neutron tenant identifier - :param network_id: globally unique neutron network identifier - :param network_name: Network name - for display purposes - :param segments: List of segments in a given network - """ - - @abc.abstractmethod - def delete_network_bulk(self, tenant_id, network_id_list, sync=False): - """Deletes the network ids specified for a tenant - - :param tenant_id: globally unique neutron tenant identifier - :param network_id_list: list of globally unique neutron network - identifiers - :param sync: This flags indicates that the region is being synced. - """ - - @abc.abstractmethod - def delete_network_segments(self, tenant_id, network_segments): - """Deletes the network segments - - :param network_segments: List of network segments to be delted. - """ - - @abc.abstractmethod - def create_instance_bulk(self, tenant_id, neutron_ports, vms, - port_profiles, sync=False): - """Sends a bulk request to create ports. - - :param tenant_id: globaly unique neutron tenant identifier - :param neutron_ports: list of ports that need to be created. - :param vms: list of vms to which the ports will be attached to. - :param sync: This flags indicates that the region is being synced. - """ - - @abc.abstractmethod - def delete_instance_bulk(self, tenant_id, instance_id_list, instance_type, - sync=False): - """Deletes instances from EOS for a given tenant - - :param tenant_id : globally unique neutron tenant identifier - :param instance_id_list : ids of instances that needs to be deleted. - :param instance_type: The type of the instance which is being deleted. - :param sync: This flags indicates that the region is being synced. - """ - - @abc.abstractmethod - def delete_vm_bulk(self, tenant_id, vm_id_list, sync=False): - """Deletes VMs from EOS for a given tenant - - :param tenant_id : globally unique neutron tenant identifier - :param vm_id_list : ids of VMs that needs to be deleted. - :param sync: This flags indicates that the region is being synced. - """ - - @abc.abstractmethod - def hpb_supported(self): - """Whether hierarchical port binding (HPB) is supported by CVX. - - Returns True if HPB is supported, False otherwise. - """ - - def apply_security_group(self, security_group, switch_bindings): - """Applies ACLs on switch interface. - - Translates neutron security group to switch ACL and applies the ACLs - on all the switch interfaces defined in the switch_bindings. - - :param security_group: Neutron security group - :param switch_bindings: Switch link information - """ - switches_with_acl = [] - for binding in switch_bindings: - try: - self.security_group_driver.apply_acl(security_group, - binding['switch_id'], - binding['port_id'], - binding['switch_info']) - switches_with_acl.append(binding) - except Exception: - message = _LW('Unable to apply security group on %s') % ( - binding['switch_id']) - LOG.warning(message) - self._clean_acls(security_group, binding['switch_id'], - switches_with_acl) - - def remove_security_group(self, security_group, switch_bindings): - """Removes ACLs from switch interface - - Translates neutron security group to switch ACL and removes the ACLs - from all the switch interfaces defined in the switch_bindings. - - :param security_group: Neutron security group - :param switch_bindings: Switch link information - """ - for binding in switch_bindings: - try: - self.security_group_driver.remove_acl(security_group, - binding['switch_id'], - binding['port_id'], - binding['switch_info']) - except Exception: - message = _LW('Unable to remove security group from %s') % ( - binding['switch_id']) - LOG.warning(message) - - -class AristaRPCWrapperJSON(AristaRPCWrapperBase): - def __init__(self, ndb): - super(AristaRPCWrapperJSON, self).__init__(ndb) - self.current_sync_name = None - - def _get_url(self, host="", user="", password=""): - return ('https://%s:%s@%s/openstack/api/' % - (user, password, host)) - - def _api_host_url(self, host=""): - return self._get_url(host, self._api_username(), self._api_password()) - - def _send_request(self, host, path, method, data=None, - sanitized_data=None): - request_headers = { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - 'X-Sync-ID': self.current_sync_name - } - url = self._api_host_url(host=host) + path - # Don't log the password - log_url = self._get_url(host=host, user=self._api_username(), - password="*****") + path - - resp = None - data = json.dumps(data) - try: - msg = (_('JSON request type: %(type)s url %(url)s data: ' - '%(data)s sync_id: %(sync)s') % - {'type': method, 'url': log_url, - 'data': sanitized_data or data, - 'sync': self.current_sync_name}) - LOG.info(msg) - func_lookup = { - 'GET': requests.get, - 'POST': requests.post, - 'PUT': requests.put, - 'PATCH': requests.patch, - 'DELETE': requests.delete - } - func = func_lookup.get(method) - if not func: - LOG.warning(_LW('Unrecognized HTTP method %s'), method) - return None - - resp = func(url, timeout=self.conn_timeout, verify=False, - data=data, headers=request_headers) - LOG.info(_LI('JSON response contains: %s'), resp.json()) - return resp.json() - except requests.exceptions.ConnectionError: - msg = (_('Error connecting to %(url)s') % {'url': url}) - LOG.warning(msg) - except requests.exceptions.ConnectTimeout: - msg = (_('Timed out connecting to API request to %(url)s') % - {'url': url}) - LOG.warning(msg) - except requests.exceptions.Timeout: - msg = (_('Timed out during API request to %(url)s') % - {'url': url}) - LOG.warning(msg) - except requests.exceptions.InvalidURL: - msg = (_('Ignore attempt to connect to invalid URL %(url)s') % - {'url': self._server_ip}) - LOG.warning(msg) - except ValueError: - LOG.warning(_LW("Ignoring invalid JSON response: %s"), resp.text) - except Exception as error: - msg = unicode(error) - LOG.warning(msg) - # reraise the exception - with excutils.save_and_reraise_exception() as ctxt: - ctxt.reraise = True - - def _check_if_cvx_leader(self, host): - url = 'agent/' - data = self._send_request(host, url, 'GET') - return False if not data else data.get('isLeader', False) - - def _get_eos_master(self): - cvx = self._get_cvx_hosts() - for self._server_ip in cvx: - if self._check_if_cvx_leader(self._server_ip): - return self._server_ip - return None - - def _send_api_request(self, path, method, data=None, sanitized_data=None): - host = self._get_eos_master() - if not host: - msg = unicode("Could not find CVX leader") - LOG.info(msg) - self.set_cvx_unavailable() - raise arista_exc.AristaRpcError(msg=msg) - self.set_cvx_available() - return self._send_request(host, path, method, data, sanitized_data) - - def _create_keystone_endpoint(self): - path = 'region/%s/service-end-point' % self.region - data = { - 'name': 'keystone', - 'authUrl': self._keystone_url(), - 'user': self.keystone_conf.admin_user or "", - 'password': self.keystone_conf.admin_password or "", - 'tenant': self.keystone_conf.admin_tenant_name or "" - } - # Hide the password - sanitized_data = data.copy() - sanitized_data['password'] = "*****" - self._send_api_request(path, 'POST', [data], [sanitized_data]) - - def _set_region_update_interval(self): - path = 'region/%s' % self.region - data = { - 'name': self.region, - 'syncInterval': self.sync_interval - } - self._send_api_request(path, 'PUT', [data]) - - def register_with_eos(self, sync=False): - self.create_region(self.region) - self._create_keystone_endpoint() - self._set_region_update_interval() - - def check_supported_features(self): - # We don't use this function as we know the features - # that are available once using this API. - pass - - def bm_and_dvr_supported(self): - return True - - def get_region_updated_time(self): - path = 'agent/' - data = self._send_api_request(path, 'GET') - return {'regionTimestamp': data['uuid']} - - def create_region(self, region): - path = 'region/' - data = {'name': region} - return self._send_api_request(path, 'POST', [data]) - - def delete_region(self, region): - path = 'region/' - data = {'name': region} - return self._send_api_request(path, 'DELETE', [data]) - - def delete_this_region(self): - return self.delete_region(self.region) - - def get_region(self, name): - path = 'region/' - regions = self._send_api_request(path, 'GET') - for region in regions: - if region['name'] == name: - return region - return None - - def sync_supported(self): - return True - - def hpb_supported(self): - return True - - def sync_start(self): - try: - region = self.get_region(self.region) - if region and region['syncStatus'] == 'syncInProgress': - LOG.info('Sync in progress, not syncing') - return False - - req_id = self._get_random_name() - data = { - 'requester': socket.gethostname().split('.')[0], - 'requestId': req_id - } - path = 'region/' + self.region + '/sync' - self._send_api_request(path, 'POST', data) - self.current_sync_name = req_id - return True - except (KeyError, arista_exc.AristaRpcError): - LOG.info('Not syncing due to RPC error') - return False - LOG.info('Not syncing due to server syncStatus') - return False - - def sync_end(self): - LOG.info('Attempting to end sync') - try: - path = 'region/' + self.region + '/sync' - self._send_api_request(path, 'DELETE') - self.current_sync_name = None - return True - except arista_exc.AristaRpcError: - LOG.info('Not ending sync due to RPC error') - return False - - def get_vms_for_tenant(self, tenant): - path = 'region/' + self.region + '/vm?tenantId=' + tenant - return self._send_api_request(path, 'GET') - - def get_dhcps_for_tenant(self, tenant): - path = 'region/' + self.region + '/dhcp?tenantId=' + tenant - return self._send_api_request(path, 'GET') - - def get_baremetals_for_tenant(self, tenant): - path = 'region/' + self.region + '/baremetal?tenantId=' + tenant - return self._send_api_request(path, 'GET') - - def get_routers_for_tenant(self, tenant): - path = 'region/' + self.region + '/router?tenantId=' + tenant - return self._send_api_request(path, 'GET') - - def get_ports_for_tenant(self, tenant, pType): - path = 'region/%s/port?tenantId=%s&type=%s' % (self.region, - tenant, pType) - return self._send_api_request(path, 'GET') - - def get_tenants(self): - path = 'region/' + self.region + '/tenant' - tenants = self._send_api_request(path, 'GET') - d = {} - for ten in tenants: - ten['tenantId'] = ten.pop('id') - - nets = self.get_networks(ten['tenantId']) - netDict = {} - try: - for net in nets: - net['networkId'] = net.pop('id') - net['networkName'] = net.pop('name') - netDict[net['networkId']] = net - except Exception as exc: - LOG.error(_LE('Failed to get tenant network %(net)s. ' - 'Reason: %(exc)s'), {'net': net, 'exc': exc}) - - ten['tenantNetworks'] = netDict - - vms = self.get_vms_for_tenant(ten['tenantId']) - vmDict = dict((v['id'], v) for v in vms) - ten['tenantVmInstances'] = vmDict - - routers = self.get_routers_for_tenant(ten['tenantId']) - routerDict = dict((r['id'], r) for r in routers) - ten['tenantRouterInstances'] = routerDict - - bms = self.get_baremetals_for_tenant(ten['tenantId']) - bmDict = dict((b['id'], b) for b in bms) - ten['tenantBaremetalInstances'] = bmDict - - d[ten['tenantId']] = ten - return d - - def delete_tenant_bulk(self, tenant_list, sync=False): - path = 'region/' + self.region + '/tenant' - data = [{'id': t} for t in tenant_list] - return self._send_api_request(path, 'DELETE', data) - - def get_networks(self, tenant): - path = 'region/' + self.region + '/network?tenantId=' + tenant - return self._send_api_request(path, 'GET') - - def create_network_bulk(self, tenant_id, network_list, sync=False): - self._create_tenant_if_needed(tenant_id) - networks = [] - segments = [] - for net in network_list: - n = { - 'id': net['network_id'], - 'tenantId': tenant_id, - 'shared': net['shared'], - } - - if net.get('network_name'): - n['name'] = net['network_name'] - if net.get('segmentation_id'): - n['segId'] = net['segmentation_id'] - - for segment in net['segments']: - if segment['network_type'] == NETWORK_TYPE_FLAT: - continue - segmentType = 'static' - if segment.get('is_dynamic', False): - segmentType = 'dynamic' - - segments.append({ - 'id': segment['id'], - 'networkId': net['network_id'], - 'type': segment['network_type'], - 'segmentationId': segment['segmentation_id'], - 'segmentType': segmentType, - }) - - networks.append(n) - - if networks: - path = 'region/' + self.region + '/network' - self._send_api_request(path, 'POST', networks) - - if segments: - path = 'region/' + self.region + '/segment' - self._send_api_request(path, 'POST', segments) - - def create_network_segments(self, tenant_id, network_id, - network_name, segments): - segment_data = [] - for segment in segments: - segmentType = 'static' - if segment.get('is_dynamic', False): - segmentType = 'dynamic' - - segment_data.append({ - 'id': segment['id'], - 'networkId': network_id, - 'type': segment['network_type'], - 'segmentationId': segment['segmentation_id'], - 'segmentType': segmentType, - }) - - path = 'region/' + self.region + '/segment' - self._send_api_request(path, 'POST', segment_data) - - def delete_network_segments(self, tenant_id, segments): - segment_data = [] - for segment in segments: - segment_data.append({ - 'id': segment['id'], - }) - path = 'region/' + self.region + '/segment' - self._send_api_request(path, 'DELETE', segment_data) - - def delete_network_bulk(self, tenant_id, network_id_list, sync=False): - path = 'region/' + self.region + '/network' - data = [{'id': n, 'tenantId': tenant_id} for n in network_id_list] - return self._send_api_request(path, 'DELETE', data) - - def _create_instance_data(self, vm_id, host_id): - return { - 'id': vm_id, - 'hostId': host_id - } - - def _create_port_data(self, port_id, tenant_id, network_id, instance_id, - name, instance_type, hosts): - - vlan_type = 'allowed' - if instance_type in InstanceType.BAREMETAL_INSTANCE_TYPES: - vlan_type = 'native' - - return { - 'id': port_id, - 'tenantId': tenant_id, - 'networkId': network_id, - 'instanceId': instance_id, - 'name': name, - 'instanceType': instance_type, - 'vlanType': vlan_type, - 'hosts': hosts or [] - } - - def _create_tenant_if_needed(self, tenant_id): - tenResponse = self.get_tenant(tenant_id) - if tenResponse is None: - self.create_tenant_bulk([tenant_id]) - - def get_tenant(self, tenant_id): - path = 'region/' + self.region + '/tenant?tenantId=' + tenant_id - tenants = self._send_api_request(path, 'GET') - if tenants: - try: - return tenants[0] - except KeyError: - return None - return None - - def create_tenant_bulk(self, tenant_ids): - path = 'region/' + self.region + '/tenant' - data = [{'id': tid} for tid in tenant_ids] - return self._send_api_request(path, 'POST', data) - - def create_instance_bulk(self, tenant_id, neutron_ports, vms, - port_profiles, sync=False): - self._create_tenant_if_needed(tenant_id) - - vmInst = {} - dhcpInst = {} - baremetalInst = {} - routerInst = {} - portInst = [] - networkSegments = {} - portBindings = {} - - for vm in vms.values(): - for v_port in vm['ports']: - port_id = v_port['portId'] - if not v_port['hosts']: - # Skip all the ports that have no host associsted with them - continue - - if port_id not in neutron_ports.keys(): - continue - neutron_port = neutron_ports[port_id] - - inst_id = vm['vmId'] - inst_host = vm['ports'][0]['hosts'][0] - instance = self._create_instance_data(inst_id, inst_host) - - device_owner = neutron_port['device_owner'] - vnic_type = port_profiles[port_id]['vnic_type'] - if device_owner == n_const.DEVICE_OWNER_DHCP: - instance_type = InstanceType.DHCP - if inst_id not in dhcpInst: - dhcpInst[inst_id] = instance - elif (device_owner.startswith('compute') or - device_owner.startswith('baremetal')): - if vnic_type == 'baremetal': - instance_type = InstanceType.BAREMETAL - if inst_id not in baremetalInst: - baremetalInst[inst_id] = instance - else: - instance_type = InstanceType.VM - if inst_id not in vmInst: - vmInst[inst_id] = instance - elif device_owner == n_const.DEVICE_OWNER_DVR_INTERFACE: - instance_type = InstanceType.ROUTER - if inst_id not in routerInst: - routerInst[inst_id] = instance - else: - LOG.warning(_LW("Unknown device owner: %s"), - neutron_port['device_owner']) - continue - - network_id = neutron_port['network_id'] - if network_id not in networkSegments: - networkSegments[ - network_id] = self._ndb.get_all_network_segments( - network_id) - - port = self._create_port_data(port_id, tenant_id, - network_id, inst_id, - neutron_port.get('name'), - instance_type, v_port['hosts']) - portInst.append(port) - - if instance_type in InstanceType.VIRTUAL_INSTANCE_TYPES: - portBinding = self._get_host_bindings( - port_id, inst_host, network_id, - networkSegments[network_id]) - elif instance_type in InstanceType.BAREMETAL_INSTANCE_TYPES: - switch_profile = json.loads(port_profiles[ - port_id]['profile']) - portBinding = self._get_switch_bindings( - port_id, inst_host, network_id, - switch_profile['local_link_information'], - networkSegments[network_id]) - if port_id not in portBindings: - portBindings[port_id] = portBinding - else: - portBindings[port_id] += portBinding - - # create instances first - if vmInst: - path = 'region/' + self.region + '/vm?tenantId=' + tenant_id - self._send_api_request(path, 'POST', list(vmInst.values())) - if dhcpInst: - path = 'region/' + self.region + '/dhcp?tenantId=' + tenant_id - self._send_api_request(path, 'POST', list(dhcpInst.values())) - if baremetalInst: - path = 'region/' + self.region + '/baremetal?tenantId=' + tenant_id - self._send_api_request(path, 'POST', list(baremetalInst.values())) - if routerInst: - path = 'region/' + self.region + '/router?tenantId=' + tenant_id - self._send_api_request(path, 'POST', list(routerInst.values())) - - # now create ports for the instances - path = 'region/' + self.region + '/port' - self._send_api_request(path, 'POST', portInst) - - # TODO(shashank): Optimize this - for port_id, bindings in portBindings.items(): - url = 'region/' + self.region + '/port/' + port_id + '/binding' - self._send_api_request(url, 'POST', bindings) - - def delete_instance_bulk(self, tenant_id, instance_id_list, instance_type, - sync=False): - path = 'region/%(region)s/%(type)s' % { - 'region': self.region, - 'type': instance_type} - - data = [{'id': i} for i in instance_id_list] - return self._send_api_request(path, 'DELETE', data) - - def delete_vm_bulk(self, tenant_id, vm_id_list, sync=False): - self.delete_instance_bulk(tenant_id, vm_id_list, InstanceType.VM) - - def delete_dhcp_bulk(self, tenant_id, dhcp_id_list): - self.delete_instance_bulk(tenant_id, dhcp_id_list, InstanceType.DHCP) - - def delete_port(self, port_id, instance_id, instance_type): - path = ('region/%s/port?portId=%s&id=%s&type=%s' % - (self.region, port_id, instance_id, instance_type)) - port = self._create_port_data(port_id, None, None, instance_id, - None, instance_type, None) - return self._send_api_request(path, 'DELETE', [port]) - - def get_instance_ports(self, instance_id, instance_type): - path = ('region/%s/port?id=%s&type=%s' % - (self.region, instance_id, instance_type)) - return self._send_api_request(path, 'GET') - - def plug_port_into_network(self, device_id, host_id, port_id, - net_id, tenant_id, port_name, device_owner, - sg, orig_sg, vnic_type, segments, - switch_bindings=None): - device_type = '' - if device_owner == n_const.DEVICE_OWNER_DHCP: - device_type = InstanceType.DHCP - elif (device_owner.startswith('compute') - or device_owner.startswith('baremetal')): - if vnic_type == 'baremetal': - device_type = InstanceType.BAREMETAL - else: - device_type = InstanceType.VM - elif device_owner == n_const.DEVICE_OWNER_DVR_INTERFACE: - device_type = InstanceType.ROUTER - else: - LOG.info(_LI('Unsupported device owner: %s'), device_owner) - return - - self._create_tenant_if_needed(tenant_id) - instance = self._create_instance_data(device_id, host_id) - port = self._create_port_data(port_id, tenant_id, net_id, device_id, - port_name, device_type, [host_id]) - url = 'region/%(region)s/%(device_type)s?tenantId=%(tenant_id)s' % { - 'region': self.region, - 'device_type': device_type, - 'tenant_id': tenant_id, - } - self._send_api_request(url, 'POST', [instance]) - self._send_api_request('region/' + self.region + '/port', 'POST', - [port]) - if device_type in InstanceType.VIRTUAL_INSTANCE_TYPES: - self.bind_port_to_host(port_id, host_id, net_id, segments) - elif device_type in InstanceType.BAREMETAL_INSTANCE_TYPES: - self.bind_port_to_switch_interface(port_id, host_id, net_id, - switch_bindings, segments) - if sg: - self.apply_security_group(sg, switch_bindings) - else: - # Security group was removed. Clean up the existing security - # groups. - if orig_sg: - self.remove_security_group(orig_sg, switch_bindings) - - def unplug_port_from_network(self, device_id, device_owner, hostname, - port_id, network_id, tenant_id, sg, vnic_type, - switch_bindings=None): - device_type = '' - if device_owner == n_const.DEVICE_OWNER_DHCP: - device_type = InstanceType.DHCP - elif (device_owner.startswith('compute') or - device_owner.startswith('baremetal')): - if vnic_type == 'baremetal': - device_type = InstanceType.BAREMETAL - else: - device_type = InstanceType.VM - elif device_owner == n_const.DEVICE_OWNER_DVR_INTERFACE: - device_type = InstanceType.ROUTER - else: - LOG.info(_LI('Unsupported device owner: %s'), device_owner) - return - - if device_type in InstanceType.VIRTUAL_INSTANCE_TYPES: - self.unbind_port_from_host(port_id, hostname) - elif device_type in InstanceType.BAREMETAL_INSTANCE_TYPES: - self.unbind_port_from_switch_interface(port_id, hostname, - switch_bindings) - self.delete_port(port_id, device_id, device_type) - port = self.get_instance_ports(device_id, device_type) - if not port: - # If the last port attached to an instance is deleted, cleanup the - # instance. - instances = [device_id] - self.delete_instance_bulk(tenant_id, instances, device_type) - - def _get_segment_list(self, network_id, segments): - if not network_id or not segments: - return [] - - return [{'id': s['id'], - 'type': s['network_type'], - 'segmentationId': s['segmentation_id'], - 'networkId': network_id, - 'segment_type': 'dynamic' if s.get('is_dynamic', False) else - 'static', - } for s in segments] - - def _get_host_bindings(self, port_id, host, network_id, segments): - return [{'portId': port_id, - 'hostBinding': [{ - 'host': host, - 'segment': self._get_segment_list(network_id, - segments), - }] - }] - - def bind_port_to_host(self, port_id, host, network_id, segments): - - url = 'region/' + self.region + '/port/' + port_id + '/binding' - bindings = self._get_host_bindings(port_id, host, network_id, - segments) - self._send_api_request(url, 'POST', bindings) - - def unbind_port_from_host(self, port_id, host): - url = 'region/' + self.region + '/port/' + port_id + '/binding' - binding = {'portId': port_id, - 'hostBinding': [{ - 'host': host, - }]} - self._send_api_request(url, 'DELETE', [binding]) - - def _get_switch_bindings(self, port_id, host, network_id, - switch_bindings, segments): - bindings = [] - for binding in switch_bindings: - if not binding: - continue - - switch = binding['switch_id'] - interface = binding['port_id'] - - bindings.append({'portId': port_id, - 'switchBinding': [{ - 'host': host, - 'switch': switch, - 'interface': interface, - 'segment': self._get_segment_list( - network_id, segments), - }]}) - return bindings - - def bind_port_to_switch_interface(self, port_id, host, network_id, - switch_bindings, segments): - - if not switch_bindings: - return - - url = 'region/' + self.region + '/port/' + port_id + '/binding' - bindings = self._get_switch_bindings(port_id, host, network_id, - switch_bindings, segments) - self._send_api_request(url, 'POST', bindings) - - def unbind_port_from_switch_interface(self, port_id, host, - switch_bindings): - url = 'region/' + self.region + '/port/' + port_id + '/binding' - bindings = self._get_switch_bindings(port_id, host, None, - switch_bindings, None) - self._send_api_request(url, 'DELETE', bindings) - - -class AristaRPCWrapperEapi(AristaRPCWrapperBase): - def __init__(self, ndb): - super(AristaRPCWrapperEapi, self).__init__(ndb) - # The cli_commands dict stores the mapping between the CLI command key - # and the actual CLI command. - self.cli_commands = { - 'timestamp': [ - 'show openstack config region %s timestamp' % self.region], - CMD_REGION_SYNC: 'region %s sync' % self.region, - CMD_INSTANCE: None, - CMD_SYNC_HEARTBEAT: 'sync heartbeat', - 'resource-pool': [], - 'features': {}, - } - - def _send_eapi_req(self, cmds, commands_to_log=None): - # This method handles all EAPI requests (using the requests library) - # and returns either None or response.json()['result'] from the EAPI - # request. - # - # Exceptions related to failures in connecting/ timeouts are caught - # here and logged. Other unexpected exceptions are logged and raised - - request_headers = {} - request_headers['Content-Type'] = 'application/json' - request_headers['Accept'] = 'application/json' - url = self._api_host_url(host=self._server_ip) - - params = {} - params['timestamps'] = "false" - params['format'] = "json" - params['version'] = 1 - params['cmds'] = cmds - - data = {} - data['id'] = "Arista ML2 driver" - data['method'] = "runCmds" - data['jsonrpc'] = "2.0" - data['params'] = params - - response = None - - try: - # NOTE(pbourke): shallow copy data and params to remove sensitive - # information before logging - log_data = dict(data) - log_data['params'] = dict(params) - log_data['params']['cmds'] = commands_to_log or cmds - msg = (_('EAPI request to %(ip)s contains %(cmd)s') % - {'ip': self._server_ip, 'cmd': json.dumps(log_data)}) - LOG.info(msg) - response = requests.post(url, timeout=self.conn_timeout, - verify=False, data=json.dumps(data)) - LOG.info(_LI('EAPI response contains: %s'), response.json()) - try: - return response.json()['result'] - except KeyError: - if response.json()['error']['code'] == 1002: - for data in response.json()['error']['data']: - if type(data) == dict and 'errors' in data: - if ERR_CVX_NOT_LEADER in data['errors'][0]: - msg = unicode("%s is not the master" % ( - self._server_ip)) - LOG.info(msg) - return None - - msg = "Unexpected EAPI error" - LOG.info(msg) - raise arista_exc.AristaRpcError(msg=msg) - except requests.exceptions.ConnectionError: - msg = (_('Error while trying to connect to %(ip)s') % - {'ip': self._server_ip}) - LOG.warning(msg) - return None - except requests.exceptions.ConnectTimeout: - msg = (_('Timed out while trying to connect to %(ip)s') % - {'ip': self._server_ip}) - LOG.warning(msg) - return None - except requests.exceptions.Timeout: - msg = (_('Timed out during an EAPI request to %(ip)s') % - {'ip': self._server_ip}) - LOG.warning(msg) - return None - except requests.exceptions.InvalidURL: - msg = (_('Ignore attempt to connect to invalid URL %(ip)s') % - {'ip': self._server_ip}) - LOG.warning(msg) - return None - except ValueError: - LOG.info("Ignoring invalid JSON response") - return None - except Exception as error: - msg = unicode(error) - LOG.warning(msg) - raise - - def check_supported_features(self): - cmd = ['show openstack instances'] - try: - self._run_eos_cmds(cmd) - self.cli_commands[CMD_INSTANCE] = 'instance' - except (arista_exc.AristaRpcError, Exception) as err: - self.cli_commands[CMD_INSTANCE] = None - LOG.warning(_LW("'instance' command is not available on EOS " - "because of %s"), err) - - # Get list of supported openstack features by CVX - cmd = ['show openstack features'] - try: - resp = self._run_eos_cmds(cmd) - self.cli_commands['features'] = resp[0].get('features', {}) - except (Exception, arista_exc.AristaRpcError): - self.cli_commands['features'] = {} - - def check_vlan_type_driver_commands(self): - """Checks the validity of CLI commands for Arista's VLAN type driver. - - This method tries to execute the commands used exclusively by the - arista_vlan type driver and stores the commands if they succeed. - """ - cmd = ['show openstack resource-pool vlan region %s uuid' - % self.region] - try: - self._run_eos_cmds(cmd) - self.cli_commands['resource-pool'] = cmd - except arista_exc.AristaRpcError: - self.cli_commands['resource-pool'] = [] - LOG.warning( - _LW("'resource-pool' command '%s' is not available on EOS"), - cmd) - - def _heartbeat_required(self, sync, counter=0): - return (sync and self.cli_commands[CMD_SYNC_HEARTBEAT] and - (counter % HEARTBEAT_INTERVAL) == 0) - - def get_vlan_assignment_uuid(self): - """Returns the UUID for the region's vlan assignment on CVX - - :returns: string containing the region's vlan assignment UUID - """ - vlan_uuid_cmd = self.cli_commands['resource-pool'] - if vlan_uuid_cmd: - return self._run_eos_cmds(commands=vlan_uuid_cmd)[0] - return None - - def get_vlan_allocation(self): - """Returns the status of the region's VLAN pool in CVX - - :returns: dictionary containg the assigned, allocated and available - VLANs for the region - """ - if not self.cli_commands['resource-pool']: - LOG.warning(_('The version of CVX you are using does not support' - 'arista VLAN type driver.')) - return None - cmd = ['show openstack resource-pools region %s' % self.region] - command_output = self._run_eos_cmds(cmd) - if command_output: - regions = command_output[0]['physicalNetwork'] - if self.region in regions.keys(): - return regions[self.region]['vlanPool']['default'] - return {'assignedVlans': '', - 'availableVlans': '', - 'allocatedVlans': ''} - - def get_tenants(self): - cmds = ['show openstack config region %s' % self.region] - command_output = self._run_eos_cmds(cmds) - tenants = command_output[0]['tenants'] - - return tenants - - def bm_and_dvr_supported(self): - return (self.cli_commands[CMD_INSTANCE] == 'instance') - - def _baremetal_support_check(self, vnic_type): - # Basic error checking for baremental deployments - if (vnic_type == portbindings.VNIC_BAREMETAL and - not self.bm_and_dvr_supported()): - msg = _("Baremetal instances are not supported in this" - " release of EOS") - LOG.error(msg) - raise arista_exc.AristaConfigError(msg=msg) - - def plug_port_into_network(self, device_id, host_id, port_id, - net_id, tenant_id, port_name, device_owner, - sg, orig_sg, vnic_type, segments, - switch_bindings=None): - if device_owner == n_const.DEVICE_OWNER_DHCP: - self.plug_dhcp_port_into_network(device_id, - host_id, - port_id, - net_id, - tenant_id, - segments, - port_name) - elif (device_owner.startswith('compute') or - device_owner.startswith('baremetal')): - if vnic_type == 'baremetal': - self.plug_baremetal_into_network(device_id, - host_id, - port_id, - net_id, - tenant_id, - segments, - port_name, - sg, orig_sg, - vnic_type, - switch_bindings) - else: - self.plug_host_into_network(device_id, - host_id, - port_id, - net_id, - tenant_id, - segments, - port_name) - elif device_owner == n_const.DEVICE_OWNER_DVR_INTERFACE: - self.plug_distributed_router_port_into_network(device_id, - host_id, - port_id, - net_id, - tenant_id, - segments) - - def unplug_port_from_network(self, device_id, device_owner, hostname, - port_id, network_id, tenant_id, sg, vnic_type, - switch_bindings=None): - if device_owner == n_const.DEVICE_OWNER_DHCP: - self.unplug_dhcp_port_from_network(device_id, - hostname, - port_id, - network_id, - tenant_id) - elif (device_owner.startswith('compute') or - device_owner.startswith('baremetal')): - if vnic_type == 'baremetal': - self.unplug_baremetal_from_network(device_id, - hostname, - port_id, - network_id, - tenant_id, - sg, - vnic_type, - switch_bindings) - else: - self.unplug_host_from_network(device_id, - hostname, - port_id, - network_id, - tenant_id) - elif device_owner == n_const.DEVICE_OWNER_DVR_INTERFACE: - self.unplug_distributed_router_port_from_network(device_id, - port_id, - hostname, - tenant_id) - - def plug_host_into_network(self, vm_id, host, port_id, - network_id, tenant_id, segments, port_name): - cmds = ['tenant %s' % tenant_id, - 'vm id %s hostid %s' % (vm_id, host)] - if port_name: - cmds.append('port id %s name "%s" network-id %s' % - (port_id, port_name, network_id)) - else: - cmds.append('port id %s network-id %s' % - (port_id, network_id)) - cmds.extend( - 'segment level %d id %s' % (level, segment['id']) - for level, segment in enumerate(segments)) - self._run_openstack_cmds(cmds) - - def plug_baremetal_into_network(self, vm_id, host, port_id, - network_id, tenant_id, segments, port_name, - sg=None, orig_sg=None, - vnic_type=None, switch_bindings=None): - # Basic error checking for baremental deployments - # notice that the following method throws and exception - # if an error condition exists - self._baremetal_support_check(vnic_type) - - # For baremetal, add host to the topology - if switch_bindings and vnic_type == portbindings.VNIC_BAREMETAL: - cmds = ['tenant %s' % tenant_id] - cmds.append('instance id %s hostid %s type baremetal' % - (vm_id, host)) - # This list keeps track of any ACLs that need to be rolled back - # in case we hit a failure trying to apply ACLs, and we end - # failing the transaction. - for binding in switch_bindings: - if not binding: - # skip all empty entries - continue - # Ensure that binding contains switch and port ID info - if binding['switch_id'] and binding['port_id']: - if port_name: - cmds.append('port id %s name "%s" network-id %s ' - 'type native switch-id %s switchport %s' % - (port_id, port_name, network_id, - binding['switch_id'], binding['port_id'])) - else: - cmds.append('port id %s network-id %s type native ' - 'switch-id %s switchport %s' % - (port_id, network_id, binding['switch_id'], - binding['port_id'])) - cmds.extend('segment level %d id %s' % (level, - segment['id']) - for level, segment in enumerate(segments)) - else: - msg = _('switch and port ID not specified for baremetal') - LOG.error(msg) - raise arista_exc.AristaConfigError(msg=msg) - cmds.append('exit') - self._run_openstack_cmds(cmds) - - if sg: - self.apply_security_group(sg, switch_bindings) - else: - # Security group was removed. Clean up the existing security - # groups. - if orig_sg: - self.remove_security_group(orig_sg, switch_bindings) - - def plug_dhcp_port_into_network(self, dhcp_id, host, port_id, - network_id, tenant_id, segments, - port_name): - cmds = ['tenant %s' % tenant_id, - 'network id %s' % network_id] - if port_name: - cmds.append('dhcp id %s hostid %s port-id %s name "%s"' % - (dhcp_id, host, port_id, port_name)) - else: - cmds.append('dhcp id %s hostid %s port-id %s' % - (dhcp_id, host, port_id)) - cmds.extend('segment level %d id %s' % (level, segment['id']) - for level, segment in enumerate(segments)) - self._run_openstack_cmds(cmds) - - def plug_distributed_router_port_into_network(self, router_id, host, - port_id, net_id, tenant_id, - segments): - if not self.bm_and_dvr_supported(): - LOG.info(ERR_DVR_NOT_SUPPORTED) - return - - cmds = ['tenant %s' % tenant_id, - 'instance id %s type router' % router_id, - 'port id %s network-id %s hostid %s' % (port_id, net_id, host)] - cmds.extend('segment level %d id %s' % (level, segment['id']) - for level, segment in enumerate(segments)) - self._run_openstack_cmds(cmds) - - def unplug_host_from_network(self, vm_id, host, port_id, - network_id, tenant_id): - cmds = ['tenant %s' % tenant_id, - 'vm id %s hostid %s' % (vm_id, host), - 'no port id %s' % port_id, - ] - self._run_openstack_cmds(cmds) - - def unplug_baremetal_from_network(self, vm_id, host, port_id, - network_id, tenant_id, sg, vnic_type, - switch_bindings=None): - # Basic error checking for baremental deployments - # notice that the following method throws and exception - # if an error condition exists - self._baremetal_support_check(vnic_type) - - # Following is a temporary code for native VLANs - should be removed - cmds = ['tenant %s' % tenant_id] - cmds.append('instance id %s hostid %s type baremetal' % (vm_id, host)) - cmds.append('no port id %s' % port_id) - self._run_openstack_cmds(cmds) - - # SG - Remove security group rules from the port - # after deleting the instance - for binding in switch_bindings: - if not binding: - continue - self.security_group_driver.remove_acl(sg, binding['switch_id'], - binding['port_id'], - binding['switch_info']) - - def unplug_dhcp_port_from_network(self, dhcp_id, host, port_id, - network_id, tenant_id): - cmds = ['tenant %s' % tenant_id, - 'network id %s' % network_id, - 'no dhcp id %s port-id %s' % (dhcp_id, port_id), - ] - self._run_openstack_cmds(cmds) - - def unplug_distributed_router_port_from_network(self, router_id, - port_id, host, tenant_id): - if not self.bm_and_dvr_supported(): - LOG.info(ERR_DVR_NOT_SUPPORTED) - return - - # When the last router port is removed, the router is deleted from EOS. - cmds = ['tenant %s' % tenant_id, - 'instance id %s type router' % router_id, - 'no port id %s hostid %s' % (port_id, host)] - self._run_openstack_cmds(cmds) - - def create_network_bulk(self, tenant_id, network_list, sync=False): - cmds = ['tenant %s' % tenant_id] - # Create a reference to function to avoid name lookups in the loop - append_cmd = cmds.append - for counter, network in enumerate(network_list, 1): - try: - append_cmd('network id %s name "%s"' % - (network['network_id'], network['network_name'])) - except KeyError: - append_cmd('network id %s' % network['network_id']) - - cmds.extend( - 'segment %s type %s id %d %s' % ( - seg['id'] if self.hpb_supported() else 1, - seg['network_type'], seg['segmentation_id'], - ('dynamic' if seg.get('is_dynamic', False) else 'static' - if self.hpb_supported() else '')) - for seg in network['segments'] - if seg['network_type'] != NETWORK_TYPE_FLAT - ) - shared_cmd = 'shared' if network['shared'] else 'no shared' - append_cmd(shared_cmd) - if self._heartbeat_required(sync, counter): - append_cmd(self.cli_commands[CMD_SYNC_HEARTBEAT]) - - if self._heartbeat_required(sync): - append_cmd(self.cli_commands[CMD_SYNC_HEARTBEAT]) - - self._run_openstack_cmds(cmds, sync=sync) - - def create_network_segments(self, tenant_id, network_id, - network_name, segments): - if segments: - cmds = ['tenant %s' % tenant_id, - 'network id %s name "%s"' % (network_id, network_name)] - cmds.extend( - 'segment %s type %s id %d %s' % ( - seg['id'], seg['network_type'], seg['segmentation_id'], - ('dynamic' if seg.get('is_dynamic', False) else 'static' - if self.hpb_supported() else '')) - for seg in segments) - self._run_openstack_cmds(cmds) - - def delete_network_segments(self, tenant_id, segments): - if not segments: - return - cmds = ['tenant %s' % tenant_id] - for segment in segments: - cmds.append('network id %s' % segment['network_id']) - cmds.append('no segment %s' % segment['id']) - - self._run_openstack_cmds(cmds) - - def delete_network_bulk(self, tenant_id, network_id_list, sync=False): - cmds = ['tenant %s' % tenant_id] - for counter, network_id in enumerate(network_id_list, 1): - cmds.append('no network id %s' % network_id) - if self._heartbeat_required(sync, counter): - cmds.append(self.cli_commands[CMD_SYNC_HEARTBEAT]) - - if self._heartbeat_required(sync): - cmds.append(self.cli_commands[CMD_SYNC_HEARTBEAT]) - self._run_openstack_cmds(cmds, sync=sync) - - def delete_vm_bulk(self, tenant_id, vm_id_list, sync=False): - cmds = ['tenant %s' % tenant_id] - counter = 0 - for vm_id in vm_id_list: - counter += 1 - cmds.append('no vm id %s' % vm_id) - if self._heartbeat_required(sync, counter): - cmds.append(self.cli_commands[CMD_SYNC_HEARTBEAT]) - - if self._heartbeat_required(sync): - cmds.append(self.cli_commands[CMD_SYNC_HEARTBEAT]) - self._run_openstack_cmds(cmds, sync=sync) - - def delete_instance_bulk(self, tenant_id, instance_id_list, instance_type, - sync=False): - cmds = ['tenant %s' % tenant_id] - counter = 0 - for instance in instance_id_list: - counter += 1 - cmds.append('no instance id %s' % instance) - if self._heartbeat_required(sync, counter): - cmds.append(self.cli_commands[CMD_SYNC_HEARTBEAT]) - - if self._heartbeat_required(sync): - cmds.append(self.cli_commands[CMD_SYNC_HEARTBEAT]) - self._run_openstack_cmds(cmds, sync=sync) - - def create_instance_bulk(self, tenant_id, neutron_ports, vms, - port_profiles, sync=False): - cmds = ['tenant %s' % tenant_id] - # Create a reference to function to avoid name lookups in the loop - append_cmd = cmds.append - counter = 0 - for vm in vms.values(): - counter += 1 - - for v_port in vm['ports']: - port_id = v_port['portId'] - if not v_port['hosts']: - # Skip all the ports that have no host associsted with them - continue - - if port_id not in neutron_ports.keys(): - continue - neutron_port = neutron_ports[port_id] - - port_name = '' - if 'name' in neutron_port: - port_name = 'name "%s"' % neutron_port['name'] - - device_owner = neutron_port['device_owner'] - vnic_type = port_profiles[port_id]['vnic_type'] - network_id = neutron_port['network_id'] - segments = [] - if self.hpb_supported(): - segments = self._ndb.get_all_network_segments(network_id) - if device_owner == n_const.DEVICE_OWNER_DHCP: - append_cmd('network id %s' % neutron_port['network_id']) - append_cmd('dhcp id %s hostid %s port-id %s %s' % - (vm['vmId'], v_port['hosts'][0], - neutron_port['id'], port_name)) - cmds.extend( - 'segment level %d id %s' % (level, segment['id']) - for level, segment in enumerate(segments)) - elif (device_owner.startswith('compute') or - device_owner.startswith('baremetal')): - if vnic_type == 'baremetal': - append_cmd('instance id %s hostid %s type baremetal' % - (vm['vmId'], v_port['hosts'][0])) - profile = port_profiles[neutron_port['id']] - profile = json.loads(profile['profile']) - for binding in profile['local_link_information']: - if not binding or not isinstance(binding, dict): - # skip all empty entries - continue - # Ensure that profile contains local link info - if binding['switch_id'] and binding['port_id']: - if port_name: - cmds.append('port id %s name "%s" ' - 'network-id %s type native ' - 'switch-id %s switchport %s' % - (port_id, port_name, - network_id, - binding['switch_id'], - binding['port_id'])) - else: - cmds.append('port id %s network-id %s ' - 'type native ' - 'switch-id %s switchport %s' % - (port_id, network_id, - binding['switch_id'], - binding['port_id'])) - cmds.extend('segment level %d id %s' % ( - level, segment['id']) - for level, segment in enumerate(segments)) - - else: - append_cmd('vm id %s hostid %s' % (vm['vmId'], - v_port['hosts'][0])) - append_cmd('port id %s %s network-id %s' % - (neutron_port['id'], port_name, - neutron_port['network_id'])) - cmds.extend('segment level %d id %s' % (level, - segment['id']) - for level, segment in enumerate(segments)) - elif device_owner == n_const.DEVICE_OWNER_DVR_INTERFACE: - if not self.bm_and_dvr_supported(): - LOG.info(ERR_DVR_NOT_SUPPORTED) - continue - append_cmd('instance id %s type router' % ( - neutron_port['device_id'])) - for host in v_port['hosts']: - append_cmd('port id %s network-id %s hostid %s' % ( - neutron_port['id'], - neutron_port['network_id'], host)) - cmds.extend('segment level %d id %s' % (level, - segment['id']) - for level, segment in enumerate(segments)) - else: - LOG.warning(_LW("Unknown device owner: %s"), - neutron_port['device_owner']) - - if self._heartbeat_required(sync, counter): - append_cmd(self.cli_commands[CMD_SYNC_HEARTBEAT]) - - if self._heartbeat_required(sync): - append_cmd(self.cli_commands[CMD_SYNC_HEARTBEAT]) - - self._run_openstack_cmds(cmds, sync=sync) - - def delete_tenant_bulk(self, tenant_list, sync=False): - cmds = [] - for tenant in tenant_list: - cmds.append('no tenant %s' % tenant) - if self._heartbeat_required(sync): - cmds.append(self.cli_commands[CMD_SYNC_HEARTBEAT]) - self._run_openstack_cmds(cmds, sync=sync) - - def delete_this_region(self): - cmds = ['enable', - 'configure', - 'cvx', - 'service openstack', - 'no region %s' % self.region, - ] - self._run_eos_cmds(cmds) - - def register_with_eos(self, sync=False): - cmds = ['auth url %s user %s password %s tenant %s' % ( - self._keystone_url(), - self.keystone_conf.admin_user, - self.keystone_conf.admin_password, - self.keystone_conf.admin_tenant_name)] - - log_cmds = ['auth url %s user %s password %s tenant %s' % ( - self._keystone_url(), - self.keystone_conf.admin_user, - '******', - self.keystone_conf.admin_tenant_name)] - - sync_interval_cmd = 'sync interval %d' % self.sync_interval - cmds.append(sync_interval_cmd) - log_cmds.append(sync_interval_cmd) - - self._run_openstack_cmds(cmds, commands_to_log=log_cmds, sync=sync) - - def get_region_updated_time(self): - timestamp_cmd = self.cli_commands['timestamp'] - if timestamp_cmd: - try: - return self._run_eos_cmds(commands=timestamp_cmd)[0] - except IndexError: - # EAPI request failed and so return none - msg = "Failed to get last sync timestamp; trigger full sync" - LOG.info(msg) - return None - - def _check_sync_lock(self, client): - """Check if the lock is owned by this client. - - :param client: Returns true only if the lock owner matches the expected - client. - """ - cmds = ['show sync lock'] - ret = self._run_openstack_cmds(cmds, sync=True) - for r in ret: - if 'owner' in r: - lock_owner = r['owner'] - LOG.info(_LI('Lock requested by: %s'), client) - LOG.info(_LI('Lock owner: %s'), lock_owner) - return lock_owner == client - return False - - def sync_supported(self): - return self.cli_commands[CMD_REGION_SYNC] - - def hpb_supported(self): - return 'hierarchical-port-binding' in self.cli_commands['features'] - - def sync_start(self): - try: - cmds = [] - if self.sync_supported(): - # Locking the region during sync is supported. - client_id = socket.gethostname().split('.')[0] - request_id = self._get_random_name() - cmds = ['sync lock %s %s' % (client_id, request_id)] - self._run_openstack_cmds(cmds) - # Check whether the lock was acquired. - return self._check_sync_lock(client_id) - else: - cmds = ['sync start'] - self._run_openstack_cmds(cmds) - return True - except arista_exc.AristaRpcError: - return False - - def sync_end(self): - try: - # 'sync end' can be sent only when the region has been entered in - # the sync mode - self._run_openstack_cmds(['sync end'], sync=True) - return True - except arista_exc.AristaRpcError: - return False - - def _run_eos_cmds(self, commands, commands_to_log=None): - """Execute/sends a CAPI (Command API) command to EOS. - - In this method, list of commands is appended with prefix and - postfix commands - to make is understandble by EOS. - - :param commands : List of command to be executed on EOS. - :param commands_to_log : This should be set to the command that is - logged. If it is None, then the commands - param is logged. - """ - - # Always figure out who is master (starting with the last known val) - try: - if self._get_eos_master() is None: - msg = "Failed to identify CVX master" - self.set_cvx_unavailable() - raise arista_exc.AristaRpcError(msg=msg) - except Exception: - self.set_cvx_unavailable() - raise - - self.set_cvx_available() - log_cmds = commands - if commands_to_log: - log_cmds = commands_to_log - - LOG.info(_LI('Executing command on Arista EOS: %s'), log_cmds) - # this returns array of return values for every command in - # full_command list - try: - response = self._send_eapi_req(cmds=commands, - commands_to_log=log_cmds) - if response is None: - # Reset the server as we failed communicating with it - self._server_ip = None - self.set_cvx_unavailable() - msg = "Failed to communicate with CVX master" - raise arista_exc.AristaRpcError(msg=msg) - return response - except arista_exc.AristaRpcError: - raise - - def _build_command(self, cmds, sync=False): - """Build full EOS's openstack CLI command. - - Helper method to add commands to enter and exit from openstack - CLI modes. - - :param cmds: The openstack CLI commands that need to be executed - in the openstack config mode. - :param sync: This flags indicates that the region is being synced. - """ - - region_cmd = 'region %s' % self.region - if sync and self.sync_supported(): - region_cmd = self.cli_commands[CMD_REGION_SYNC] - - full_command = [ - 'enable', - 'configure', - 'cvx', - 'service openstack', - region_cmd, - ] - full_command.extend(cmds) - return full_command - - def _run_openstack_cmds(self, commands, commands_to_log=None, sync=False): - """Execute/sends a CAPI (Command API) command to EOS. - - In this method, list of commands is appended with prefix and - postfix commands - to make is understandble by EOS. - - :param commands : List of command to be executed on EOS. - :param commands_to_logs : This should be set to the command that is - logged. If it is None, then the commands - param is logged. - :param sync: This flags indicates that the region is being synced. - """ - - full_command = self._build_command(commands, sync=sync) - if commands_to_log: - full_log_command = self._build_command(commands_to_log, sync=sync) - else: - full_log_command = None - return self._run_eos_cmds(full_command, full_log_command) - - def _get_eos_master(self): - # Use guarded command to figure out if this is the master - cmd = ['show openstack agent uuid'] - - cvx = self._get_cvx_hosts() - # Identify which EOS instance is currently the master - for self._server_ip in cvx: - try: - response = self._send_eapi_req(cmds=cmd, commands_to_log=cmd) - if response is not None: - return self._server_ip - else: - continue # Try another EOS instance - except Exception: - raise - - # Couldn't find an instance that is the leader and returning none - self._server_ip = None - msg = "Failed to reach the CVX master" - LOG.error(msg) - return None - - def _api_host_url(self, host=""): - return ('https://%s:%s@%s/command-api' % - (self._api_username(), - self._api_password(), - host)) - - def get_physical_network(self, host_id): - """Returns dirctionary which contains physical topology information - - for a given host_id - """ - fqdns_used = cfg.CONF.ml2_arista['use_fqdn'] - physnet = None - switch_id = None - mac_to_hostname = {} - cmds = ['show network physical-topology neighbors', - 'show network physical-topology hosts'] - try: - response = self._run_eos_cmds(cmds) - # Get response for 'show network physical-topology neighbors' - # command - neighbors = response[0]['neighbors'] - for neighbor in neighbors: - if host_id in neighbor: - switchname = neighbors[neighbor]['toPort'][0]['hostname'] - physnet = switchname if fqdns_used else ( - switchname.split('.')[0]) - switch_id = neighbors[neighbor]['toPort'][0].get('hostid') - if not switch_id: - switch_id = response[1]['hosts'][switchname]['name'] - break - - # Check if the switch is part of an MLAG pair, and lookup the - # pair's physnet name if so - physnet = self.mlag_pairs.get(physnet, physnet) - - for host in response[1]['hosts'].values(): - mac_to_hostname[host['name']] = host['hostname'] - - res = {'physnet': physnet, - 'switch_id': switch_id, - 'mac_to_hostname': mac_to_hostname} - LOG.debug("get_physical_network: Physical Network info for " - "%(host)s is %(res)s", {'host': host_id, - 'res': res}) - return res - except Exception as exc: - LOG.error(_LE('command %(cmds)s failed with ' - '%(exc)s'), {'cmds': cmds, 'exc': exc}) - return {} - - -class SyncService(object): - """Synchronization of information between Neutron and EOS - - Periodically (through configuration option), this service - ensures that Networks and VMs configured on EOS/Arista HW - are always in sync with Neutron DB. - """ - def __init__(self, rpc_wrapper, neutron_db): - self._rpc = rpc_wrapper - self._ndb = neutron_db - self._force_sync = True - self._region_updated_time = None - - def force_sync(self): - """Sets the force_sync flag.""" - self._force_sync = True - - def do_synchronize(self): - """Periodically check whether EOS is in sync with ML2 driver. - - If ML2 database is not in sync with EOS, then compute the diff and - send it down to EOS. - """ - # Perform sync of Security Groups unconditionally - try: - self._rpc.perform_sync_of_sg() - except Exception as e: - LOG.warning(e) - - # Check whether CVX is available before starting the sync. - if not self._rpc.check_cvx_availability(): - LOG.warning("Not syncing as CVX is unreachable") - self.force_sync() - return - - if not self._sync_required(): - return - - LOG.info('Attempting to sync') - # Send 'sync start' marker. - if not self._rpc.sync_start(): - LOG.info(_LI('Not starting sync, setting force')) - self._force_sync = True - return - - # Perform the actual synchronization. - self.synchronize() - - # Send 'sync end' marker. - if not self._rpc.sync_end(): - LOG.info(_LI('Sync end failed, setting force')) - self._force_sync = True - return - - self._set_region_updated_time() - - def synchronize(self): - """Sends data to EOS which differs from neutron DB.""" - - LOG.info(_LI('Syncing Neutron <-> EOS')) - try: - # Register with EOS to ensure that it has correct credentials - self._rpc.register_with_eos(sync=True) - self._rpc.check_supported_features() - eos_tenants = self._rpc.get_tenants() - except arista_exc.AristaRpcError: - LOG.warning(EOS_UNREACHABLE_MSG) - self._force_sync = True - return - - db_tenants = db_lib.get_tenants() - - # Delete tenants that are in EOS, but not in the database - tenants_to_delete = frozenset(eos_tenants.keys()).difference( - db_tenants.keys()) - - if tenants_to_delete: - try: - self._rpc.delete_tenant_bulk(tenants_to_delete, sync=True) - except arista_exc.AristaRpcError: - LOG.warning(EOS_UNREACHABLE_MSG) - self._force_sync = True - return - - # None of the commands have failed till now. But if subsequent - # operations fail, then force_sync is set to true - self._force_sync = False - - # Create a dict of networks keyed by id. - neutron_nets = dict( - (network['id'], network) for network in - self._ndb.get_all_networks() - ) - - # Get Baremetal port switch_bindings, if any - port_profiles = db_lib.get_all_portbindings() - # To support shared networks, split the sync loop in two parts: - # In first loop, delete unwanted VM and networks and update networks - # In second loop, update VMs. This is done to ensure that networks for - # all tenats are updated before VMs are updated - instances_to_update = {} - for tenant in db_tenants.keys(): - db_nets = db_lib.get_networks(tenant) - db_instances = db_lib.get_vms(tenant) - - eos_nets = self._get_eos_networks(eos_tenants, tenant) - eos_vms, eos_bms, eos_routers = self._get_eos_vms(eos_tenants, - tenant) - - db_nets_key_set = frozenset(db_nets.keys()) - db_instances_key_set = frozenset(db_instances.keys()) - eos_nets_key_set = frozenset(eos_nets.keys()) - eos_vms_key_set = frozenset(eos_vms.keys()) - eos_routers_key_set = frozenset(eos_routers.keys()) - eos_bms_key_set = frozenset(eos_bms.keys()) - - # Create a candidate list by incorporating all instances - eos_instances_key_set = (eos_vms_key_set | eos_routers_key_set | - eos_bms_key_set) - - # Find the networks that are present on EOS, but not in Neutron DB - nets_to_delete = eos_nets_key_set.difference(db_nets_key_set) - - # Find the VMs that are present on EOS, but not in Neutron DB - instances_to_delete = eos_instances_key_set.difference( - db_instances_key_set) - - vms_to_delete = [ - vm for vm in eos_vms_key_set if vm in instances_to_delete] - routers_to_delete = [ - r for r in eos_routers_key_set if r in instances_to_delete] - bms_to_delete = [ - b for b in eos_bms_key_set if b in instances_to_delete] - - # Find the Networks that are present in Neutron DB, but not on EOS - nets_to_update = db_nets_key_set.difference(eos_nets_key_set) - - # Find the VMs that are present in Neutron DB, but not on EOS - instances_to_update[tenant] = db_instances_key_set.difference( - eos_instances_key_set) - - try: - if vms_to_delete: - self._rpc.delete_vm_bulk(tenant, vms_to_delete, sync=True) - if routers_to_delete: - if self._rpc.bm_and_dvr_supported(): - self._rpc.delete_instance_bulk(tenant, - routers_to_delete, - InstanceType.ROUTER, - sync=True) - else: - LOG.info(ERR_DVR_NOT_SUPPORTED) - - if bms_to_delete: - if self._rpc.bm_and_dvr_supported(): - self._rpc.delete_instance_bulk(tenant, - bms_to_delete, - InstanceType.BAREMETAL, - sync=True) - else: - LOG.info(BAREMETAL_NOT_SUPPORTED) - - if nets_to_delete: - self._rpc.delete_network_bulk(tenant, nets_to_delete, - sync=True) - if nets_to_update: - networks = [{ - 'network_id': net_id, - 'network_name': - neutron_nets.get(net_id, {'name': ''})['name'], - 'shared': - neutron_nets.get(net_id, - {'shared': False})['shared'], - 'segments': self._ndb.get_all_network_segments(net_id), - } - for net_id in nets_to_update - ] - self._rpc.create_network_bulk(tenant, networks, sync=True) - except arista_exc.AristaRpcError: - LOG.warning(EOS_UNREACHABLE_MSG) - self._force_sync = True - - # Now update the VMs - for tenant in instances_to_update: - if not instances_to_update[tenant]: - continue - try: - # Filter the ports to only the vms that we are interested - # in. - ports_of_interest = {} - for port in self._ndb.get_all_ports_for_tenant(tenant): - ports_of_interest.update( - self._port_dict_representation(port)) - - if ports_of_interest: - db_vms = db_lib.get_vms(tenant) - if db_vms: - self._rpc.create_instance_bulk(tenant, - ports_of_interest, - db_vms, - port_profiles, - sync=True) - except arista_exc.AristaRpcError: - LOG.warning(EOS_UNREACHABLE_MSG) - self._force_sync = True - - def _region_in_sync(self): - """Checks if the region is in sync with EOS. - - Checks whether the timestamp stored in EOS is the same as the - timestamp stored locally. - """ - eos_region_updated_times = self._rpc.get_region_updated_time() - if eos_region_updated_times: - return (self._region_updated_time and - (self._region_updated_time['regionTimestamp'] == - eos_region_updated_times['regionTimestamp'])) - else: - return False - - def _sync_required(self): - """"Check whether the sync is required.""" - try: - # Get the time at which entities in the region were updated. - # If the times match, then ML2 is in sync with EOS. Otherwise - # perform a complete sync. - if not self._force_sync and self._region_in_sync(): - LOG.info(_LI('OpenStack and EOS are in sync!')) - return False - except arista_exc.AristaRpcError: - LOG.warning(EOS_UNREACHABLE_MSG) - # Force an update incase of an error. - self._force_sync = True - return True - - def _set_region_updated_time(self): - """Get the region updated time from EOS and store it locally.""" - try: - self._region_updated_time = self._rpc.get_region_updated_time() - except arista_exc.AristaRpcError: - # Force an update incase of an error. - self._force_sync = True - - def _get_eos_networks(self, eos_tenants, tenant): - networks = {} - if eos_tenants and tenant in eos_tenants: - networks = eos_tenants[tenant]['tenantNetworks'] - return networks - - def _get_eos_vms(self, eos_tenants, tenant): - vms = {} - bms = {} - routers = {} - if eos_tenants and tenant in eos_tenants: - vms = eos_tenants[tenant]['tenantVmInstances'] - if 'tenantBaremetalInstances' in eos_tenants[tenant]: - # Check if baremetal service is supported - bms = eos_tenants[tenant]['tenantBaremetalInstances'] - if 'tenantRouterInstances' in eos_tenants[tenant]: - routers = eos_tenants[tenant]['tenantRouterInstances'] - return vms, bms, routers - - def _port_dict_representation(self, port): - return {port['id']: {'device_owner': port['device_owner'], - 'device_id': port['device_id'], - 'name': port['name'], - 'id': port['id'], - 'tenant_id': port['tenant_id'], - 'network_id': port['network_id']}} diff --git a/networking_arista/ml2/arista_sec_gp.py b/networking_arista/ml2/arista_sec_gp.py deleted file mode 100644 index 020a3ad..0000000 --- a/networking_arista/ml2/arista_sec_gp.py +++ /dev/null @@ -1,603 +0,0 @@ -# Copyright (c) 2016 OpenStack Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import json - -from oslo_config import cfg -from oslo_log import log as logging - -from networking_arista._i18n import _, _LI -from networking_arista.common import api -from networking_arista.common import db_lib -from networking_arista.common import exceptions as arista_exc - -LOG = logging.getLogger(__name__) - -EOS_UNREACHABLE_MSG = _('Unable to reach EOS') - -# Note 'None,null' means default rule - i.e. deny everything -SUPPORTED_SG_PROTOCOLS = [None, 'tcp', 'udp', 'icmp'] - -acl_cmd = { - 'acl': {'create': ['ip access-list {0}'], - 'in_rule': ['permit {0} {1} any range {2} {3}'], - 'out_rule': ['permit {0} any {1} range {2} {3}'], - 'in_icmp_custom1': ['permit icmp {0} any {1}'], - 'out_icmp_custom1': ['permit icmp any {0} {1}'], - 'in_icmp_custom2': ['permit icmp {0} any {1} {2}'], - 'out_icmp_custom2': ['permit icmp any {0} {1} {2}'], - 'default': [], - 'delete_acl': ['no ip access-list {0}'], - 'del_in_icmp_custom1': ['ip access-list {0}', - 'no permit icmp {1} any {2}', - 'exit'], - 'del_out_icmp_custom1': ['ip access-list {0}', - 'no permit icmp any {1} {2}', - 'exit'], - 'del_in_icmp_custom2': ['ip access-list {0}', - 'no permit icmp {1} any {2} {3}', - 'exit'], - 'del_out_icmp_custom2': ['ip access-list {0}', - 'no permit icmp any {1} {2} {3}', - 'exit'], - 'del_in_acl_rule': ['ip access-list {0}', - 'no permit {1} {2} any range {3} {4}', - 'exit'], - 'del_out_acl_rule': ['ip access-list {0}', - 'no permit {1} any {2} range {3} {4}', - 'exit']}, - - 'apply': {'ingress': ['interface {0}', - 'ip access-group {1} in', - 'exit'], - 'egress': ['interface {0}', - 'ip access-group {1} out', - 'exit'], - 'rm_ingress': ['interface {0}', - 'no ip access-group {1} in', - 'exit'], - 'rm_egress': ['interface {0}', - 'no ip access-group {1} out', - 'exit']}} - - -class AristaSecGroupSwitchDriver(object): - """Wraps Arista JSON RPC. - - All communications between Neutron and EOS are over JSON RPC. - EOS - operating system used on Arista hardware - Command API - JSON RPC API provided by Arista EOS - """ - def __init__(self, neutron_db): - self._ndb = neutron_db - self._servers = [] - self._hosts = {} - self.sg_enabled = cfg.CONF.ml2_arista.get('sec_group_support') - self._validate_config() - for s in cfg.CONF.ml2_arista.switch_info: - switch_ip, switch_user, switch_pass = s.split(":") - if switch_pass == "''": - switch_pass = '' - self._hosts[switch_ip] = ( - {'user': switch_user, 'password': switch_pass}) - self._servers.append(self._make_eapi_client(switch_ip)) - self.aclCreateDict = acl_cmd['acl'] - self.aclApplyDict = acl_cmd['apply'] - - def _make_eapi_client(self, host): - return api.EAPIClient( - host, - username=self._hosts[host]['user'], - password=self._hosts[host]['password'], - verify=False, - timeout=cfg.CONF.ml2_arista.conn_timeout - ) - - def _validate_config(self): - if not self.sg_enabled: - return - if len(cfg.CONF.ml2_arista.get('switch_info')) < 1: - msg = _('Required option - when "sec_group_support" is enabled, ' - 'at least one switch must be specified ') - LOG.exception(msg) - raise arista_exc.AristaConfigError(msg=msg) - - def _create_acl_on_eos(self, in_cmds, out_cmds, protocol, cidr, - from_port, to_port, direction): - """Creates an ACL on Arista HW Device. - - :param name: Name for the ACL - :param server: Server endpoint on the Arista switch to be configured - """ - if protocol == 'icmp': - # ICMP rules require special processing - if ((from_port and to_port) or - (not from_port and not to_port)): - rule = 'icmp_custom2' - elif from_port and not to_port: - rule = 'icmp_custom1' - else: - msg = _('Invalid ICMP rule specified') - LOG.exception(msg) - raise arista_exc.AristaSecurityGroupError(msg=msg) - rule_type = 'in' - cmds = in_cmds - if direction == 'egress': - rule_type = 'out' - cmds = out_cmds - final_rule = rule_type + '_' + rule - acl_dict = self.aclCreateDict[final_rule] - - # None port is probematic - should be replaced with 0 - if not from_port: - from_port = 0 - if not to_port: - to_port = 0 - - for c in acl_dict: - if rule == 'icmp_custom2': - cmds.append(c.format(cidr, from_port, to_port)) - else: - cmds.append(c.format(cidr, from_port)) - return in_cmds, out_cmds - else: - # Non ICMP rules processing here - acl_dict = self.aclCreateDict['in_rule'] - cmds = in_cmds - if direction == 'egress': - acl_dict = self.aclCreateDict['out_rule'] - cmds = out_cmds - if not protocol: - acl_dict = self.aclCreateDict['default'] - - for c in acl_dict: - cmds.append(c.format(protocol, cidr, - from_port, to_port)) - return in_cmds, out_cmds - - def _delete_acl_from_eos(self, name, server): - """deletes an ACL from Arista HW Device. - - :param name: Name for the ACL - :param server: Server endpoint on the Arista switch to be configured - """ - cmds = [] - - for c in self.aclCreateDict['delete_acl']: - cmds.append(c.format(name)) - - self._run_openstack_sg_cmds(cmds, server) - - def _delete_acl_rule_from_eos(self, name, - protocol, cidr, - from_port, to_port, - direction, server): - """deletes an ACL from Arista HW Device. - - :param name: Name for the ACL - :param server: Server endpoint on the Arista switch to be configured - """ - cmds = [] - - if protocol == 'icmp': - # ICMP rules require special processing - if ((from_port and to_port) or - (not from_port and not to_port)): - rule = 'icmp_custom2' - elif from_port and not to_port: - rule = 'icmp_custom1' - else: - msg = _('Invalid ICMP rule specified') - LOG.exception(msg) - raise arista_exc.AristaSecurityGroupError(msg=msg) - rule_type = 'del_in' - if direction == 'egress': - rule_type = 'del_out' - final_rule = rule_type + '_' + rule - acl_dict = self.aclCreateDict[final_rule] - - # None port is probematic - should be replaced with 0 - if not from_port: - from_port = 0 - if not to_port: - to_port = 0 - - for c in acl_dict: - if rule == 'icmp_custom2': - cmds.append(c.format(name, cidr, from_port, to_port)) - else: - cmds.append(c.format(name, cidr, from_port)) - - else: - acl_dict = self.aclCreateDict['del_in_acl_rule'] - if direction == 'egress': - acl_dict = self.aclCreateDict['del_out_acl_rule'] - - for c in acl_dict: - cmds.append(c.format(name, protocol, cidr, - from_port, to_port)) - - self._run_openstack_sg_cmds(cmds, server) - - def _apply_acl_on_eos(self, port_id, name, direction, server): - """Creates an ACL on Arista HW Device. - - :param port_id: The port where the ACL needs to be applied - :param name: Name for the ACL - :param direction: must contain "ingress" or "egress" - :param server: Server endpoint on the Arista switch to be configured - """ - cmds = [] - - for c in self.aclApplyDict[direction]: - cmds.append(c.format(port_id, name)) - - self._run_openstack_sg_cmds(cmds, server) - - def _remove_acl_from_eos(self, port_id, name, direction, server): - """Remove an ACL from a port on Arista HW Device. - - :param port_id: The port where the ACL needs to be applied - :param name: Name for the ACL - :param direction: must contain "ingress" or "egress" - :param server: Server endpoint on the Arista switch to be configured - """ - cmds = [] - - acl_cmd = self.aclApplyDict['rm_ingress'] - if direction == 'egress': - acl_cmd = self.aclApplyDict['rm_egress'] - for c in acl_cmd: - cmds.append(c.format(port_id, name)) - - self._run_openstack_sg_cmds(cmds, server) - - def _create_acl_rule(self, in_cmds, out_cmds, sgr): - """Creates an ACL on Arista Switch. - - For a given Security Group (ACL), it adds additional rule - Deals with multiple configurations - such as multiple switches - """ - # Only deal with valid protocols - skip the rest - if not sgr or sgr['protocol'] not in SUPPORTED_SG_PROTOCOLS: - return in_cmds, out_cmds - - remote_ip = sgr['remote_ip_prefix'] - if not remote_ip: - remote_ip = 'any' - min_port = sgr['port_range_min'] - if not min_port: - min_port = 0 - max_port = sgr['port_range_max'] - if not max_port and sgr['protocol'] != 'icmp': - max_port = 65535 - in_cmds, out_cmds = self._create_acl_on_eos(in_cmds, out_cmds, - sgr['protocol'], - remote_ip, - min_port, - max_port, - sgr['direction']) - return in_cmds, out_cmds - - def create_acl_rule(self, sgr): - """Creates an ACL on Arista Switch. - - For a given Security Group (ACL), it adds additional rule - Deals with multiple configurations - such as multiple switches - """ - # Do nothing if Security Groups are not enabled - if not self.sg_enabled: - return - - name = self._arista_acl_name(sgr['security_group_id'], - sgr['direction']) - cmds = [] - for c in self.aclCreateDict['create']: - cmds.append(c.format(name)) - in_cmds, out_cmds = self._create_acl_rule(cmds, cmds, sgr) - - cmds = in_cmds - if sgr['direction'] == 'egress': - cmds = out_cmds - - cmds.append('exit') - - for s in self._servers: - try: - self._run_openstack_sg_cmds(cmds, s) - except Exception: - msg = (_('Failed to create ACL rule on EOS %s') % s) - LOG.exception(msg) - raise arista_exc.AristaSecurityGroupError(msg=msg) - - def delete_acl_rule(self, sgr): - """Deletes an ACL rule on Arista Switch. - - For a given Security Group (ACL), it adds removes a rule - Deals with multiple configurations - such as multiple switches - """ - # Do nothing if Security Groups are not enabled - if not self.sg_enabled: - return - - # Only deal with valid protocols - skip the rest - if not sgr or sgr['protocol'] not in SUPPORTED_SG_PROTOCOLS: - return - - # Build seperate ACL for ingress and egress - name = self._arista_acl_name(sgr['security_group_id'], - sgr['direction']) - remote_ip = sgr['remote_ip_prefix'] - if not remote_ip: - remote_ip = 'any' - min_port = sgr['port_range_min'] - if not min_port: - min_port = 0 - max_port = sgr['port_range_max'] - if not max_port and sgr['protocol'] != 'icmp': - max_port = 65535 - for s in self._servers: - try: - self._delete_acl_rule_from_eos(name, - sgr['protocol'], - remote_ip, - min_port, - max_port, - sgr['direction'], - s) - except Exception: - msg = (_('Failed to delete ACL on EOS %s') % s) - LOG.exception(msg) - raise arista_exc.AristaSecurityGroupError(msg=msg) - - def _create_acl_shell(self, sg_id): - """Creates an ACL on Arista Switch. - - For a given Security Group (ACL), it adds additional rule - Deals with multiple configurations - such as multiple switches - """ - # Build seperate ACL for ingress and egress - direction = ['ingress', 'egress'] - cmds = [] - for d in range(len(direction)): - name = self._arista_acl_name(sg_id, direction[d]) - cmds.append([]) - for c in self.aclCreateDict['create']: - cmds[d].append(c.format(name)) - return cmds[0], cmds[1] - - def create_acl(self, sg): - """Creates an ACL on Arista Switch. - - Deals with multiple configurations - such as multiple switches - """ - # Do nothing if Security Groups are not enabled - if not self.sg_enabled: - return - - if not sg: - msg = _('Invalid or Empty Security Group Specified') - raise arista_exc.AristaSecurityGroupError(msg=msg) - - in_cmds, out_cmds = self._create_acl_shell(sg['id']) - for sgr in sg['security_group_rules']: - in_cmds, out_cmds = self._create_acl_rule(in_cmds, out_cmds, sgr) - in_cmds.append('exit') - out_cmds.append('exit') - - for s in self._servers: - try: - self._run_openstack_sg_cmds(in_cmds, s) - self._run_openstack_sg_cmds(out_cmds, s) - - except Exception: - msg = (_('Failed to create ACL on EOS %s') % s) - LOG.exception(msg) - raise arista_exc.AristaSecurityGroupError(msg=msg) - - def delete_acl(self, sg): - """Deletes an ACL from Arista Switch. - - Deals with multiple configurations - such as multiple switches - """ - # Do nothing if Security Groups are not enabled - if not self.sg_enabled: - return - - if not sg: - msg = _('Invalid or Empty Security Group Specified') - raise arista_exc.AristaSecurityGroupError(msg=msg) - - direction = ['ingress', 'egress'] - for d in range(len(direction)): - name = self._arista_acl_name(sg['id'], direction[d]) - - for s in self._servers: - try: - self._delete_acl_from_eos(name, s) - except Exception: - msg = (_('Failed to create ACL on EOS %s') % s) - LOG.exception(msg) - raise arista_exc.AristaSecurityGroupError(msg=msg) - - def apply_acl(self, sgs, switch_id, port_id, switch_info): - """Creates an ACL on Arista Switch. - - Applies ACLs to the baremetal ports only. The port/switch - details is passed through the parameters. - Deals with multiple configurations - such as multiple switches - param sgs: List of Security Groups - param switch_id: Switch ID of TOR where ACL needs to be applied - param port_id: Port ID of port where ACL needs to be applied - param switch_info: IP address of the TOR - """ - # Do nothing if Security Groups are not enabled - if not self.sg_enabled: - return - - # We do not support more than one security group on a port - if not sgs or len(sgs) > 1: - msg = (_('Only one Security Group Supported on a port %s') % sgs) - raise arista_exc.AristaSecurityGroupError(msg=msg) - - sg = self._ndb.get_security_group(sgs[0]) - - # We already have ACLs on the TORs. - # Here we need to find out which ACL is applicable - i.e. - # Ingress ACL, egress ACL or both - direction = ['ingress', 'egress'] - - server = self._make_eapi_client(switch_info) - - for d in range(len(direction)): - name = self._arista_acl_name(sg['id'], direction[d]) - try: - self._apply_acl_on_eos(port_id, name, direction[d], server) - except Exception: - msg = (_('Failed to apply ACL on port %s') % port_id) - LOG.exception(msg) - raise arista_exc.AristaSecurityGroupError(msg=msg) - - def remove_acl(self, sgs, switch_id, port_id, switch_info): - """Removes an ACL from Arista Switch. - - Removes ACLs from the baremetal ports only. The port/switch - details is passed throuhg the parameters. - param sgs: List of Security Groups - param switch_id: Switch ID of TOR where ACL needs to be removed - param port_id: Port ID of port where ACL needs to be removed - param switch_info: IP address of the TOR - """ - # Do nothing if Security Groups are not enabled - if not self.sg_enabled: - return - - # We do not support more than one security group on a port - if not sgs or len(sgs) > 1: - msg = (_('Only one Security Group Supported on a port %s') % sgs) - raise arista_exc.AristaSecurityGroupError(msg=msg) - - sg = self._ndb.get_security_group(sgs[0]) - - # We already have ACLs on the TORs. - # Here we need to find out which ACL is applicable - i.e. - # Ingress ACL, egress ACL or both - direction = [] - for sgr in sg['security_group_rules']: - # Only deal with valid protocols - skip the rest - if not sgr or sgr['protocol'] not in SUPPORTED_SG_PROTOCOLS: - continue - - if sgr['direction'] not in direction: - direction.append(sgr['direction']) - - # THIS IS TOTAL HACK NOW - just for testing - # Assumes the credential of all switches are same as specified - # in the condig file - server = self._make_eapi_client(switch_info) - for d in range(len(direction)): - name = self._arista_acl_name(sg['id'], direction[d]) - try: - self._remove_acl_from_eos(port_id, name, direction[d], server) - except Exception: - msg = (_('Failed to remove ACL on port %s') % port_id) - LOG.exception(msg) - # No need to raise exception for ACL removal - # raise arista_exc.AristaSecurityGroupError(msg=msg) - - def _run_openstack_sg_cmds(self, commands, server): - """Execute/sends a CAPI (Command API) command to EOS. - - In this method, list of commands is appended with prefix and - postfix commands - to make is understandble by EOS. - - :param commands : List of command to be executed on EOS. - :param server: Server endpoint on the Arista switch to be configured - """ - command_start = ['enable', 'configure'] - command_end = ['exit'] - full_command = command_start + commands + command_end - - LOG.info(_LI('Executing command on Arista EOS: %s'), full_command) - - try: - # this returns array of return values for every command in - # full_command list - ret = server.execute(full_command) - LOG.info(_LI('Results of execution on Arista EOS: %s'), ret) - - except Exception: - msg = (_('Error occurred while trying to execute ' - 'commands %(cmd)s on EOS %(host)s') % - {'cmd': full_command, 'host': server}) - LOG.exception(msg) - raise arista_exc.AristaServicePluginRpcError(msg=msg) - - def _arista_acl_name(self, name, direction): - """Generate an arista specific name for this ACL. - - Use a unique name so that OpenStack created ACLs - can be distinguishged from the user created ACLs - on Arista HW. - """ - in_out = 'IN' - if direction == 'egress': - in_out = 'OUT' - return 'SG' + '-' + in_out + '-' + name - - def perform_sync_of_sg(self): - """Perform sync of the security groups between ML2 and EOS. - - This is unconditional sync to ensure that all security - ACLs are pushed to all the switches, in case of switch - or neutron reboot - """ - # Do nothing if Security Groups are not enabled - if not self.sg_enabled: - return - - arista_ports = db_lib.get_ports() - neutron_sgs = self._ndb.get_security_groups() - sg_bindings = self._ndb.get_all_security_gp_to_port_bindings() - sgs = [] - sgs_dict = {} - arista_port_ids = arista_ports.keys() - - # Get the list of Security Groups of interest to us - for s in sg_bindings: - if s['port_id'] in arista_port_ids: - if not s['security_group_id'] in sgs: - sgs_dict[s['port_id']] = ( - {'security_group_id': s['security_group_id']}) - sgs.append(s['security_group_id']) - - # Create the ACLs on Arista Switches - for idx in range(len(sgs)): - self.create_acl(neutron_sgs[sgs[idx]]) - - # Get Baremetal port profiles, if any - bm_port_profiles = db_lib.get_all_baremetal_ports() - - if bm_port_profiles: - for bm in bm_port_profiles.values(): - if bm['port_id'] in sgs_dict: - sg = sgs_dict[bm['port_id']]['security_group_id'] - profile = json.loads(bm['profile']) - link_info = profile['local_link_information'] - for l in link_info: - if not l: - # skip all empty entries - continue - self.apply_acl([sg], l['switch_id'], - l['port_id'], l['switch_info']) diff --git a/networking_arista/ml2/drivers/__init__.py b/networking_arista/ml2/drivers/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/networking_arista/ml2/drivers/driver_helpers.py b/networking_arista/ml2/drivers/driver_helpers.py deleted file mode 100644 index ad926d8..0000000 --- a/networking_arista/ml2/drivers/driver_helpers.py +++ /dev/null @@ -1,148 +0,0 @@ -# Copyright (c) 2016 OpenStack Foundation -# 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. - -from neutron_lib.db import api as db_api -from oslo_log import log -from six import moves - -from neutron.db.models.plugins.ml2 import vlanallocation - -from networking_arista._i18n import _LI -from networking_arista.common import exceptions as arista_exc -from networking_arista.ml2.arista_ml2 import EOS_UNREACHABLE_MSG - -LOG = log.getLogger(__name__) - - -class VlanSyncService(object): - """Sync vlan assignment from CVX into the OpenStack db.""" - - def __init__(self, rpc_wrapper): - self._rpc = rpc_wrapper - self._force_sync = True - self._vlan_assignment_uuid = None - self._assigned_vlans = dict() - - def force_sync(self): - self._force_sync = True - - def _parse_vlan_ranges(self, vlan_pool, return_as_ranges=False): - vlan_ids = set() - if return_as_ranges: - vlan_ids = list() - if not vlan_pool: - return vlan_ids - vlan_ranges = vlan_pool.split(',') - for vlan_range in vlan_ranges: - endpoints = vlan_range.split('-') - if len(endpoints) == 2: - vlan_min = int(endpoints[0]) - vlan_max = int(endpoints[1]) - if return_as_ranges: - vlan_ids.append((vlan_min, vlan_max)) - else: - vlan_ids |= set(moves.range(vlan_min, vlan_max + 1)) - elif len(endpoints) == 1: - single_vlan = int(endpoints[0]) - if return_as_ranges: - vlan_ids.append((single_vlan, single_vlan)) - else: - vlan_ids.add(single_vlan) - return vlan_ids - - def get_network_vlan_ranges(self): - return self._assigned_vlans - - def _sync_required(self): - try: - if not self._force_sync and self._region_in_sync(): - LOG.info(_LI('VLANs are in sync!')) - return False - except arista_exc.AristaRpcError: - LOG.warning(EOS_UNREACHABLE_MSG) - self._force_sync = True - return True - - def _region_in_sync(self): - eos_vlan_assignment_uuid = self._rpc.get_vlan_assignment_uuid() - return (self._vlan_assignment_uuid and - (self._vlan_assignment_uuid['uuid'] == - eos_vlan_assignment_uuid['uuid'])) - - def _set_vlan_assignment_uuid(self): - try: - self._vlan_assignment_uuid = self._rpc.get_vlan_assignment_uuid() - except arista_exc.AristaRpcError: - self._force_sync = True - - def do_synchronize(self): - if not self._sync_required(): - return - - self.synchronize() - self._set_vlan_assignment_uuid() - - def synchronize(self): - LOG.info(_LI('Syncing VLANs with EOS')) - try: - self._rpc.register_with_eos() - vlan_pool = self._rpc.get_vlan_allocation() - except arista_exc.AristaRpcError: - LOG.warning(EOS_UNREACHABLE_MSG) - self._force_sync = True - return - - self._assigned_vlans = { - 'default': self._parse_vlan_ranges(vlan_pool['assignedVlans'], - return_as_ranges=True), - } - - assigned_vlans = ( - self._parse_vlan_ranges(vlan_pool['assignedVlans'])) - available_vlans = frozenset( - self._parse_vlan_ranges(vlan_pool['availableVlans'])) - used_vlans = frozenset( - self._parse_vlan_ranges(vlan_pool['allocatedVlans'])) - - self._force_sync = False - - session = db_api.get_writer_session() - with session.begin(subtransactions=True): - allocs = ( - session.query(vlanallocation.VlanAllocation).with_lockmode( - 'update')) - - for alloc in allocs: - if alloc.physical_network != 'default': - session.delete(alloc) - - try: - assigned_vlans.remove(alloc.vlan_id) - except KeyError: - session.delete(alloc) - continue - - if alloc.allocated and alloc.vlan_id in available_vlans: - alloc.update({"allocated": False}) - elif not alloc.allocated and alloc.vlan_id in used_vlans: - alloc.update({"allocated": True}) - - for vlan_id in sorted(assigned_vlans): - allocated = vlan_id in used_vlans - alloc = vlanallocation.VlanAllocation( - physical_network='default', - vlan_id=vlan_id, - allocated=allocated) - session.add(alloc) diff --git a/networking_arista/ml2/drivers/type_arista_vlan.py b/networking_arista/ml2/drivers/type_arista_vlan.py deleted file mode 100644 index b25eedb..0000000 --- a/networking_arista/ml2/drivers/type_arista_vlan.py +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright (c) 2016 OpenStack Foundation -# 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. - -import threading - -from oslo_config import cfg -from oslo_log import log - -from neutron.plugins.ml2.drivers import type_vlan - -from networking_arista._i18n import _LI -from networking_arista.common import db_lib -from networking_arista.common import exceptions as exc -from networking_arista.ml2 import arista_ml2 -from networking_arista.ml2.drivers import driver_helpers - -LOG = log.getLogger(__name__) -cfg.CONF.import_group('arista_type_driver', 'networking_arista.common.config') - - -class AristaVlanTypeDriver(type_vlan.VlanTypeDriver): - """Manage state for VLAN networks with ML2. - - The VlanTypeDriver implements the 'vlan' network_type. VLAN - network segments provide connectivity between VMs and other - devices using any connected IEEE 802.1Q conformant - physical_network segmented into virtual networks via IEEE 802.1Q - headers. Up to 4094 VLAN network segments can exist on each - available physical_network. - """ - - def __init__(self): - super(AristaVlanTypeDriver, self).__init__() - ndb = db_lib.NeutronNets() - self.rpc = arista_ml2.AristaRPCWrapperEapi(ndb) - self.sync_service = driver_helpers.VlanSyncService(self.rpc) - self.network_vlan_ranges = dict() - self.sync_timeout = cfg.CONF.arista_type_driver['sync_interval'] - - def initialize(self): - self.rpc.check_supported_features() - self.rpc.check_vlan_type_driver_commands() - self._synchronization_thread() - LOG.info(_LI("AristaVlanTypeDriver initialization complete")) - - def _synchronization_thread(self): - self.sync_service.do_synchronize() - self.network_vlan_ranges = self.sync_service.get_network_vlan_ranges() - self.timer = threading.Timer(self.sync_timeout, - self._synchronization_thread) - self.timer.start() - - def allocate_fully_specified_segment(self, session, **raw_segment): - alloc = session.query(self.model).filter_by(**raw_segment).first() - if not alloc: - raise exc.VlanUnavailable(**raw_segment) - return super(AristaVlanTypeDriver, - self).allocate_fully_specified_segment( - session, **raw_segment) diff --git a/networking_arista/ml2/mechanism_arista.py b/networking_arista/ml2/mechanism_arista.py deleted file mode 100644 index 5888498..0000000 --- a/networking_arista/ml2/mechanism_arista.py +++ /dev/null @@ -1,1048 +0,0 @@ -# Copyright (c) 2013 OpenStack Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import threading - -from neutron_lib.api.definitions import portbindings -from neutron_lib import constants as n_const -from neutron_lib.plugins.ml2 import api as driver_api -from neutron_lib import worker -from oslo_config import cfg -from oslo_log import log as logging -from oslo_service import loopingcall -from oslo_utils import excutils - -from neutron.common import constants as neutron_const - -from networking_arista._i18n import _, _LI, _LE -from networking_arista.common import db -from networking_arista.common import db_lib -from networking_arista.common import exceptions as arista_exc -from networking_arista.ml2 import arista_ml2 -from networking_arista.ml2 import sec_group_callback - -LOG = logging.getLogger(__name__) -cfg.CONF.import_group('ml2_arista', 'networking_arista.common.config') - -# Messages -EOS_UNREACHABLE_MSG = _('Unable to reach EOS') -UNABLE_TO_DELETE_PORT_MSG = _('Unable to delete port from EOS') -UNABLE_TO_DELETE_DEVICE_MSG = _('Unable to delete device') - -# Constants -INTERNAL_TENANT_ID = 'INTERNAL-TENANT-ID' -PORT_BINDING_HOST = 'binding:host_id' -MECHANISM_DRV_NAME = 'arista' - - -def pretty_log(tag, obj): - import json - log_data = json.dumps(obj, sort_keys=True, indent=4) - LOG.debug(tag) - LOG.debug(log_data) - - -class AristaSyncWorker(worker.BaseWorker): - def __init__(self, rpc, ndb): - super(AristaSyncWorker, self).__init__(worker_process_count=0) - self.ndb = ndb - self.rpc = rpc - self.sync_service = arista_ml2.SyncService(rpc, ndb) - rpc.sync_service = self.sync_service - self._loop = None - - def start(self): - super(AristaSyncWorker, self).start() - - self._sync_running = True - self._sync_event = threading.Event() - - self._cleanup_db() - # Registering with EOS updates self.rpc.region_updated_time. Clear it - # to force an initial sync - self.rpc.clear_region_updated_time() - - if self._loop is None: - self._loop = loopingcall.FixedIntervalLoopingCall( - self.sync_service.do_synchronize - ) - self._loop.start(interval=cfg.CONF.ml2_arista.sync_interval) - - def stop(self, graceful=False): - if self._loop is not None: - self._loop.stop() - - def wait(self): - if self._loop is not None: - self._loop.wait() - - def reset(self): - self.stop() - self.wait() - self.start() - - def _cleanup_db(self): - """Clean up any unnecessary entries in our DB.""" - - LOG.info('Arista Sync: DB Cleanup') - neutron_nets = self.ndb.get_all_networks() - arista_db_nets = db_lib.get_networks(tenant_id='any') - neutron_net_ids = set() - for net in neutron_nets: - neutron_net_ids.add(net['id']) - - # Remove networks from the Arista DB if the network does not exist in - # Neutron DB - for net_id in set(arista_db_nets.keys()).difference(neutron_net_ids): - tenant_network = arista_db_nets[net_id] - db_lib.forget_network_segment(tenant_network['tenantId'], net_id) - db_lib.forget_all_ports_for_network(net_id) - - -class AristaDriver(driver_api.MechanismDriver): - """Ml2 Mechanism driver for Arista networking hardware. - - Remembers all networks and VMs that are provisioned on Arista Hardware. - Does not send network provisioning request if the network has already been - provisioned before for the given port. - """ - def __init__(self, rpc=None): - - self.ndb = db_lib.NeutronNets() - self.db_nets = db.AristaProvisionedNets() - self.db_vms = db.AristaProvisionedVms() - self.db_tenants = db.AristaProvisionedTenants() - - confg = cfg.CONF.ml2_arista - self.segmentation_type = db_lib.VLAN_SEGMENTATION - self.timer = None - self.managed_physnets = confg['managed_physnets'] - self.manage_fabric = confg['manage_fabric'] - self.eos_sync_lock = threading.Lock() - - self.eapi = None - - if rpc is not None: - LOG.info("Using passed in parameter for RPC") - self.rpc = rpc - self.eapi = rpc - else: - self.eapi = arista_ml2.AristaRPCWrapperEapi(self.ndb) - api_type = confg['api_type'].upper() - if api_type == 'EAPI': - LOG.info("Using EAPI for RPC") - self.rpc = arista_ml2.AristaRPCWrapperEapi(self.ndb) - elif api_type == 'JSON': - LOG.info("Using JSON for RPC") - self.rpc = arista_ml2.AristaRPCWrapperJSON(self.ndb) - else: - msg = "RPC mechanism %s not recognized" % api_type - LOG.error(msg) - raise arista_exc.AristaRpcError(msg=msg) - - def initialize(self): - if self.rpc.check_cvx_availability(): - self.rpc.register_with_eos() - self.rpc.check_supported_features() - - self.sg_handler = sec_group_callback.AristaSecurityGroupHandler(self) - - def get_workers(self): - return [AristaSyncWorker(self.rpc, self.ndb)] - - def create_network_precommit(self, context): - """Remember the tenant, and network information.""" - - network = context.current - segments = context.network_segments - if not self.rpc.hpb_supported(): - # Hierarchical port binding is not supported by CVX, only - # allow VLAN network type. - if(segments and - segments[0][driver_api.NETWORK_TYPE] != n_const.TYPE_VLAN): - return - network_id = network['id'] - tenant_id = network['tenant_id'] or INTERNAL_TENANT_ID - with self.eos_sync_lock: - db_lib.remember_tenant(tenant_id) - for segment in segments: - db_lib.remember_network_segment(tenant_id, - network_id, - segment.get('segmentation_id'), - segment.get('id')) - - def create_network_postcommit(self, context): - """Provision the network on the Arista Hardware.""" - - network = context.current - network_id = network['id'] - network_name = network['name'] - tenant_id = network['tenant_id'] or INTERNAL_TENANT_ID - segments = context.network_segments - shared_net = network['shared'] - with self.eos_sync_lock: - if db_lib.is_network_provisioned(tenant_id, network_id): - try: - network_dict = { - 'network_id': network_id, - 'segments': segments, - 'network_name': network_name, - 'shared': shared_net} - self.rpc.create_network(tenant_id, network_dict) - except arista_exc.AristaRpcError as err: - LOG.error(_LE("create_network_postcommit: Did not create " - "network %(name)s. Reason: %(err)s"), - {'name': network_name, 'err': err}) - else: - LOG.info(_LI('Network %s is not created as it is not found in ' - 'Arista DB'), network_id) - - def update_network_precommit(self, context): - """At the moment we only support network name change - - Any other change in network is not supported at this time. - We do not store the network names, therefore, no DB store - action is performed here. - """ - new_network = context.current - orig_network = context.original - if new_network['name'] != orig_network['name']: - LOG.info(_LI('Network name changed to %s'), new_network['name']) - - def update_network_postcommit(self, context): - """At the moment we only support network name change - - If network name is changed, a new network create request is - sent to the Arista Hardware. - """ - new_network = context.current - orig_network = context.original - if ((new_network['name'] != orig_network['name']) or - (new_network['shared'] != orig_network['shared'])): - network_id = new_network['id'] - network_name = new_network['name'] - tenant_id = new_network['tenant_id'] or INTERNAL_TENANT_ID - shared_net = new_network['shared'] - with self.eos_sync_lock: - if db_lib.is_network_provisioned(tenant_id, network_id): - try: - network_dict = { - 'network_id': network_id, - 'segments': context.network_segments, - 'network_name': network_name, - 'shared': shared_net} - self.rpc.create_network(tenant_id, network_dict) - except arista_exc.AristaRpcError as err: - LOG.error(_LE('update_network_postcommit: Did not ' - 'update network %(name)s. ' - 'Reason: %(err)s'), - {'name': network_name, 'err': err}) - else: - LOG.info(_LI('Network %s is not updated as it is not found' - ' in Arista DB'), network_id) - - def delete_network_precommit(self, context): - """Delete the network information from the DB.""" - network = context.current - network_id = network['id'] - tenant_id = network['tenant_id'] or INTERNAL_TENANT_ID - with self.eos_sync_lock: - if db_lib.is_network_provisioned(tenant_id, network_id): - if db_lib.are_ports_attached_to_network(network_id): - db_lib.forget_all_ports_for_network(network_id) - LOG.info(_LI('Deleting all ports on network %s'), - network_id) - db_lib.forget_network_segment(tenant_id, network_id) - - def delete_network_postcommit(self, context): - """Send network delete request to Arista HW.""" - network = context.current - segments = context.network_segments - if not self.rpc.hpb_supported(): - # Hierarchical port binding is not supported by CVX, only - # send the request if network type is VLAN. - if (segments and - segments[0][driver_api.NETWORK_TYPE] != n_const.TYPE_VLAN): - # If network type is not VLAN, do nothing - return - # No need to pass segments info when calling delete_network as - # HPB is not supported. - segments = [] - network_id = network['id'] - tenant_id = network['tenant_id'] or INTERNAL_TENANT_ID - with self.eos_sync_lock: - - # Succeed deleting network in case EOS is not accessible. - # EOS state will be updated by sync thread once EOS gets - # alive. - try: - self.rpc.delete_network(tenant_id, network_id, segments) - # if necessary, delete tenant as well. - self.delete_tenant(tenant_id) - except arista_exc.AristaRpcError as err: - LOG.error(_LE('delete_network_postcommit: Did not delete ' - 'network %(network_id)s. Reason: %(err)s'), - {'network_id': network_id, 'err': err}) - - def create_port_precommit(self, context): - """Remember the information about a VM and its ports - - A VM information, along with the physical host information - is saved. - """ - - # Returning from here, since the update_port_precommit is performing - # same operation, and also need of port binding information to decide - # whether to react to a port create event which is not available when - # this method is called. - - return - - def _get_physnet_from_link_info(self, port, physnet_info): - - binding_profile = port.get(portbindings.PROFILE) - if not binding_profile: - return - - link_info = binding_profile.get('local_link_information') - if not link_info: - return - - mac_to_hostname = physnet_info.get('mac_to_hostname', {}) - for link in link_info: - if link.get('switch_id') in mac_to_hostname: - physnet = mac_to_hostname.get(link.get('switch_id')) - return self.rpc.mlag_pairs.get(physnet, physnet) - - def _bind_port_to_baremetal(self, context, segment): - - port = context.current - vnic_type = port.get('binding:vnic_type') - if vnic_type != portbindings.VNIC_BAREMETAL: - # We are only interested in binding baremetal ports. - return - - binding_profile = port.get(portbindings.PROFILE) - if not binding_profile: - return - - link_info = binding_profile.get('local_link_information') - if not link_info: - return - - vif_details = { - portbindings.VIF_DETAILS_VLAN: str( - segment[driver_api.SEGMENTATION_ID]) - } - context.set_binding(segment[driver_api.ID], - portbindings.VIF_TYPE_OTHER, - vif_details, - n_const.ACTIVE) - LOG.debug("AristaDriver: bound port info- port ID %(id)s " - "on network %(network)s", - {'id': port['id'], - 'network': context.network.current['id']}) - - def bind_port(self, context): - """Bind port to a network segment. - - Provisioning request to Arista Hardware to plug a host - into appropriate network is done when the port is created - this simply tells the ML2 Plugin that we are binding the port - """ - host_id = context.host - port = context.current - physnet_info = {} - for segment in context.segments_to_bind: - physnet = segment.get(driver_api.PHYSICAL_NETWORK) - if not self._is_in_managed_physnets(physnet): - LOG.debug("bind_port for port %(port)s: physical_network " - "%(physnet)s is not managed by Arista " - "mechanism driver", {'port': port.get('id'), - 'physnet': physnet}) - continue - # If physnet is not set, we need to look it up using hostname - # and topology info - if not physnet: - if not physnet_info: - # We only need to get physnet_info once - physnet_info = self.eapi.get_physical_network(host_id) - if (port.get('binding:vnic_type') == - portbindings.VNIC_BAREMETAL): - # Find physnet using link_information in baremetal case - physnet = self._get_physnet_from_link_info(port, - physnet_info) - else: - physnet = physnet_info.get('physnet') - # If physnet was not found, we cannot bind this port - if not physnet: - LOG.debug("bind_port for port %(port)s: no physical_network " - "found", {'port': port.get('id')}) - continue - if segment[driver_api.NETWORK_TYPE] == n_const.TYPE_VXLAN: - # Check if CVX supports HPB - if not self.rpc.hpb_supported(): - LOG.debug("bind_port: HPB is not supported") - return - - # The physical network is connected to arista switches, - # allocate dynamic segmentation id to bind the port to - # the network that the port belongs to. - try: - next_segment = context.allocate_dynamic_segment( - {'id': context.network.current['id'], - 'network_type': n_const.TYPE_VLAN, - 'physical_network': physnet}) - except Exception as exc: - LOG.error(_LE("bind_port for port %(port)s: Failed to " - "allocate dynamic segment for physnet " - "%(physnet)s. %(exc)s"), - {'port': port.get('id'), 'physnet': physnet, - 'exc': exc}) - return - - LOG.debug("bind_port for port %(port)s: " - "current_segment=%(current_seg)s, " - "next_segment=%(next_seg)s", - {'port': port.get('id'), 'current_seg': segment, - 'next_seg': next_segment}) - context.continue_binding(segment['id'], [next_segment]) - elif port.get('binding:vnic_type') == portbindings.VNIC_BAREMETAL: - # The network_type is vlan, try binding process for baremetal. - self._bind_port_to_baremetal(context, segment) - - def create_port_postcommit(self, context): - """Plug a physical host into a network. - - Send provisioning request to Arista Hardware to plug a host - into appropriate network. - """ - - # Returning from here, since the update_port_postcommit is performing - # same operation, and also need of port binding information to decide - # whether to react to a port create event which is not available when - # this method is called. - - return - - def _supported_device_owner(self, device_owner): - supported_device_owner = [n_const.DEVICE_OWNER_DHCP, - n_const.DEVICE_OWNER_DVR_INTERFACE] - - if any([device_owner in supported_device_owner, - device_owner.startswith('compute') and - device_owner != 'compute:probe', - device_owner.startswith('baremetal')]): - return True - - LOG.debug('Unsupported device owner: %s', device_owner) - - def _network_owner_tenant(self, context, network_id, tenant_id): - tid = tenant_id - if network_id and tenant_id: - context = context._plugin_context - network_owner = self.ndb.get_network_from_net_id(network_id, - context=context) - if network_owner and network_owner[0]['tenant_id'] != tenant_id: - tid = network_owner[0]['tenant_id'] or tenant_id - return tid - - def _is_in_managed_physnets(self, physnet): - # Check if this is a fabric segment - if not physnet: - return self.manage_fabric - # If managed physnet is empty, accept all. - if not self.managed_physnets: - return True - # managed physnet is not empty, find for matching physnet - return any(pn == physnet for pn in self.managed_physnets) - - def _bound_segments(self, context): - """Check if a given port is managed by the mechanism driver. - - It returns bound segment dictionary, if physical network in the bound - segment is included in the managed physical network list. - """ - if not self.managed_physnets: - return [ - binding_level.get(driver_api.BOUND_SEGMENT) - for binding_level in (context.binding_levels or []) - ] - - bound_segments = [] - for binding_level in (context.binding_levels or []): - bound_segment = binding_level.get(driver_api.BOUND_SEGMENT) - if (bound_segment and - self._is_in_managed_physnets( - bound_segment.get(driver_api.PHYSICAL_NETWORK))): - bound_segments.append(bound_segment) - return bound_segments - - def _handle_port_migration_precommit(self, context): - """Handles port migration in precommit - - It updates the port's new host in the DB - """ - orig_port = context.original - orig_host = context.original_host - new_host = context.host - new_port = context.current - port_id = orig_port['id'] - - if new_host and orig_host and new_host != orig_host: - LOG.debug("Handling port migration for: %s " % orig_port) - network_id = orig_port['network_id'] - tenant_id = orig_port['tenant_id'] or INTERNAL_TENANT_ID - # Ensure that we use tenant Id for the network owner - tenant_id = self._network_owner_tenant(context, network_id, - tenant_id) - device_id = new_port['device_id'] - with self.eos_sync_lock: - port_provisioned = db_lib.is_port_provisioned(port_id, - orig_host) - if port_provisioned: - db_lib.update_port(device_id, new_host, port_id, - network_id, tenant_id) - - return True - - def _handle_port_migration_postcommit(self, context): - """Handles port migration in postcommit - - In case of port migration, it removes the port from the original host - and also it release the segment id if no port is attached to the same - segment id that the port is attached to. - """ - orig_port = context.original - orig_host = context.original_host - new_host = context.host - - if new_host and orig_host and new_host != orig_host: - self._try_to_release_dynamic_segment(context, migration=True) - - # Handling migration case. - # 1. The port should be unplugged from network - # 2. If segment_id is provisioned and it not bound to any port it - # should be removed from EOS. - network_id = orig_port['network_id'] - tenant_id = orig_port['tenant_id'] or INTERNAL_TENANT_ID - # Ensure that we use tenant Id for the network owner - tenant_id = self._network_owner_tenant(context, network_id, - tenant_id) - for binding_level in context._original_binding_levels or []: - if self._network_provisioned( - tenant_id, network_id, - segment_id=binding_level.segment_id): - with self.eos_sync_lock: - # Removing the port form original host - self._delete_port(orig_port, orig_host, tenant_id) - - # If segment id is not bound to any port, then - # remove it from EOS - segment = self.ndb.get_segment_by_id( - context._plugin_context, - binding_level.segment_id) - if not segment: - try: - segment_info = [{ - 'id': binding_level.segment_id, - 'network_id': network_id, - }] - LOG.debug("migration_postcommit:" - "deleting segment %s", segment_info) - self.rpc.delete_network_segments(tenant_id, - segment_info) - # Remove the segment from the provisioned - # network DB. - db_lib.forget_network_segment( - tenant_id, network_id, - binding_level.segment_id) - except arista_exc.AristaRpcError: - LOG.info(EOS_UNREACHABLE_MSG) - - return True - - def update_port_precommit(self, context): - """Update the name of a given port. - - At the moment we only support port name change. - Any other change to port is not supported at this time. - We do not store the port names, therefore, no DB store - action is performed here. - """ - new_port = context.current - orig_port = context.original - if new_port['name'] != orig_port['name']: - LOG.info(_LI('Port name changed to %s'), new_port['name']) - device_id = new_port['device_id'] - host = context.host - - pretty_log("update_port_precommit: new", new_port) - pretty_log("update_port_precommit: orig", orig_port) - - if not self._supported_device_owner(new_port['device_owner']): - return - - # Check if it is port migration case - if self._handle_port_migration_precommit(context): - return - - # Check if the port is part of managed physical network - seg_info = self._bound_segments(context) - if not seg_info: - # Ignoring the update as the port is not managed by - # arista mechanism driver. - return - - # device_id and device_owner are set on VM boot - port_id = new_port['id'] - network_id = new_port['network_id'] - tenant_id = new_port['tenant_id'] or INTERNAL_TENANT_ID - # Ensure that we use tenant Id for the network owner - tenant_id = self._network_owner_tenant(context, network_id, tenant_id) - - for seg in seg_info: - if not self._network_provisioned(tenant_id, network_id, - seg[driver_api.SEGMENTATION_ID], - seg[driver_api.ID]): - LOG.info( - _LI("Adding %s to provisioned network database"), seg) - with self.eos_sync_lock: - db_lib.remember_tenant(tenant_id) - db_lib.remember_network_segment( - tenant_id, network_id, - seg[driver_api.SEGMENTATION_ID], - seg[driver_api.ID]) - - with self.eos_sync_lock: - port_down = False - if(new_port['device_owner'] == - n_const.DEVICE_OWNER_DVR_INTERFACE): - # We care about port status only for DVR ports because - # for DVR, a single port exists on multiple hosts. If a port - # is no longer needed on a host then the driver gets a - # port_update notification for that with the - # port status as PORT_STATUS_DOWN. - port_down = context.status == n_const.PORT_STATUS_DOWN - - if host and not port_down: - port_host_filter = None - if(new_port['device_owner'] == - n_const.DEVICE_OWNER_DVR_INTERFACE): - # uniquely identifies a DVR port. Other - # ports are identified by just the port id - port_host_filter = host - - port_provisioned = db_lib.is_port_provisioned( - port_id, port_host_filter) - - if not port_provisioned: - LOG.info("Remembering the port") - # Create a new port in the DB - db_lib.remember_tenant(tenant_id) - db_lib.remember_vm(device_id, host, port_id, - network_id, tenant_id) - else: - if(new_port['device_id'] != orig_port['device_id'] or - context.host != context.original_host or - new_port['network_id'] != orig_port['network_id'] or - new_port['tenant_id'] != orig_port['tenant_id']): - LOG.info("Updating the port") - # Port exists in the DB. Update it - db_lib.update_port(device_id, host, port_id, - network_id, tenant_id) - else: # Unbound or down port does not concern us - orig_host = context.original_host - LOG.info("Forgetting the port on %s" % str(orig_host)) - db_lib.forget_port(port_id, orig_host) - - def _port_updated(self, context): - """Returns true if any port parameters have changed.""" - new_port = context.current - orig_port = context.original - return (new_port['device_id'] != orig_port['device_id'] or - context.host != context.original_host or - new_port['network_id'] != orig_port['network_id'] or - new_port['tenant_id'] != orig_port['tenant_id']) - - def update_port_postcommit(self, context): - """Update the name of a given port in EOS. - - At the moment we only support port name change - Any other change to port is not supported at this time. - """ - port = context.current - orig_port = context.original - - device_id = port['device_id'] - device_owner = port['device_owner'] - host = context.host - is_vm_boot = device_id and device_owner - - if not self._supported_device_owner(device_owner): - return - - vnic_type = port['binding:vnic_type'] - binding_profile = port['binding:profile'] - bindings = [] - if binding_profile: - bindings = binding_profile.get('local_link_information', []) - - port_id = port['id'] - port_name = port['name'] - network_id = port['network_id'] - tenant_id = port['tenant_id'] or INTERNAL_TENANT_ID - # Ensure that we use tenant Id for the network owner - tenant_id = self._network_owner_tenant(context, network_id, tenant_id) - sg = port['security_groups'] - orig_sg = orig_port['security_groups'] - - pretty_log("update_port_postcommit: new", port) - pretty_log("update_port_postcommit: orig", orig_port) - - # Check if it is port migration case - if self._handle_port_migration_postcommit(context): - # Return from here as port migration is already handled. - return - - seg_info = self._bound_segments(context) - if not seg_info: - LOG.debug("Ignoring the update as the port is not managed by " - "Arista switches.") - return - - with self.eos_sync_lock: - hostname = self._host_name(host) - port_host_filter = None - if(port['device_owner'] == - n_const.DEVICE_OWNER_DVR_INTERFACE): - # uniquely identifies a DVR port. Other - # ports are identified by just the port id - port_host_filter = host - - port_provisioned = db_lib.is_port_provisioned(port_id, - port_host_filter) - # If network does not exist under this tenant, - # it may be a shared network. Get shared network owner Id - net_provisioned = self._network_provisioned( - tenant_id, network_id) - for seg in seg_info: - if not self._network_provisioned( - tenant_id, network_id, - segmentation_id=seg[driver_api.SEGMENTATION_ID]): - net_provisioned = False - segments = [] - if net_provisioned and self.rpc.hpb_supported(): - segments = seg_info - all_segments = self.ndb.get_all_network_segments( - network_id, context=context._plugin_context) - try: - self.rpc.create_network_segments( - tenant_id, network_id, - context.network.current['name'], all_segments) - except arista_exc.AristaRpcError: - with excutils.save_and_reraise_exception(): - LOG.error(_LE("Failed to create network segments")) - - try: - orig_host = context.original_host - port_down = False - if(port['device_owner'] == n_const.DEVICE_OWNER_DVR_INTERFACE): - # We care about port status only for DVR ports - port_down = context.status == n_const.PORT_STATUS_DOWN - - if orig_host and (port_down or host != orig_host or - device_id == neutron_const.DEVICE_ID_RESERVED_DHCP_PORT): - LOG.info("Deleting the port %s" % str(orig_port)) - # The port moved to a different host or the VM - # connected to the port was deleted or its in DOWN - # state. So delete the old port on the old host. - self._delete_port(orig_port, orig_host, tenant_id) - if(port_provisioned and net_provisioned and hostname and - is_vm_boot and not port_down and - device_id != neutron_const.DEVICE_ID_RESERVED_DHCP_PORT): - LOG.info(_LI("Port plugged into network")) - # Plug port into the network only if it exists in the db - # and is bound to a host and the port is up. - self.rpc.plug_port_into_network(device_id, - hostname, - port_id, - network_id, - tenant_id, - port_name, - device_owner, - sg, orig_sg, - vnic_type, - segments=segments, - switch_bindings=bindings) - else: - LOG.info(_LI("Port not plugged into network")) - except arista_exc.AristaRpcError as err: - LOG.error(_LE('update_port_postcommit: Did not update ' - 'port %(port_id)s. Reason: %(err)s'), - {'port_id': port_id, 'err': err}) - - def delete_port_precommit(self, context): - """Delete information about a VM and host from the DB.""" - port = context.current - - pretty_log("delete_port_precommit:", port) - - port_id = port['id'] - host_id = context.host - with self.eos_sync_lock: - if db_lib.is_port_provisioned(port_id, host_id): - db_lib.forget_port(port_id, host_id) - - def delete_port_postcommit(self, context): - """Unplug a physical host from a network. - - Send provisioning request to Arista Hardware to unplug a host - from appropriate network. - """ - port = context.current - host = context.host - network_id = port['network_id'] - - tenant_id = port['tenant_id'] or INTERNAL_TENANT_ID - # Ensure that we use tenant Id for the network owner - tenant_id = self._network_owner_tenant(context, network_id, tenant_id) - - pretty_log("delete_port_postcommit:", port) - - # If this port is the last one using dynamic segmentation id, - # and the segmentation id was allocated by this driver, it needs - # to be released. - self._try_to_release_dynamic_segment(context) - - with self.eos_sync_lock: - try: - self._delete_port(port, host, tenant_id) - self._delete_segment(context, tenant_id) - except arista_exc.AristaRpcError: - # Can't do much if deleting a port failed. - # Log a warning and continue. - LOG.warning(UNABLE_TO_DELETE_PORT_MSG) - - def _delete_port(self, port, host, tenant_id): - """Deletes the port from EOS. - - param port: Port which is to be deleted - param host: The host on which the port existed - param tenant_id: The tenant to which the port belongs to. Some times - the tenant id in the port dict is not present (as in - the case of HA router). - """ - device_id = port['device_id'] - port_id = port['id'] - network_id = port['network_id'] - device_owner = port['device_owner'] - - if not self._supported_device_owner(device_owner): - return - - vnic_type = port['binding:vnic_type'] - binding_profile = port['binding:profile'] - switch_bindings = [] - if binding_profile: - switch_bindings = binding_profile.get('local_link_information', []) - sg = port['security_groups'] - - if not device_id or not host: - LOG.warning(UNABLE_TO_DELETE_DEVICE_MSG) - return - - try: - if not self._network_provisioned(tenant_id, network_id): - # If we do not have network associated with this, ignore it - return - hostname = self._host_name(host) - self.rpc.unplug_port_from_network(device_id, device_owner, - hostname, port_id, network_id, - tenant_id, sg, vnic_type, - switch_bindings=switch_bindings) - self.rpc.remove_security_group(sg, switch_bindings) - - # if necessary, delete tenant as well. - self.delete_tenant(tenant_id) - except arista_exc.AristaRpcError: - LOG.info(EOS_UNREACHABLE_MSG) - - def _delete_segment(self, context, tenant_id): - """Deletes a dynamic network segment from EOS. - - param context: The port context - param tenant_id: The tenant which the port belongs to - """ - - if not self.rpc.hpb_supported(): - # Returning as HPB not supported by CVX - return - - port = context.current - network_id = port.get('network_id') - - if not context._binding_levels: - return - for binding_level in context._binding_levels: - LOG.debug("deleting segment %s", binding_level.segment_id) - if self._network_provisioned(tenant_id, network_id, - segment_id=binding_level.segment_id): - segment = self.ndb.get_segment_by_id( - context._plugin_context, binding_level.segment_id) - if not segment: - # The segment is already released. Delete it from EOS - LOG.debug("Deleting segment %s", binding_level.segment_id) - try: - segment_info = { - 'id': binding_level.segment_id, - 'network_id': network_id, - } - self.rpc.delete_network_segments(tenant_id, - [segment_info]) - # Remove the segment from the provisioned network DB. - db_lib.forget_network_segment( - tenant_id, network_id, binding_level.segment_id) - except arista_exc.AristaRpcError: - LOG.info(EOS_UNREACHABLE_MSG) - else: - LOG.debug("Cannot delete segment_id %(segid)s " - "segment is %(seg)s", - {'segid': binding_level.segment_id, - 'seg': segment}) - - def _try_to_release_dynamic_segment(self, context, migration=False): - """Release dynamic segment allocated by the driver - - If this port is the last port using the segmentation id allocated - by the driver, it should be released - """ - host = context.original_host if migration else context.host - - physnet_info = self.eapi.get_physical_network(host) - physnet = physnet_info.get('physnet') - if not physnet: - return - - binding_levels = context.binding_levels - LOG.debug("_try_release_dynamic_segment: " - "binding_levels=%(bl)s", {'bl': binding_levels}) - if not binding_levels: - return - - segment_id = None - bound_drivers = [] - for binding_level in binding_levels: - bound_segment = binding_level.get(driver_api.BOUND_SEGMENT) - driver = binding_level.get(driver_api.BOUND_DRIVER) - bound_drivers.append(driver) - if (bound_segment and - bound_segment.get('physical_network') == physnet and - bound_segment.get('network_type') == n_const.TYPE_VLAN): - segment_id = bound_segment.get('id') - break - - # If the segment id is found and it is bound by this driver, and also - # the segment id is not bound to any other port, release the segment. - # When Arista driver participate in port binding by allocating dynamic - # segment and then calling continue_binding, the driver should the - # second last driver in the bound drivers list. - if (segment_id and bound_drivers[-2:-1] == [MECHANISM_DRV_NAME]): - filters = {'segment_id': segment_id} - result = db_lib.get_port_binding_level(filters) - LOG.debug("Looking for entry with filters=%(filters)s " - "result=%(result)s ", {'filters': filters, - 'result': result}) - if not result: - # The requested segment_id does not exist in the port binding - # database. Release the dynamic segment. - context.release_dynamic_segment(segment_id) - LOG.debug("Released dynamic segment %(seg)s allocated " - "by %(drv)s", {'seg': segment_id, - 'drv': bound_drivers[-2]}) - - def delete_tenant(self, tenant_id): - """delete a tenant from DB. - - A tenant is deleted only if there is no network or VM configured - configured for this tenant. - """ - objects_for_tenant = (db_lib.num_nets_provisioned(tenant_id) + - db_lib.num_vms_provisioned(tenant_id)) - if not objects_for_tenant: - db_lib.forget_tenant(tenant_id) - try: - self.rpc.delete_tenant(tenant_id) - except arista_exc.AristaRpcError: - with excutils.save_and_reraise_exception(): - LOG.info(EOS_UNREACHABLE_MSG) - - def _host_name(self, hostname): - fqdns_used = cfg.CONF.ml2_arista['use_fqdn'] - return hostname if fqdns_used else hostname.split('.')[0] - - def _network_provisioned(self, tenant_id, network_id, - segmentation_id=None, segment_id=None): - # If network does not exist under this tenant, - # it may be a shared network. - - return ( - db_lib.is_network_provisioned(tenant_id, network_id, - segmentation_id, segment_id) or - self.ndb.get_shared_network_owner_id(network_id) - ) - - def create_security_group(self, sg): - try: - self.rpc.create_acl(sg) - except Exception: - msg = (_('Failed to create ACL on EOS %s') % sg) - LOG.exception(msg) - raise arista_exc.AristaSecurityGroupError(msg=msg) - - def delete_security_group(self, sg): - try: - self.rpc.delete_acl(sg) - except Exception: - msg = (_('Failed to create ACL on EOS %s') % sg) - LOG.exception(msg) - raise arista_exc.AristaSecurityGroupError(msg=msg) - - def update_security_group(self, sg): - try: - self.rpc.create_acl(sg) - except Exception: - msg = (_('Failed to create ACL on EOS %s') % sg) - LOG.exception(msg) - raise arista_exc.AristaSecurityGroupError(msg=msg) - - def create_security_group_rule(self, sgr): - try: - self.rpc.create_acl_rule(sgr) - except Exception: - msg = (_('Failed to create ACL rule on EOS %s') % sgr) - LOG.exception(msg) - raise arista_exc.AristaSecurityGroupError(msg=msg) - - def delete_security_group_rule(self, sgr_id): - if sgr_id: - sgr = self.ndb.get_security_group_rule(sgr_id) - if sgr: - try: - self.rpc.delete_acl_rule(sgr) - except Exception: - msg = (_('Failed to delete ACL rule on EOS %s') % sgr) - LOG.exception(msg) - raise arista_exc.AristaSecurityGroupError(msg=msg) diff --git a/networking_arista/ml2/sec_group_callback.py b/networking_arista/ml2/sec_group_callback.py deleted file mode 100644 index 25241e8..0000000 --- a/networking_arista/ml2/sec_group_callback.py +++ /dev/null @@ -1,117 +0,0 @@ -# Copyright (c) 2016 OpenStack Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from neutron_lib.callbacks import events -from neutron_lib.callbacks import registry -from neutron_lib.callbacks import resources -from oslo_log import helpers as log_helpers -from oslo_log import log as logging -from oslo_utils import excutils - -from networking_arista._i18n import _LE - -LOG = logging.getLogger(__name__) - - -class AristaSecurityGroupHandler(object): - """Security Group Handler for Arista networking hardware. - - Registers for the notification of security group updates. - Once a notification is recieved, it takes appropriate actions by updating - Arista hardware appropriately. - """ - def __init__(self, client): - self.client = client - self.subscribe() - - @log_helpers.log_method_call - def create_security_group(self, resource, event, trigger, **kwargs): - sg = kwargs.get('security_group') - try: - self.client.create_security_group(sg) - except Exception as e: - with excutils.save_and_reraise_exception(): - LOG.error(_LE("Failed to create a security group %(sg_id)s " - "in Arista Driver: %(err)s"), - {"sg_id": sg["id"], "err": e}) - try: - self.client.delete_security_group(sg) - except Exception: - LOG.exception(_LE("Failed to delete security group %s"), - sg['id']) - - @log_helpers.log_method_call - def delete_security_group(self, resource, event, trigger, **kwargs): - sg = kwargs.get('security_group') - try: - self.client.delete_security_group(sg) - except Exception as e: - LOG.error(_LE("Failed to delete security group %(sg_id)s " - "in Arista Driver: %(err)s"), - {"sg_id": sg["id"], "err": e}) - - @log_helpers.log_method_call - def update_security_group(self, resource, event, trigger, **kwargs): - sg = kwargs.get('security_group') - try: - self.client.update_security_group(sg) - except Exception as e: - LOG.error(_LE("Failed to update security group %(sg_id)s " - "in Arista Driver: %(err)s"), - {"sg_id": sg["id"], "err": e}) - - @log_helpers.log_method_call - def create_security_group_rule(self, resource, event, trigger, **kwargs): - sgr = kwargs.get('security_group_rule') - try: - self.client.create_security_group_rule(sgr) - except Exception as e: - with excutils.save_and_reraise_exception(): - LOG.error(_LE("Failed to create a security group %(sgr_id)s " - "rule in Arista Driver: %(err)s"), - {"sgr_id": sgr["id"], "err": e}) - try: - self.client.delete_security_group_rule(sgr) - except Exception: - LOG.exception(_LE("Failed to delete security group " - "rule %s"), sgr['id']) - - @log_helpers.log_method_call - def delete_security_group_rule(self, resource, event, trigger, **kwargs): - sgr_id = kwargs.get('security_group_rule_id') - try: - self.client.delete_security_group_rule(sgr_id) - except Exception as e: - LOG.error(_LE("Failed to delete security group %(sgr_id)s " - "rule in Arista Driver: %(err)s"), - {"sgr_id": sgr_id, "err": e}) - - def subscribe(self): - # Subscribe to the events related to security groups and rules - registry.subscribe( - self.create_security_group, resources.SECURITY_GROUP, - events.AFTER_CREATE) - registry.subscribe( - self.update_security_group, resources.SECURITY_GROUP, - events.AFTER_UPDATE) - registry.subscribe( - self.delete_security_group, resources.SECURITY_GROUP, - events.BEFORE_DELETE) - registry.subscribe( - self.create_security_group_rule, resources.SECURITY_GROUP_RULE, - events.AFTER_CREATE) - registry.subscribe( - self.delete_security_group_rule, resources.SECURITY_GROUP_RULE, - events.BEFORE_DELETE) diff --git a/networking_arista/tests/__init__.py b/networking_arista/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/networking_arista/tests/base.py b/networking_arista/tests/base.py deleted file mode 100644 index 1c30cdb..0000000 --- a/networking_arista/tests/base.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright 2010-2011 OpenStack Foundation -# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from oslotest import base - - -class TestCase(base.BaseTestCase): - - """Test case base class for all unit tests.""" diff --git a/networking_arista/tests/test_networking_arista.py b/networking_arista/tests/test_networking_arista.py deleted file mode 100644 index effffaa..0000000 --- a/networking_arista/tests/test_networking_arista.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- - -# 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. - -""" -test_networking_arista ----------------------------------- - -Tests for `networking_arista` module. -""" - -from networking_arista.tests import base - - -class TestNetworking_arista(base.TestCase): - - def test_something(self): - pass diff --git a/networking_arista/tests/unit/__init__.py b/networking_arista/tests/unit/__init__.py deleted file mode 100644 index faed26a..0000000 --- a/networking_arista/tests/unit/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright 2011 OpenStack Foundation. -# 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. - -from oslo_config import cfg - - -cfg.CONF.use_stderr = False diff --git a/networking_arista/tests/unit/common/__init__.py b/networking_arista/tests/unit/common/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/networking_arista/tests/unit/common/test_api.py b/networking_arista/tests/unit/common/test_api.py deleted file mode 100644 index 42bfa80..0000000 --- a/networking_arista/tests/unit/common/test_api.py +++ /dev/null @@ -1,257 +0,0 @@ -# Copyright (c) 2017 Arista Networks, Inc -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import mock -import requests -from requests import exceptions as requests_exc -import testtools - -from networking_arista.common import api - - -class TestEAPIClientInit(testtools.TestCase): - def test_basic_init(self): - host_ip = '10.20.30.40' - client = api.EAPIClient(host_ip) - self.assertEqual(client.host, host_ip) - self.assertEqual(client.url, 'https://10.20.30.40/command-api') - self.assertDictContainsSubset( - {'Content-Type': 'application/json', 'Accept': 'application/json'}, - client.session.headers - ) - - def test_init_enable_verify(self): - client = api.EAPIClient('10.0.0.1', verify=True) - self.assertTrue(client.session.verify) - - def test_init_auth(self): - client = api.EAPIClient('10.0.0.1', username='user', password='pass') - self.assertEqual(client.session.auth, ('user', 'pass')) - - def test_init_timeout(self): - client = api.EAPIClient('10.0.0.1', timeout=99) - self.assertEqual(client.timeout, 99) - - def test_make_url(self): - url = api.EAPIClient._make_url('1.2.3.4') - self.assertEqual(url, 'https://1.2.3.4/command-api') - - def test_make_url_http(self): - url = api.EAPIClient._make_url('5.6.7.8', 'http') - self.assertEqual(url, 'http://5.6.7.8/command-api') - - -class TestEAPIClientExecute(testtools.TestCase): - def setUp(self): - super(TestEAPIClientExecute, self).setUp() - - mock.patch('requests.Session.post').start() - self.mock_log = mock.patch.object(api, 'LOG').start() - self.mock_json_dumps = mock.patch.object(api.json, 'dumps').start() - - self.addCleanup(mock.patch.stopall) - - self.client = api.EAPIClient('10.0.0.1', timeout=99) - - def _test_execute_helper(self, commands, commands_to_log=None): - expected_data = { - 'id': 'Networking Arista Driver', - 'method': 'runCmds', - 'jsonrpc': '2.0', - 'params': { - 'timestamps': False, - 'format': 'json', - 'version': 1, - 'cmds': commands - } - } - - self.client.session.post.assert_called_once_with( - 'https://10.0.0.1/command-api', - data=self.mock_json_dumps.return_value, - timeout=99 - ) - - self.mock_log.info.assert_has_calls( - [ - mock.call( - mock.ANY, - { - 'ip': '10.0.0.1', - 'data': self.mock_json_dumps.return_value - } - ) - ] - ) - - log_data = dict(expected_data) - log_data['params'] = dict(expected_data['params']) - log_data['params']['cmds'] = commands_to_log or commands - - self.mock_json_dumps.assert_has_calls( - [ - mock.call(log_data), - mock.call(expected_data) - ] - ) - - def test_command_prep(self): - commands = ['enable'] - self.client.execute(commands) - self._test_execute_helper(commands) - - def test_commands_to_log(self): - commands = ['config', 'secret'] - commands_to_log = ['config', '******'] - self.client.execute(commands, commands_to_log) - self._test_execute_helper(commands, commands_to_log) - - def _test_execute_error_helper(self, raise_exception, expected_exception, - warning_has_params=False): - commands = ['config'] - - self.client.session.post.side_effect = raise_exception - - self.assertRaises( - expected_exception, - self.client.execute, - commands - ) - - self._test_execute_helper(commands) - - if warning_has_params: - args = (mock.ANY, mock.ANY) - else: - args = (mock.ANY,) - self.mock_log.warning.assert_called_once_with(*args) - - def test_request_connection_error(self): - self._test_execute_error_helper( - requests_exc.ConnectionError, - api.arista_exc.AristaRpcError - ) - - def test_request_connect_timeout(self): - self._test_execute_error_helper( - requests_exc.ConnectTimeout, - api.arista_exc.AristaRpcError - ) - - def test_request_timeout(self): - self._test_execute_error_helper( - requests_exc.Timeout, - api.arista_exc.AristaRpcError - ) - - def test_request_connect_InvalidURL(self): - self._test_execute_error_helper( - requests_exc.InvalidURL, - api.arista_exc.AristaRpcError - ) - - def test_request_other_exception(self): - class OtherException(Exception): - pass - - self._test_execute_error_helper( - OtherException, - OtherException, - warning_has_params=True - ) - - def _test_response_helper(self, response_data): - mock_response = mock.MagicMock(requests.Response) - mock_response.json.return_value = response_data - self.client.session.post.return_value = mock_response - - def test_response_success(self): - mock_response = mock.MagicMock(requests.Response) - mock_response.json.return_value = {'result': mock.sentinel} - self.client.session.post.return_value = mock_response - - retval = self.client.execute(['enable']) - self.assertEqual(retval, mock.sentinel) - - def test_response_json_error(self): - mock_response = mock.MagicMock(requests.Response) - mock_response.json.side_effect = ValueError - self.client.session.post.return_value = mock_response - - retval = self.client.execute(['enable']) - self.assertIsNone(retval) - self.mock_log.info.assert_has_calls([mock.call(mock.ANY)]) - - def _test_response_format_error_helper(self, bad_response): - mock_response = mock.MagicMock(requests.Response) - mock_response.json.return_value = bad_response - self.client.session.post.return_value = mock_response - - self.assertRaises( - api.arista_exc.AristaRpcError, - self.client.execute, - ['enable'] - ) - self.mock_log.info.assert_has_calls([mock.call(mock.ANY)]) - - def test_response_format_error(self): - self._test_response_format_error_helper({}) - - def test_response_unknown_error_code(self): - self._test_response_format_error_helper( - {'error': {'code': 999}} - ) - - def test_response_known_error_code(self): - self._test_response_format_error_helper( - {'error': {'code': 1002, 'data': []}} - ) - - def test_response_known_error_code_data_is_not_dict(self): - self._test_response_format_error_helper( - {'error': {'code': 1002, 'data': ['some text']}} - ) - - def test_response_not_cvx_leader(self): - mock_response = mock.MagicMock(requests.Response) - mock_response.json.return_value = { - 'error': { - 'code': 1002, - 'data': [{'errors': [api.ERR_CVX_NOT_LEADER]}] - } - } - self.client.session.post.return_value = mock_response - - retval = self.client.execute(['enable']) - self.assertIsNone(retval) - - def test_response_other_exception(self): - class OtherException(Exception): - pass - - mock_response = mock.MagicMock(requests.Response) - mock_response.json.return_value = 'text' - self.client.session.post.return_value = mock_response - - self.assertRaises( - TypeError, - self.client.execute, - ['enable'] - ) - self.mock_log.warning.assert_has_calls( - [ - mock.call(mock.ANY, {'error': mock.ANY}) - ] - ) diff --git a/networking_arista/tests/unit/l3Plugin/__init__.py b/networking_arista/tests/unit/l3Plugin/__init__.py deleted file mode 100644 index faed26a..0000000 --- a/networking_arista/tests/unit/l3Plugin/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright 2011 OpenStack Foundation. -# 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. - -from oslo_config import cfg - - -cfg.CONF.use_stderr = False diff --git a/networking_arista/tests/unit/l3Plugin/test_arista_l3_driver.py b/networking_arista/tests/unit/l3Plugin/test_arista_l3_driver.py deleted file mode 100644 index 8353dbd..0000000 --- a/networking_arista/tests/unit/l3Plugin/test_arista_l3_driver.py +++ /dev/null @@ -1,456 +0,0 @@ -# Copyright (c) 2013 OpenStack Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -import mock -from oslo_config import cfg - -from neutron.tests import base - -from networking_arista.l3Plugin import arista_l3_driver as arista - - -def setup_arista_config(value='', vrf=False, mlag=False): - cfg.CONF.set_override('primary_l3_host', value, "l3_arista") - cfg.CONF.set_override('primary_l3_host_username', value, "l3_arista") - if vrf: - cfg.CONF.set_override('use_vrf', vrf, "l3_arista") - if mlag: - cfg.CONF.set_override('secondary_l3_host', value, "l3_arista") - cfg.CONF.set_override('mlag_config', mlag, "l3_arista") - - -class AristaL3DriverTestCasesDefaultVrf(base.BaseTestCase): - """Test cases to test the RPC between Arista Driver and EOS. - - Tests all methods used to send commands between Arista L3 Driver and EOS - to program routing functions in Default VRF. - """ - - def setUp(self): - super(AristaL3DriverTestCasesDefaultVrf, self).setUp() - setup_arista_config('value') - self.drv = arista.AristaL3Driver() - self.drv._servers = [] - self.drv._servers.append(mock.MagicMock()) - - def test_no_exception_on_correct_configuration(self): - self.assertIsNotNone(self.drv) - - def test_create_router_on_eos(self): - router_name = 'test-router-1' - route_domain = '123:123' - - self.drv.create_router_on_eos(router_name, route_domain, - self.drv._servers[0]) - cmds = ['enable', 'configure', 'exit'] - - self.drv._servers[0].execute.assert_called_once_with(cmds) - - def test_delete_router_from_eos(self): - router_name = 'test-router-1' - - self.drv.delete_router_from_eos(router_name, self.drv._servers[0]) - cmds = ['enable', 'configure', 'exit'] - - self.drv._servers[0].execute.assert_called_once_with(cmds) - - def test_add_interface_to_router_on_eos(self): - router_name = 'test-router-1' - segment_id = '123' - router_ip = '10.10.10.10' - gw_ip = '10.10.10.1' - mask = '255.255.255.0' - - self.drv.add_interface_to_router(segment_id, router_name, gw_ip, - router_ip, mask, self.drv._servers[0]) - cmds = ['enable', 'configure', 'ip routing', - 'vlan %s' % segment_id, 'exit', - 'interface vlan %s' % segment_id, - 'ip address %s/%s' % (gw_ip, mask), 'exit'] - - self.drv._servers[0].execute.assert_called_once_with(cmds) - - def test_delete_interface_from_router_on_eos(self): - router_name = 'test-router-1' - segment_id = '123' - - self.drv.delete_interface_from_router(segment_id, router_name, - self.drv._servers[0]) - cmds = ['enable', 'configure', 'no interface vlan %s' % segment_id, - 'exit'] - - self.drv._servers[0].execute.assert_called_once_with(cmds) - - -class AristaL3DriverTestCasesUsingVRFs(base.BaseTestCase): - """Test cases to test the RPC between Arista Driver and EOS. - - Tests all methods used to send commands between Arista L3 Driver and EOS - to program routing functions using multiple VRFs. - Note that the configuration commands are different when VRFs are used. - """ - - def setUp(self): - super(AristaL3DriverTestCasesUsingVRFs, self).setUp() - setup_arista_config('value', vrf=True) - self.drv = arista.AristaL3Driver() - self.drv._servers = [] - self.drv._servers.append(mock.MagicMock()) - - def test_no_exception_on_correct_configuration(self): - self.assertIsNotNone(self.drv) - - def test_create_router_on_eos(self): - max_vrfs = 5 - routers = ['testRouter-%s' % n for n in range(max_vrfs)] - domains = ['10%s' % n for n in range(max_vrfs)] - - for (r, d) in zip(routers, domains): - self.drv.create_router_on_eos(r, d, self.drv._servers[0]) - - cmds = ['enable', 'configure', - 'vrf definition %s' % r, - 'rd %(rd)s:%(rd)s' % {'rd': d}, 'exit', 'exit'] - - self.drv._servers[0].execute.assert_called_with(cmds) - - def test_delete_router_from_eos(self): - max_vrfs = 5 - routers = ['testRouter-%s' % n for n in range(max_vrfs)] - - for r in routers: - self.drv.delete_router_from_eos(r, self.drv._servers[0]) - cmds = ['enable', 'configure', 'no vrf definition %s' % r, - 'exit'] - - self.drv._servers[0].execute.assert_called_with(cmds) - - def test_add_interface_to_router_on_eos(self): - router_name = 'test-router-1' - segment_id = '123' - router_ip = '10.10.10.10' - gw_ip = '10.10.10.1' - mask = '255.255.255.0' - - self.drv.add_interface_to_router(segment_id, router_name, gw_ip, - router_ip, mask, self.drv._servers[0]) - cmds = ['enable', 'configure', - 'ip routing vrf %s' % router_name, - 'vlan %s' % segment_id, 'exit', - 'interface vlan %s' % segment_id, - 'vrf forwarding %s' % router_name, - 'ip address %s/%s' % (gw_ip, mask), 'exit'] - - self.drv._servers[0].execute.assert_called_once_with(cmds) - - def test_delete_interface_from_router_on_eos(self): - router_name = 'test-router-1' - segment_id = '123' - - self.drv.delete_interface_from_router(segment_id, router_name, - self.drv._servers[0]) - cmds = ['enable', 'configure', 'no interface vlan %s' % segment_id, - 'exit'] - - self.drv._servers[0].execute.assert_called_once_with(cmds) - - -class AristaL3DriverTestCasesMlagConfig(base.BaseTestCase): - """Test cases to test the RPC between Arista Driver and EOS. - - Tests all methods used to send commands between Arista L3 Driver and EOS - to program routing functions in Default VRF using MLAG configuration. - MLAG configuration means that the commands will be sent to both - primary and secondary Arista Switches. - """ - - def setUp(self): - super(AristaL3DriverTestCasesMlagConfig, self).setUp() - setup_arista_config('value', mlag=True) - self.drv = arista.AristaL3Driver() - self.drv._servers = [] - self.drv._servers.append(mock.MagicMock()) - self.drv._servers.append(mock.MagicMock()) - - def test_no_exception_on_correct_configuration(self): - self.assertIsNotNone(self.drv) - - def test_create_router_on_eos(self): - router_name = 'test-router-1' - route_domain = '123:123' - router_mac = '00:11:22:33:44:55' - - for s in self.drv._servers: - self.drv.create_router_on_eos(router_name, route_domain, s) - cmds = ['enable', 'configure', - 'ip virtual-router mac-address %s' % router_mac, 'exit'] - - s.execute.assert_called_with(cmds) - - def test_delete_router_from_eos(self): - router_name = 'test-router-1' - - for s in self.drv._servers: - self.drv.delete_router_from_eos(router_name, s) - cmds = ['enable', 'configure', 'exit'] - - s.execute.assert_called_once_with(cmds) - - def test_add_interface_to_router_on_eos(self): - router_name = 'test-router-1' - segment_id = '123' - router_ip = '10.10.10.10' - gw_ip = '10.10.10.1' - mask = '255.255.255.0' - - for s in self.drv._servers: - self.drv.add_interface_to_router(segment_id, router_name, gw_ip, - router_ip, mask, s) - cmds = ['enable', 'configure', 'ip routing', - 'vlan %s' % segment_id, 'exit', - 'interface vlan %s' % segment_id, - 'ip address %s' % router_ip, - 'ip virtual-router address %s' % gw_ip, 'exit'] - - s.execute.assert_called_once_with(cmds) - - def test_delete_interface_from_router_on_eos(self): - router_name = 'test-router-1' - segment_id = '123' - - for s in self.drv._servers: - self.drv.delete_interface_from_router(segment_id, router_name, s) - - cmds = ['enable', 'configure', 'no interface vlan %s' % segment_id, - 'exit'] - - s.execute.assert_called_once_with(cmds) - - -class AristaL3DriverTestCases_v4(base.BaseTestCase): - """Test cases to test the RPC between Arista Driver and EOS. - - Tests all methods used to send commands between Arista L3 Driver and EOS - to program routing functions in Default VRF using IPv4. - """ - - def setUp(self): - super(AristaL3DriverTestCases_v4, self).setUp() - setup_arista_config('value') - self.drv = arista.AristaL3Driver() - self.drv._servers = [] - self.drv._servers.append(mock.MagicMock()) - - def test_no_exception_on_correct_configuration(self): - self.assertIsNotNone(self.drv) - - def test_add_v4_interface_to_router(self): - gateway_ip = '10.10.10.1' - cidrs = ['10.10.10.0/24', '10.11.11.0/24'] - - # Add couple of IPv4 subnets to router - for cidr in cidrs: - router = {'name': 'test-router-1', - 'tenant_id': 'ten-a', - 'seg_id': '123', - 'cidr': "%s" % cidr, - 'gip': "%s" % gateway_ip, - 'ip_version': 4} - - self.assertFalse(self.drv.add_router_interface(None, router)) - - def test_delete_v4_interface_from_router(self): - gateway_ip = '10.10.10.1' - cidrs = ['10.10.10.0/24', '10.11.11.0/24'] - - # remove couple of IPv4 subnets from router - for cidr in cidrs: - router = {'name': 'test-router-1', - 'tenant_id': 'ten-a', - 'seg_id': '123', - 'cidr': "%s" % cidr, - 'gip': "%s" % gateway_ip, - 'ip_version': 4} - - self.assertFalse(self.drv.remove_router_interface(None, router)) - - -class AristaL3DriverTestCases_v6(base.BaseTestCase): - """Test cases to test the RPC between Arista Driver and EOS. - - Tests all methods used to send commands between Arista L3 Driver and EOS - to program routing functions in Default VRF using IPv6. - """ - - def setUp(self): - super(AristaL3DriverTestCases_v6, self).setUp() - setup_arista_config('value') - self.drv = arista.AristaL3Driver() - self.drv._servers = [] - self.drv._servers.append(mock.MagicMock()) - - def test_no_exception_on_correct_configuration(self): - self.assertIsNotNone(self.drv) - - def test_add_v6_interface_to_router(self): - gateway_ip = '3FFE::1' - cidrs = ['3FFE::/16', '2001::/16'] - - # Add couple of IPv6 subnets to router - for cidr in cidrs: - router = {'name': 'test-router-1', - 'tenant_id': 'ten-a', - 'seg_id': '123', - 'cidr': "%s" % cidr, - 'gip': "%s" % gateway_ip, - 'ip_version': 6} - - self.assertFalse(self.drv.add_router_interface(None, router)) - - def test_delete_v6_interface_from_router(self): - gateway_ip = '3FFE::1' - cidrs = ['3FFE::/16', '2001::/16'] - - # remove couple of IPv6 subnets from router - for cidr in cidrs: - router = {'name': 'test-router-1', - 'tenant_id': 'ten-a', - 'seg_id': '123', - 'cidr': "%s" % cidr, - 'gip': "%s" % gateway_ip, - 'ip_version': 6} - - self.assertFalse(self.drv.remove_router_interface(None, router)) - - -class AristaL3DriverTestCases_MLAG_v6(base.BaseTestCase): - """Test cases to test the RPC between Arista Driver and EOS. - - Tests all methods used to send commands between Arista L3 Driver and EOS - to program routing functions in Default VRF on MLAG'ed switches using IPv6. - """ - - def setUp(self): - super(AristaL3DriverTestCases_MLAG_v6, self).setUp() - setup_arista_config('value', mlag=True) - self.drv = arista.AristaL3Driver() - self.drv._servers = [] - self.drv._servers.append(mock.MagicMock()) - self.drv._servers.append(mock.MagicMock()) - - def test_no_exception_on_correct_configuration(self): - self.assertIsNotNone(self.drv) - - def test_add_v6_interface_to_router(self): - gateway_ip = '3FFE::1' - cidrs = ['3FFE::/16', '2001::/16'] - - # Add couple of IPv6 subnets to router - for cidr in cidrs: - router = {'name': 'test-router-1', - 'tenant_id': 'ten-a', - 'seg_id': '123', - 'cidr': "%s" % cidr, - 'gip': "%s" % gateway_ip, - 'ip_version': 6} - - self.assertFalse(self.drv.add_router_interface(None, router)) - - def test_delete_v6_interface_from_router(self): - gateway_ip = '3FFE::1' - cidrs = ['3FFE::/16', '2001::/16'] - - # remove couple of IPv6 subnets from router - for cidr in cidrs: - router = {'name': 'test-router-1', - 'tenant_id': 'ten-a', - 'seg_id': '123', - 'cidr': "%s" % cidr, - 'gip': "%s" % gateway_ip, - 'ip_version': 6} - - self.assertFalse(self.drv.remove_router_interface(None, router)) - - -class AristaL3DriverTestCasesMlag_one_switch_failed(base.BaseTestCase): - """Test cases to test with non redundant hardare in redundancy mode. - - In the following test cases, the driver is configured in MLAG (redundancy - mode) but, one of the switches is mocked to throw exceptoin to mimic - failure of the switch. Ensure that the the operation does not fail when - one of the switches fails. - """ - - def setUp(self): - super(AristaL3DriverTestCasesMlag_one_switch_failed, self).setUp() - setup_arista_config('value', mlag=True) - self.drv = arista.AristaL3Driver() - self.drv._servers = [] - self.drv._servers.append(mock.MagicMock()) - self.drv._servers.append(mock.MagicMock()) - - def test_create_router_when_one_switch_fails(self): - router = {} - router['name'] = 'test-router-1' - tenant = '123' - - # Make one of the switches throw an exception - i.e. fail - self.drv._servers[0].execute = mock.Mock(side_effect=Exception) - with mock.patch.object(arista.LOG, 'exception') as log_exception: - self.drv.create_router(None, tenant, router) - log_exception.assert_called_once_with(mock.ANY) - - def test_delete_router_when_one_switch_fails(self): - router = {} - router['name'] = 'test-router-1' - tenant = '123' - router_id = '345' - - # Make one of the switches throw an exception - i.e. fail - self.drv._servers[1].execute = mock.Mock(side_effect=Exception) - with mock.patch.object(arista.LOG, 'exception') as log_exception: - self.drv.delete_router(None, tenant, router_id, router) - log_exception.assert_called_once_with(mock.ANY) - - def test_add_router_interface_when_one_switch_fails(self): - router = {} - router['name'] = 'test-router-1' - router['tenant_id'] = 'ten-1' - router['seg_id'] = '100' - router['ip_version'] = 4 - router['cidr'] = '10.10.10.0/24' - router['gip'] = '10.10.10.1' - - # Make one of the switches throw an exception - i.e. fail - self.drv._servers[1].execute = mock.Mock(side_effect=Exception) - with mock.patch.object(arista.LOG, 'exception') as log_exception: - self.drv.add_router_interface(None, router) - log_exception.assert_called_once_with(mock.ANY) - - def test_remove_router_interface_when_one_switch_fails(self): - router = {} - router['name'] = 'test-router-1' - router['tenant_id'] = 'ten-1' - router['seg_id'] = '100' - router['ip_version'] = 4 - router['cidr'] = '10.10.10.0/24' - router['gip'] = '10.10.10.1' - - # Make one of the switches throw an exception - i.e. fail - self.drv._servers[0].execute = mock.Mock(side_effect=Exception) - with mock.patch.object(arista.LOG, 'exception') as log_exception: - self.drv.remove_router_interface(None, router) - log_exception.assert_called_once_with(mock.ANY) diff --git a/networking_arista/tests/unit/ml2/__init__.py b/networking_arista/tests/unit/ml2/__init__.py deleted file mode 100644 index faed26a..0000000 --- a/networking_arista/tests/unit/ml2/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright 2011 OpenStack Foundation. -# 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. - -from oslo_config import cfg - - -cfg.CONF.use_stderr = False diff --git a/networking_arista/tests/unit/ml2/test_arista_mechanism_driver.py b/networking_arista/tests/unit/ml2/test_arista_mechanism_driver.py deleted file mode 100644 index 8fe4d6e..0000000 --- a/networking_arista/tests/unit/ml2/test_arista_mechanism_driver.py +++ /dev/null @@ -1,2173 +0,0 @@ -# Copyright (c) 2013 OpenStack Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import functools -import operator -import socket - -import mock -from mock import patch -from neutron_lib.api.definitions import portbindings -from neutron_lib import constants as n_const -from neutron_lib.db import api as db_api -from neutron_lib.plugins import constants as plugin_constants -from neutron_lib.plugins import directory -from neutron_lib.plugins.ml2 import api as driver_api -from oslo_config import cfg -from oslo_utils import importutils -import six - -from neutron.tests import base -from neutron.tests.unit import testlib_api - -from networking_arista.common import db_lib -from networking_arista.common import exceptions as arista_exc -from networking_arista.ml2 import arista_ml2 -from networking_arista.ml2 import mechanism_arista -import networking_arista.tests.unit.ml2.utils as utils - - -def setup_valid_config(): - utils.setup_arista_wrapper_config(cfg) - - -class AristaProvisionedVlansStorageTestCase(testlib_api.SqlTestCase): - """Test storing and retriving functionality of Arista mechanism driver. - - Tests all methods of this class by invoking them separately as well - as a group. - """ - - def test_tenant_is_remembered(self): - tenant_id = 'test' - - db_lib.remember_tenant(tenant_id) - net_provisioned = db_lib.is_tenant_provisioned(tenant_id) - self.assertTrue(net_provisioned, 'Tenant must be provisioned') - - def test_tenant_is_removed(self): - tenant_id = 'test' - - db_lib.remember_tenant(tenant_id) - db_lib.forget_tenant(tenant_id) - net_provisioned = db_lib.is_tenant_provisioned(tenant_id) - self.assertFalse(net_provisioned, 'The Tenant should be deleted') - - def test_network_is_remembered(self): - tenant_id = 'test' - network_id = '123' - segmentation_id = 456 - segment_id = 'segment_id_%s' % segmentation_id - - db_lib.remember_network_segment(tenant_id, network_id, segmentation_id, - segment_id) - net_provisioned = db_lib.is_network_provisioned(tenant_id, - network_id) - self.assertTrue(net_provisioned, 'Network must be provisioned') - - def test_network_is_removed(self): - tenant_id = 'test' - network_id = '123' - segment_id = 'segment_id_1' - - db_lib.remember_network_segment(tenant_id, network_id, '123', - segment_id) - db_lib.forget_network_segment(tenant_id, network_id) - net_provisioned = db_lib.is_network_provisioned(tenant_id, network_id) - self.assertFalse(net_provisioned, 'The network should be deleted') - - def test_vm_is_remembered(self): - vm_id = 'VM-1' - tenant_id = 'test' - network_id = '123' - port_id = 456 - host_id = 'ubuntu1' - - db_lib.remember_vm(vm_id, host_id, port_id, network_id, tenant_id) - vm_provisioned = db_lib.is_vm_provisioned(vm_id, host_id, port_id, - network_id, tenant_id) - self.assertTrue(vm_provisioned, 'VM must be provisioned') - - def test_vm_is_removed(self): - vm_id = 'VM-1' - tenant_id = 'test' - network_id = '123' - port_id = 456 - host_id = 'ubuntu1' - - db_lib.remember_vm(vm_id, host_id, port_id, network_id, tenant_id) - db_lib.forget_port(port_id, host_id) - vm_provisioned = db_lib.is_vm_provisioned(vm_id, host_id, port_id, - network_id, tenant_id) - self.assertFalse(vm_provisioned, 'The vm should be deleted') - - def test_remembers_multiple_networks(self): - tenant_id = 'test' - expected_num_nets = 100 - segment_id = 'segment_%s' - nets = ['id%s' % n for n in range(expected_num_nets)] - for net_id in nets: - db_lib.remember_network_segment(tenant_id, net_id, 123, - segment_id % net_id) - - num_nets_provisioned = db_lib.num_nets_provisioned(tenant_id) - self.assertEqual(expected_num_nets, num_nets_provisioned, - 'There should be %d nets, not %d' % - (expected_num_nets, num_nets_provisioned)) - - def test_removes_all_networks(self): - tenant_id = 'test' - num_nets = 100 - old_nets = db_lib.num_nets_provisioned(tenant_id) - nets = ['id_%s' % n for n in range(num_nets)] - segment_id = 'segment_%s' - for net_id in nets: - db_lib.remember_network_segment(tenant_id, net_id, 123, - segment_id % net_id) - for net_id in nets: - db_lib.forget_network_segment(tenant_id, net_id) - - num_nets_provisioned = db_lib.num_nets_provisioned(tenant_id) - expected = old_nets - self.assertEqual(expected, num_nets_provisioned, - 'There should be %d nets, not %d' % - (expected, num_nets_provisioned)) - - def test_remembers_multiple_tenants(self): - expected_num_tenants = 100 - tenants = ['id%s' % n for n in range(expected_num_tenants)] - for tenant_id in tenants: - db_lib.remember_tenant(tenant_id) - - num_tenants_provisioned = db_lib.num_provisioned_tenants() - self.assertEqual(expected_num_tenants, num_tenants_provisioned, - 'There should be %d tenants, not %d' % - (expected_num_tenants, num_tenants_provisioned)) - - def test_removes_multiple_tenants(self): - num_tenants = 100 - tenants = ['id%s' % n for n in range(num_tenants)] - for tenant_id in tenants: - db_lib.remember_tenant(tenant_id) - for tenant_id in tenants: - db_lib.forget_tenant(tenant_id) - - num_tenants_provisioned = db_lib.num_provisioned_tenants() - expected = 0 - self.assertEqual(expected, num_tenants_provisioned, - 'There should be %d tenants, not %d' % - (expected, num_tenants_provisioned)) - - def test_num_vm_is_valid(self): - tenant_id = 'test' - network_id = '123' - port_id_base = 'port-id' - host_id = 'ubuntu1' - - vm_to_remember = ['vm1', 'vm2', 'vm3'] - vm_to_forget = ['vm2', 'vm1'] - - for vm in vm_to_remember: - port_id = port_id_base + vm - db_lib.remember_vm(vm, host_id, port_id, network_id, tenant_id) - for vm in vm_to_forget: - port_id = port_id_base + vm - db_lib.forget_port(port_id, host_id) - - num_vms = len(db_lib.get_vms(tenant_id)) - expected = len(vm_to_remember) - len(vm_to_forget) - - self.assertEqual(expected, num_vms, - 'There should be %d records, ' - 'got %d records' % (expected, num_vms)) - # clean up afterwards - db_lib.forget_port(port_id, host_id) - - def test_get_network_list_returns_eos_compatible_data(self): - tenant = u'test-1' - segm_type = 'vlan' - network_id = u'123' - network2_id = u'1234' - vlan_id = 123 - vlan2_id = 1234 - segment_id1 = '11111-%s' % vlan_id - segment_id2 = '11111-%s' % vlan2_id - expected_eos_net_list = {network_id: {u'networkId': network_id, - u'segmentationTypeId': vlan_id, - u'tenantId': tenant, - u'segmentId': segment_id1, - u'segmentationType': segm_type}, - network2_id: {u'networkId': network2_id, - u'tenantId': tenant, - u'segmentId': segment_id2, - u'segmentationTypeId': vlan2_id, - u'segmentationType': segm_type}} - - db_lib.remember_network_segment(tenant, - network_id, vlan_id, segment_id1) - db_lib.remember_network_segment(tenant, - network2_id, vlan2_id, segment_id2) - - net_list = db_lib.get_networks(tenant) - self.assertEqual(net_list, expected_eos_net_list, ('%s != %s' % - (net_list, expected_eos_net_list))) - - -BASE_RPC = "networking_arista.ml2.arista_ml2.AristaRPCWrapperJSON." -JSON_SEND_FUNC = BASE_RPC + "_send_api_request" -RAND_FUNC = BASE_RPC + "_get_random_name" -EAPI_SEND_FUNC = ('networking_arista.ml2.arista_ml2.AristaRPCWrapperEapi' - '._send_eapi_req') - - -def port_dict_representation(port): - return {port['portId']: {'device_owner': port['device_owner'], - 'device_id': port['device_id'], - 'name': port['name'], - 'id': port['portId'], - 'tenant_id': port['tenant_id'], - 'network_id': port['network_id']}} - - -class _UnorderedDictList(list): - def __init__(self, iterable='', sort_key=None): - super(_UnorderedDictList, self).__init__(iterable) - try: - (self[0] or {})[sort_key] - self.sort_key = sort_key - except (IndexError, KeyError): - self.sort_key = None - - def __eq__(self, other): - if isinstance(other, list) and self.sort_key: - key = operator.itemgetter(self.sort_key) - return sorted(self, key=key) == sorted(other, key=key) - else: - return super(_UnorderedDictList, self).__eq__(other) - - -class TestAristaJSONRPCWrapper(testlib_api.SqlTestCase): - def setUp(self): - super(TestAristaJSONRPCWrapper, self).setUp() - plugin_klass = importutils.import_class( - "neutron.db.db_base_plugin_v2.NeutronDbPluginV2") - directory.add_plugin(plugin_constants.CORE, plugin_klass()) - setup_valid_config() - ndb = db_lib.NeutronNets() - self.drv = arista_ml2.AristaRPCWrapperJSON(ndb) - self.drv._server_ip = "10.11.12.13" - self.region = 'RegionOne' - - def _verify_send_api_request_call(self, mock_send_api_req, calls, - unordered_dict_list=False): - if unordered_dict_list: - wrapper = functools.partial(_UnorderedDictList, sort_key='id') - else: - wrapper = lambda x: x - - expected_calls = [ - mock.call(c[0], c[1], *(wrapper(d) for d in c[2:])) for c in calls - ] - - mock_send_api_req.assert_has_calls(expected_calls, any_order=True) - - @patch(JSON_SEND_FUNC) - def test_register_with_eos(self, mock_send_api_req): - self.drv.register_with_eos() - post_data = {'name': 'keystone', 'password': 'fun', - 'tenant': 'tenant_name', 'user': 'neutron', - 'authUrl': 'abc://host:5000/v3/'} - clean_data = post_data.copy() - clean_data['password'] = "*****" - calls = [ - ('region/RegionOne/service-end-point', 'POST', [post_data], - [clean_data]), - ('region/RegionOne', 'PUT', - [{'name': 'RegionOne', 'syncInterval': 10}]) - ] - self._verify_send_api_request_call(mock_send_api_req, calls) - - def _get_random_name(self): - return 'thisWillBeRandomInProd' - - @patch(JSON_SEND_FUNC) - @patch(RAND_FUNC, _get_random_name) - def test_sync_start(self, mock_send_api_req): - mock_send_api_req.side_effect = [ - [{'name': 'RegionOne', 'syncStatus': ''}], - [{}], - [{'syncStatus': 'syncInProgress', - 'requestId': self._get_random_name()}] - ] - assert self.drv.sync_start() - calls = [ - ('region/RegionOne/sync', 'POST', - {'requester': socket.gethostname().split('.')[0], - 'requestId': self._get_random_name()}) - ] - self._verify_send_api_request_call(mock_send_api_req, calls) - - @patch(JSON_SEND_FUNC) - @patch(RAND_FUNC, _get_random_name) - def test_sync_end(self, mock_send_api_req): - mock_send_api_req.return_value = [{'requester': - self._get_random_name()}] - self.drv.current_sync_name = self._get_random_name() - assert self.drv.sync_end() - calls = [ - ('region/RegionOne/sync', 'DELETE') - ] - self._verify_send_api_request_call(mock_send_api_req, calls) - - @patch(JSON_SEND_FUNC) - def test_create_region(self, mock_send_api_req): - self.drv.create_region('foo') - calls = [('region/', 'POST', [{'name': 'foo'}])] - self._verify_send_api_request_call(mock_send_api_req, calls) - - @patch(JSON_SEND_FUNC) - def test_delete_region(self, mock_send_api_req): - self.drv.delete_region('foo') - calls = [('region/', 'DELETE', [{'name': 'foo'}])] - self._verify_send_api_request_call(mock_send_api_req, calls) - - @patch(JSON_SEND_FUNC) - def test_get_tenants(self, mock_send_api_req): - self.drv.get_tenants() - calls = [('region/RegionOne/tenant', 'GET')] - self._verify_send_api_request_call(mock_send_api_req, calls) - - @patch(JSON_SEND_FUNC) - def test_delete_tenant_bulk(self, mock_send_api_req): - self.drv.delete_tenant_bulk(['t1', 't2']) - calls = [('region/RegionOne/tenant', 'DELETE', - [{'id': 't1'}, {'id': 't2'}])] - self._verify_send_api_request_call(mock_send_api_req, calls) - - def _createNetworkData(self, tenant_id, network_id, shared=False, - seg_id=100, network_type='vlan'): - return { - 'network_id': network_id, - 'tenantId': tenant_id, - 'shared': shared, - 'segments': [{'segmentation_id': seg_id, - 'physical_network': 'default', - 'id': 'segment_id_1', - 'is_dynamic': False, - 'network_type': network_type}], - } - - @patch(JSON_SEND_FUNC) - def test_create_network_bulk(self, mock_send_api_req): - n = [] - n.append(self._createNetworkData('t1', 'net1', seg_id=100)) - n.append(self._createNetworkData('t1', 'net2', seg_id=200)) - n.append(self._createNetworkData('t1', 'net3', network_type='flat')) - self.drv.create_network_bulk('t1', n) - calls = [ - ('region/RegionOne/network', 'POST', - [{'id': 'net1', 'tenantId': 't1', 'shared': False}, - {'id': 'net2', 'tenantId': 't1', 'shared': False}, - {'id': 'net3', 'tenantId': 't1', 'shared': False}]), - ('region/RegionOne/segment', 'POST', - [{'id': 'segment_id_1', 'networkId': 'net1', 'type': 'vlan', - 'segmentationId': 100, 'segmentType': 'static'}, - {'id': 'segment_id_1', 'networkId': 'net2', 'type': 'vlan', - 'segmentationId': 200, 'segmentType': 'static'}]) - ] - self._verify_send_api_request_call(mock_send_api_req, calls, True) - - @patch(JSON_SEND_FUNC) - def test_delete_network_bulk(self, mock_send_api_req): - self.drv.delete_network_bulk('t1', ['net1', 'net2']) - calls = [ - ('region/RegionOne/network', 'DELETE', - [{'id': 'net1', 'tenantId': 't1'}, - {'id': 'net2', 'tenantId': 't1'}]) - ] - self._verify_send_api_request_call(mock_send_api_req, calls, True) - - @patch(JSON_SEND_FUNC) - def test_create_network_segments(self, mock_send_api_req): - segments = [{'segmentation_id': 101, - 'physical_network': 'default', - 'id': 'segment_id_1', - 'is_dynamic': False, - 'network_type': 'vlan'}, - {'segmentation_id': 102, - 'physical_network': 'default', - 'id': 'segment_id_2', - 'is_dynamic': True, - 'network_type': 'vlan'}] - self.drv.create_network_segments('t1', 'n1', 'net1', segments) - calls = [ - ('region/RegionOne/segment', 'POST', - [{'id': 'segment_id_1', 'networkId': 'n1', 'type': 'vlan', - 'segmentationId': 101, 'segmentType': 'static'}, - {'id': 'segment_id_2', 'networkId': 'n1', 'type': 'vlan', - 'segmentationId': 102, 'segmentType': 'dynamic'}]) - ] - self._verify_send_api_request_call(mock_send_api_req, calls, True) - - @patch(JSON_SEND_FUNC) - def test_delete_network_segments(self, mock_send_api_req): - segments = [{'segmentation_id': 101, - 'physical_network': 'default', - 'id': 'segment_id_1', - 'is_dynamic': False, - 'network_type': 'vlan'}, - {'segmentation_id': 102, - 'physical_network': 'default', - 'id': 'segment_id_2', - 'is_dynamic': True, - 'network_type': 'vlan'}] - self.drv.delete_network_segments('t1', segments) - calls = [ - ('region/RegionOne/segment', 'DELETE', - [{'id': 'segment_id_1'}, - {'id': 'segment_id_2'}]) - ] - self._verify_send_api_request_call(mock_send_api_req, calls) - - @patch(JSON_SEND_FUNC) - def test_create_instance_bulk(self, mock_send_api_req): - tenant_id = 'ten-3' - num_devices = 8 - num_ports_per_device = 2 - - device_count = 0 - devices = {} - for device_id in range(0, num_devices): - dev_id = 'dev-id-%d' % device_id - devices[dev_id] = {'vmId': dev_id, - 'baremetal_instance': False, - 'ports': [] - } - for port_id in range(0, num_ports_per_device): - port_id = 'port-id-%d-%d' % (device_id, port_id) - port = { - 'device_id': 'dev-id-%d' % device_id, - 'hosts': ['host_%d' % (device_count)], - 'portId': port_id - } - devices[dev_id]['ports'].append(port) - device_count += 1 - - device_owners = [n_const.DEVICE_OWNER_DHCP, - 'compute', - 'baremetal', - n_const.DEVICE_OWNER_DVR_INTERFACE] - port_list = [] - - net_count = 0 - for device_id in range(0, num_devices): - for port_id in range(0, num_ports_per_device): - port = { - 'portId': 'port-id-%d-%d' % (device_id, port_id), - 'device_id': 'dev-id-%d' % device_id, - 'device_owner': device_owners[device_id % 4], - 'network_id': 'network-id-%d' % net_count, - 'name': 'port-%d-%d' % (device_id, port_id), - 'tenant_id': tenant_id, - } - port_list.append(port) - net_count += 1 - - create_ports = {} - for port in port_list: - create_ports.update(port_dict_representation(port)) - - profiles = {} - for port in port_list: - profiles[port['portId']] = {'vnic_type': 'normal'} - if port['device_owner'] == 'baremetal': - profiles[port['portId']] = { - 'vnic_type': 'baremetal', - 'profile': '{"local_link_information":' - '[{"switch_id": "switch01", "port_id": "Ethernet1"}]}'} - self.drv.create_instance_bulk(tenant_id, create_ports, devices, - profiles) - calls = [ - ('region/RegionOne/tenant?tenantId=ten-3', 'GET'), - ('region/RegionOne/dhcp?tenantId=ten-3', 'POST', - [{'id': 'dev-id-0', 'hostId': 'host_0'}, - {'id': 'dev-id-4', 'hostId': 'host_4'}]), - ('region/RegionOne/vm?tenantId=ten-3', 'POST', - [{'id': 'dev-id-1', 'hostId': 'host_1'}, - {'id': 'dev-id-5', 'hostId': 'host_5'}]), - ('region/RegionOne/baremetal?tenantId=ten-3', 'POST', - [{'id': 'dev-id-2', 'hostId': 'host_2'}, - {'id': 'dev-id-6', 'hostId': 'host_6'}]), - ('region/RegionOne/router?tenantId=ten-3', 'POST', - [{'id': 'dev-id-3', 'hostId': 'host_3'}, - {'id': 'dev-id-7', 'hostId': 'host_7'}]), - ('region/RegionOne/port', 'POST', - [{'networkId': 'network-id-0', 'id': 'port-id-0-0', - 'tenantId': 'ten-3', 'instanceId': 'dev-id-0', - 'name': 'port-0-0', 'hosts': ['host_0'], - 'instanceType': 'dhcp', 'vlanType': 'allowed'}, - {'networkId': 'network-id-1', 'id': 'port-id-0-1', - 'tenantId': 'ten-3', 'instanceId': 'dev-id-0', - 'name': 'port-0-1', 'hosts': ['host_0'], - 'instanceType': 'dhcp', 'vlanType': 'allowed'}, - - {'networkId': 'network-id-2', 'id': 'port-id-1-0', - 'tenantId': 'ten-3', 'instanceId': 'dev-id-1', - 'name': 'port-1-0', 'hosts': ['host_1'], - 'instanceType': 'vm', 'vlanType': 'allowed'}, - {'networkId': 'network-id-3', 'id': 'port-id-1-1', - 'tenantId': 'ten-3', 'instanceId': 'dev-id-1', - 'name': 'port-1-1', 'hosts': ['host_1'], - 'instanceType': 'vm', 'vlanType': 'allowed'}, - - {'networkId': 'network-id-4', 'id': 'port-id-2-0', - 'tenantId': 'ten-3', 'instanceId': 'dev-id-2', - 'name': 'port-2-0', 'hosts': ['host_2'], - 'instanceType': 'baremetal', 'vlanType': 'native'}, - {'networkId': 'network-id-5', 'id': 'port-id-2-1', - 'tenantId': 'ten-3', 'instanceId': 'dev-id-2', - 'name': 'port-2-1', 'hosts': ['host_2'], - 'instanceType': 'baremetal', 'vlanType': 'native'}, - - {'networkId': 'network-id-6', 'id': 'port-id-3-0', - 'tenantId': 'ten-3', 'instanceId': 'dev-id-3', - 'name': 'port-3-0', 'hosts': ['host_3'], - 'instanceType': 'router', 'vlanType': 'allowed'}, - {'networkId': 'network-id-7', 'id': 'port-id-3-1', - 'tenantId': 'ten-3', 'instanceId': 'dev-id-3', - 'name': 'port-3-1', 'hosts': ['host_3'], - 'instanceType': 'router', 'vlanType': 'allowed'}, - - {'networkId': 'network-id-8', 'id': 'port-id-4-0', - 'tenantId': 'ten-3', 'instanceId': 'dev-id-4', - 'name': 'port-4-0', 'hosts': ['host_4'], - 'instanceType': 'dhcp', 'vlanType': 'allowed'}, - {'networkId': 'network-id-9', 'id': 'port-id-4-1', - 'tenantId': 'ten-3', 'instanceId': 'dev-id-4', - 'name': 'port-4-1', 'hosts': ['host_4'], - 'instanceType': 'dhcp', 'vlanType': 'allowed'}, - - {'networkId': 'network-id-10', 'id': 'port-id-5-0', - 'tenantId': 'ten-3', 'instanceId': 'dev-id-5', - 'name': 'port-5-0', 'hosts': ['host_5'], - 'instanceType': 'vm', 'vlanType': 'allowed'}, - {'networkId': 'network-id-11', 'id': 'port-id-5-1', - 'tenantId': 'ten-3', 'instanceId': 'dev-id-5', - 'name': 'port-5-1', 'hosts': ['host_5'], - 'instanceType': 'vm', 'vlanType': 'allowed'}, - - {'networkId': 'network-id-12', 'id': 'port-id-6-0', - 'tenantId': 'ten-3', 'instanceId': 'dev-id-6', - 'name': 'port-6-0', 'hosts': ['host_6'], - 'instanceType': 'baremetal', 'vlanType': 'native'}, - {'networkId': 'network-id-13', 'id': 'port-id-6-1', - 'tenantId': 'ten-3', 'instanceId': 'dev-id-6', - 'name': 'port-6-1', 'hosts': ['host_6'], - 'instanceType': 'baremetal', 'vlanType': 'native'}, - - {'networkId': 'network-id-14', 'id': 'port-id-7-0', - 'tenantId': 'ten-3', 'instanceId': 'dev-id-7', - 'name': 'port-7-0', 'hosts': ['host_7'], - 'instanceType': 'router', 'vlanType': 'allowed'}, - {'networkId': 'network-id-15', 'id': 'port-id-7-1', - 'tenantId': 'ten-3', 'instanceId': 'dev-id-7', - 'name': 'port-7-1', 'hosts': ['host_7'], - 'instanceType': 'router', 'vlanType': 'allowed'}]), - - ('region/RegionOne/port/port-id-0-0/binding', - 'POST', [{'portId': 'port-id-0-0', 'hostBinding': [ - {'segment': [], 'host': 'host_0'}]}]), - ('region/RegionOne/port/port-id-0-1/binding', - 'POST', [{'portId': 'port-id-0-1', 'hostBinding': [ - {'segment': [], 'host': 'host_0'}]}]), - - ('region/RegionOne/port/port-id-1-0/binding', - 'POST', [{'portId': 'port-id-1-0', 'hostBinding': [ - {'segment': [], 'host': 'host_1'}]}]), - ('region/RegionOne/port/port-id-1-1/binding', - 'POST', [{'portId': 'port-id-1-1', 'hostBinding': [ - {'segment': [], 'host': 'host_1'}]}]), - - ('region/RegionOne/port/port-id-2-0/binding', - 'POST', [{'portId': 'port-id-2-0', 'switchBinding': [ - {'interface': u'Ethernet1', 'host': 'host_2', - 'segment': [], 'switch': u'switch01'}]}]), - ('region/RegionOne/port/port-id-2-1/binding', - 'POST', [{'portId': 'port-id-2-1', 'switchBinding': [ - {'interface': u'Ethernet1', 'host': 'host_2', - 'segment': [], 'switch': u'switch01'}]}]), - - ('region/RegionOne/port/port-id-3-0/binding', - 'POST', [{'portId': 'port-id-3-0', 'hostBinding': [ - {'segment': [], 'host': 'host_3'}]}]), - ('region/RegionOne/port/port-id-3-1/binding', - 'POST', [{'portId': 'port-id-3-1', 'hostBinding': [ - {'segment': [], 'host': 'host_3'}]}]), - - ('region/RegionOne/port/port-id-4-0/binding', - 'POST', [{'portId': 'port-id-4-0', 'hostBinding': [ - {'segment': [], 'host': 'host_4'}]}]), - ('region/RegionOne/port/port-id-4-1/binding', - 'POST', [{'portId': 'port-id-4-1', 'hostBinding': [ - {'segment': [], 'host': 'host_4'}]}]), - - ('region/RegionOne/port/port-id-5-0/binding', - 'POST', [{'portId': 'port-id-5-0', 'hostBinding': [ - {'segment': [], 'host': 'host_5'}]}]), - ('region/RegionOne/port/port-id-5-1/binding', - 'POST', [{'portId': 'port-id-5-1', 'hostBinding': [ - {'segment': [], 'host': 'host_5'}]}]), - - ('region/RegionOne/port/port-id-6-0/binding', - 'POST', [{'portId': 'port-id-6-0', 'switchBinding': [ - {'interface': u'Ethernet1', 'host': 'host_6', - 'segment': [], 'switch': u'switch01'}]}]), - ('region/RegionOne/port/port-id-6-1/binding', - 'POST', [{'portId': 'port-id-6-1', 'switchBinding': [ - {'interface': u'Ethernet1', 'host': 'host_6', - 'segment': [], 'switch': u'switch01'}]}]), - - ('region/RegionOne/port/port-id-7-0/binding', - 'POST', [{'portId': 'port-id-7-0', 'hostBinding': [ - {'segment': [], 'host': 'host_7'}]}]), - ('region/RegionOne/port/port-id-7-1/binding', - 'POST', [{'portId': 'port-id-7-1', 'hostBinding': [ - {'segment': [], 'host': 'host_7'}]}]), - ] - self._verify_send_api_request_call(mock_send_api_req, calls, True) - - @patch(JSON_SEND_FUNC) - def test_delete_vm_bulk(self, mock_send_api_req): - self.drv.delete_vm_bulk('t1', ['vm1', 'vm2']) - calls = [ - ('region/RegionOne/vm', 'DELETE', - [{'id': 'vm1'}, {'id': 'vm2'}]) - ] - self._verify_send_api_request_call(mock_send_api_req, calls) - - @patch(JSON_SEND_FUNC) - def test_delete_dhcp_bulk(self, mock_send_api_req): - self.drv.delete_dhcp_bulk('t1', ['dhcp1', 'dhcp2']) - calls = [ - ('region/RegionOne/dhcp', 'DELETE', - [{'id': 'dhcp1'}, {'id': 'dhcp2'}]) - ] - self._verify_send_api_request_call(mock_send_api_req, calls) - - @patch(JSON_SEND_FUNC) - def test_delete_port(self, mock_send_api_req): - self.drv.delete_port('p1', 'inst1', 'vm') - self.drv.delete_port('p2', 'inst2', 'dhcp') - calls = [ - ('region/RegionOne/port?portId=p1&id=inst1&type=vm', - 'DELETE', - [{'hosts': [], 'id': 'p1', 'tenantId': None, 'networkId': None, - 'instanceId': 'inst1', 'name': None, 'instanceType': 'vm', - 'vlanType': 'allowed'}]), - ('region/RegionOne/port?portId=p2&id=inst2&type=dhcp', - 'DELETE', - [{'hosts': [], 'id': 'p2', 'tenantId': None, 'networkId': None, - 'instanceId': 'inst2', 'name': None, 'instanceType': 'dhcp', - 'vlanType': 'allowed'}]) - ] - self._verify_send_api_request_call(mock_send_api_req, calls) - - @patch(JSON_SEND_FUNC) - def test_get_port(self, mock_send_api_req): - self.drv.get_instance_ports('inst1', 'vm') - calls = [ - ('region/RegionOne/port?id=inst1&type=vm', - 'GET') - ] - self._verify_send_api_request_call(mock_send_api_req, calls) - - @patch(JSON_SEND_FUNC) - def test_plug_virtual_port_into_network(self, mock_send_api_req): - segments = [{'segmentation_id': 101, - 'id': 'segment_id_1', - 'network_type': 'vlan', - 'is_dynamic': False}] - self.drv.plug_port_into_network('vm1', 'h1', 'p1', 'n1', 't1', 'port1', - 'compute', None, None, None, segments) - calls = [ - ('region/RegionOne/vm?tenantId=t1', 'POST', - [{'id': 'vm1', 'hostId': 'h1'}]), - ('region/RegionOne/port', 'POST', - [{'id': 'p1', 'hosts': ['h1'], 'tenantId': 't1', - 'networkId': 'n1', 'instanceId': 'vm1', 'name': 'port1', - 'instanceType': 'vm', 'vlanType': 'allowed'}]), - ('region/RegionOne/port/p1/binding', 'POST', - [{'portId': 'p1', 'hostBinding': [{'host': 'h1', 'segment': [{ - 'id': 'segment_id_1', 'type': 'vlan', 'segmentationId': 101, - 'networkId': 'n1', 'segment_type': 'static'}]}]}]), - ] - self._verify_send_api_request_call(mock_send_api_req, calls) - - @patch(JSON_SEND_FUNC) - @patch('networking_arista.ml2.arista_ml2.AristaRPCWrapperJSON.' - 'get_instance_ports') - def test_unplug_virtual_port_from_network(self, mock_get_instance_ports, - mock_send_api_req): - mock_get_instance_ports.return_value = [] - self.drv.unplug_port_from_network('vm1', 'compute', 'h1', 'p1', 'n1', - 't1', None, None) - port = self.drv._create_port_data('p1', None, None, 'vm1', None, 'vm', - None) - calls = [ - ('region/RegionOne/port/p1/binding', 'DELETE', - [{'portId': 'p1', 'hostBinding': [{'host': 'h1'}]}]), - ('region/RegionOne/port?portId=p1&id=vm1&type=vm', - 'DELETE', [port]), - ('region/RegionOne/vm', 'DELETE', [{'id': 'vm1'}]) - ] - self._verify_send_api_request_call(mock_send_api_req, calls) - - @patch(JSON_SEND_FUNC) - def test_plug_baremetal_port_into_network(self, mock_send_api_req): - segments = [{'segmentation_id': 101, - 'id': 'segment_id_1', - 'network_type': 'vlan', - 'is_dynamic': False}] - sg = {'id': 'security-group-1'} - switch_bindings = [{'switch_id': 'switch01', 'port_id': 'Ethernet1', - 'switch_info': 'switch01'}] - self.drv.plug_port_into_network('bm1', 'h1', 'p1', 'n1', 't1', 'port1', - 'baremetal', sg, None, 'baremetal', - segments, - switch_bindings=switch_bindings) - calls = [ - ('region/RegionOne/baremetal?tenantId=t1', 'POST', - [{'id': 'bm1', 'hostId': 'h1'}]), - ('region/RegionOne/port', 'POST', - [{'id': 'p1', 'hosts': ['h1'], 'tenantId': 't1', - 'networkId': 'n1', 'instanceId': 'bm1', 'name': 'port1', - 'instanceType': 'baremetal', 'vlanType': 'native'}]), - ('region/RegionOne/port/p1/binding', 'POST', - [{'portId': 'p1', 'switchBinding': [{'host': 'h1', - 'switch': 'switch01', 'interface': 'Ethernet1', 'segment': [{ - 'id': 'segment_id_1', 'type': 'vlan', 'segmentationId': 101, - 'networkId': 'n1', 'segment_type': 'static'}]}]}]), - ] - self._verify_send_api_request_call(mock_send_api_req, calls) - - @patch(JSON_SEND_FUNC) - @patch('networking_arista.ml2.arista_ml2.AristaRPCWrapperJSON.' - 'get_instance_ports') - def test_unplug_baremetal_port_from_network(self, mock_get_instance_ports, - mock_send_api_req): - mock_get_instance_ports.return_value = [] - switch_bindings = [{'switch_id': 'switch01', 'port_id': 'Ethernet1'}] - self.drv.unplug_port_from_network('bm1', 'baremetal', 'h1', 'p1', 'n1', - 't1', None, 'baremetal', - switch_bindings) - port = self.drv._create_port_data('p1', None, None, 'bm1', None, - 'baremetal', None) - calls = [ - ('region/RegionOne/port/p1/binding', 'DELETE', - [{'portId': 'p1', 'switchBinding': - [{'host': 'h1', 'switch': 'switch01', 'segment': [], - 'interface': 'Ethernet1'}]}]), - ('region/RegionOne/port?portId=p1&id=bm1&type=baremetal', - 'DELETE', [port]), - ('region/RegionOne/baremetal', 'DELETE', [{'id': 'bm1'}]) - ] - self._verify_send_api_request_call(mock_send_api_req, calls) - - @patch(JSON_SEND_FUNC) - def test_plug_dhcp_port_into_network(self, mock_send_api_req): - segments = [{'segmentation_id': 101, - 'id': 'segment_id_1', - 'network_type': 'vlan', - 'is_dynamic': False}] - self.drv.plug_port_into_network('vm1', 'h1', 'p1', 'n1', 't1', 'port1', - n_const.DEVICE_OWNER_DHCP, None, None, - None, segments) - calls = [ - ('region/RegionOne/dhcp?tenantId=t1', 'POST', - [{'id': 'vm1', 'hostId': 'h1'}]), - ('region/RegionOne/port', 'POST', - [{'id': 'p1', 'hosts': ['h1'], 'tenantId': 't1', - 'networkId': 'n1', 'instanceId': 'vm1', 'name': 'port1', - 'instanceType': 'dhcp', 'vlanType': 'allowed'}]) - ] - self._verify_send_api_request_call(mock_send_api_req, calls) - - @patch(JSON_SEND_FUNC) - @patch('networking_arista.ml2.arista_ml2.AristaRPCWrapperJSON.' - 'get_instance_ports') - def test_unplug_dhcp_port_from_network(self, mock_get_instance_ports, - mock_send_api_req): - mock_get_instance_ports.return_value = [] - self.drv.unplug_port_from_network('dhcp1', n_const.DEVICE_OWNER_DHCP, - 'h1', 'p1', 'n1', 't1', None, None) - calls = [ - ('region/RegionOne/port?portId=p1&id=dhcp1&type=dhcp', - 'DELETE', - [{'id': 'p1', 'hosts': [], 'tenantId': None, 'networkId': None, - 'instanceId': 'dhcp1', 'name': None, 'instanceType': 'dhcp', - 'vlanType': 'allowed'}]), - ('region/RegionOne/dhcp', 'DELETE', - [{'id': 'dhcp1'}]) - ] - self._verify_send_api_request_call(mock_send_api_req, calls) - - @patch(JSON_SEND_FUNC) - def test_plug_router_port_into_network(self, mock_send_api_req): - segments = [{'segmentation_id': 101, - 'id': 'segment_id_1', - 'network_type': 'vlan', - 'is_dynamic': False}] - self.drv.plug_port_into_network('router1', 'h1', 'p1', 'n1', 't1', - 'port1', - n_const.DEVICE_OWNER_DVR_INTERFACE, - None, None, None, segments) - calls = [ - ('region/RegionOne/router?tenantId=t1', 'POST', - [{'id': 'router1', 'hostId': 'h1'}]), - ('region/RegionOne/port', 'POST', - [{'id': 'p1', 'hosts': ['h1'], 'tenantId': 't1', - 'networkId': 'n1', 'instanceId': 'router1', 'name': 'port1', - 'instanceType': 'router', 'vlanType': 'allowed'}]) - ] - self._verify_send_api_request_call(mock_send_api_req, calls) - - @patch(JSON_SEND_FUNC) - @patch('networking_arista.ml2.arista_ml2.AristaRPCWrapperJSON.' - 'get_instance_ports') - def test_unplug_router_port_from_network(self, mock_get_instance_ports, - mock_send_api_req): - mock_get_instance_ports.return_value = [] - self.drv.unplug_port_from_network('router1', - n_const.DEVICE_OWNER_DVR_INTERFACE, - 'h1', 'p1', 'n1', 't1', None, None) - calls = [ - ('region/RegionOne/port?portId=p1&id=router1&type=router', - 'DELETE', - [{'id': 'p1', 'hosts': [], 'tenantId': None, 'networkId': None, - 'instanceId': 'router1', 'name': None, 'instanceType': 'router', - 'vlanType': 'allowed'}]), - ('region/RegionOne/router', 'DELETE', - [{'id': 'router1'}]) - ] - self._verify_send_api_request_call(mock_send_api_req, calls) - - -class PositiveRPCWrapperValidConfigTestCase(testlib_api.SqlTestCase): - """Test cases to test the RPC between Arista Driver and EOS. - - Tests all methods used to send commands between Arista Driver and EOS - """ - - def setUp(self): - super(PositiveRPCWrapperValidConfigTestCase, self).setUp() - setup_valid_config() - ndb = db_lib.NeutronNets() - self.drv = arista_ml2.AristaRPCWrapperEapi(ndb) - self.drv._server_ip = "10.11.12.13" - self.region = 'RegionOne' - - def _get_exit_mode_cmds(self, modes): - return ['exit'] * len(modes) - - def _verify_send_eapi_request_calls(self, mock_send_eapi_req, cmds, - commands_to_log=None): - calls = [] - calls.extend( - mock.call(cmds=cmd, commands_to_log=log_cmd) - for cmd, log_cmd in six.moves.zip(cmds, commands_to_log or cmds)) - mock_send_eapi_req.assert_has_calls(calls) - - def test_no_exception_on_correct_configuration(self): - self.assertIsNotNone(self.drv) - - @patch(EAPI_SEND_FUNC) - def test_plug_host_into_network(self, mock_send_eapi_req): - tenant_id = 'ten-1' - vm_id = 'vm-1' - port_id = 123 - network_id = 'net-id' - host = 'host' - port_name = '123-port' - segment_id = 'segment_id_1' - segments = [{'network_type': 'vlan', 'physical_network': 'default', - 'segmentation_id': 1234, 'id': segment_id}] - - self.drv.plug_host_into_network(vm_id, host, port_id, - network_id, tenant_id, segments, - port_name) - cmd1 = ['show openstack agent uuid'] - cmd2 = ['enable', 'configure', 'cvx', 'service openstack', - 'region RegionOne', - 'tenant ten-1', 'vm id vm-1 hostid host', - 'port id 123 name "123-port" network-id net-id', - ] - for level, segment in enumerate(segments): - cmd2.append('segment level %s id %s' % (level, segment['id'])) - - self._verify_send_eapi_request_calls(mock_send_eapi_req, [cmd1, cmd2]) - - @patch(EAPI_SEND_FUNC) - def test_plug_dhcp_port_into_network(self, mock_send_eapi_req): - tenant_id = 'ten-1' - vm_id = 'vm-1' - port_id = 123 - network_id = 'net-id' - host = 'host' - port_name = '123-port' - segments = [] - - self.drv.plug_port_into_network(vm_id, host, port_id, network_id, - tenant_id, port_name, - n_const.DEVICE_OWNER_DHCP, None, None, - None, segments) - cmd1 = ['show openstack agent uuid'] - cmd2 = ['enable', 'configure', 'cvx', 'service openstack', - 'region RegionOne', - 'tenant ten-1', 'network id net-id', - 'dhcp id vm-1 hostid host port-id 123 name "123-port"', - ] - self._verify_send_eapi_request_calls(mock_send_eapi_req, [cmd1, cmd2]) - - @patch(EAPI_SEND_FUNC) - def test_unplug_host_from_network(self, mock_send_eapi_req): - tenant_id = 'ten-1' - vm_id = 'vm-1' - port_id = 123 - network_id = 'net-id' - host = 'host' - self.drv.unplug_host_from_network(vm_id, host, port_id, - network_id, tenant_id) - cmd1 = ['show openstack agent uuid'] - cmd2 = ['enable', 'configure', 'cvx', 'service openstack', - 'region RegionOne', - 'tenant ten-1', 'vm id vm-1 hostid host', - 'no port id 123', - ] - self._verify_send_eapi_request_calls(mock_send_eapi_req, [cmd1, cmd2]) - - @patch(EAPI_SEND_FUNC) - def test_unplug_dhcp_port_from_network(self, mock_send_eapi_req): - tenant_id = 'ten-1' - vm_id = 'vm-1' - port_id = 123 - network_id = 'net-id' - host = 'host' - - self.drv.unplug_dhcp_port_from_network(vm_id, host, port_id, - network_id, tenant_id) - cmd1 = ['show openstack agent uuid'] - cmd2 = ['enable', 'configure', 'cvx', 'service openstack', - 'region RegionOne', - 'tenant ten-1', 'network id net-id', - 'no dhcp id vm-1 port-id 123', - ] - self._verify_send_eapi_request_calls(mock_send_eapi_req, [cmd1, cmd2]) - - @patch(EAPI_SEND_FUNC) - def test_create_network(self, mock_send_eapi_req): - tenant_id = 'ten-1' - self.drv.cli_commands['features'] = {'hierarchical-port-binding': 1} - network = { - 'network_id': 'net-id', - 'network_name': 'net-name', - 'segments': [{'segmentation_id': 123, - 'physical_network': 'default', - 'network_type': 'vlan', - 'id': 'segment_id_1'}], - 'shared': False, - } - self.drv.create_network(tenant_id, network) - cmd1 = ['show openstack agent uuid'] - cmd2 = ['enable', 'configure', 'cvx', 'service openstack', - 'region RegionOne', - 'tenant ten-1', 'network id net-id name "net-name"', - ] - for seg in network['segments']: - is_dynamic = seg.get('is_dynamic', False) - cmd2.append('segment %s type %s id %d %s' % (seg['id'], - seg['network_type'], seg['segmentation_id'], - 'dynamic' if is_dynamic else 'static')) - cmd2.append('no shared') - - self._verify_send_eapi_request_calls(mock_send_eapi_req, [cmd1, cmd2]) - - @patch(EAPI_SEND_FUNC) - def test_create_shared_network(self, mock_send_eapi_req): - tenant_id = 'ten-1' - segment_id = 'abcd-cccc' - segmentation_id = 123 - network_type = 'vlan' - segments = [{'segmentation_id': segmentation_id, - 'id': segment_id, - 'network_type': network_type}] - network = { - 'network_id': 'net-id', - 'network_name': 'net-name', - 'segments': segments, - 'shared': True} - self.drv.cli_commands['features'] = {'hierarchical-port-binding': 1} - self.drv.create_network(tenant_id, network) - cmd1 = ['show openstack agent uuid'] - cmd2 = ['enable', 'configure', 'cvx', 'service openstack', - 'region RegionOne', - 'tenant ten-1', 'network id net-id name "net-name"', - 'segment %s type %s id %d %s' % (segment_id, network_type, - segmentation_id, 'static'), - 'shared', - ] - self._verify_send_eapi_request_calls(mock_send_eapi_req, [cmd1, cmd2]) - - @patch(EAPI_SEND_FUNC) - def test_create_network_bulk(self, mock_send_eapi_req): - tenant_id = 'ten-2' - num_networks = 10 - network_type = 'vlan' - segment_id = 'abcd-eeee-%s' - self.drv.cli_commands['features'] = {'hierarchical-port-binding': 1} - networks = [{ - 'network_id': 'net-id-%d' % net_id, - 'network_name': 'net-name-%d' % net_id, - 'segments': [{'segmentation_id': net_id, - 'network_type': 'vlan', - 'id': segment_id % net_id}], - 'shared': True, - } for net_id in range(1, num_networks) - ] - - self.drv.create_network_bulk(tenant_id, networks) - cmd1 = ['show openstack agent uuid'] - cmd2 = ['enable', - 'configure', - 'cvx', - 'service openstack', - 'region RegionOne', - 'tenant ten-2'] - for net_id in range(1, num_networks): - cmd2.append('network id net-id-%d name "net-name-%d"' % - (net_id, net_id)) - cmd2.append('segment %s type %s id %d %s' % ( - segment_id % net_id, network_type, net_id, 'static')) - cmd2.append('shared') - self._verify_send_eapi_request_calls(mock_send_eapi_req, [cmd1, cmd2]) - - @patch(EAPI_SEND_FUNC) - def test_delete_network(self, mock_send_eapi_req): - tenant_id = 'ten-1' - network_id = 'net-id' - segments = [{'segmentation_id': 101, - 'physical_network': 'default', - 'id': 'segment_id_1', - 'network_type': 'vlan'}] - self.drv.delete_network(tenant_id, network_id, segments) - cmd1 = ['show openstack agent uuid'] - cmd2 = ['enable', 'configure', 'cvx', 'service openstack', - 'region RegionOne', - 'tenant ten-1', - 'network id net-id', - 'no segment segment_id_1', - ] - cmd3 = ['enable', 'configure', 'cvx', 'service openstack', - 'region RegionOne', - 'tenant ten-1', - 'no network id net-id', - ] - self._verify_send_eapi_request_calls(mock_send_eapi_req, - [cmd1, cmd2, cmd1, cmd3]) - - @patch(EAPI_SEND_FUNC) - def test_delete_network_bulk(self, mock_send_eapi_req): - tenant_id = 'ten-2' - num_networks = 10 - - networks = ['net-id-%d' % net_id for net_id in range(1, num_networks)] - self.drv.delete_network_bulk(tenant_id, networks) - cmd1 = ['show openstack agent uuid'] - cmd2 = ['enable', - 'configure', - 'cvx', - 'service openstack', - 'region RegionOne', - 'tenant ten-2'] - for net_id in range(1, num_networks): - cmd2.append('no network id net-id-%d' % net_id) - self._verify_send_eapi_request_calls(mock_send_eapi_req, [cmd1, cmd2]) - - @patch(EAPI_SEND_FUNC) - def test_delete_vm(self, mock_send_eapi_req): - tenant_id = 'ten-1' - vm_id = 'vm-id' - self.drv.delete_vm(tenant_id, vm_id) - cmd1 = ['show openstack agent uuid'] - cmd2 = ['enable', 'configure', 'cvx', 'service openstack', - 'region RegionOne', - 'tenant ten-1', 'no vm id vm-id', - ] - self._verify_send_eapi_request_calls(mock_send_eapi_req, [cmd1, cmd2]) - - @patch(EAPI_SEND_FUNC) - def test_delete_vm_bulk(self, mock_send_eapi_req): - tenant_id = 'ten-2' - num_vms = 10 - vm_ids = ['vm-id-%d' % vm_id for vm_id in range(1, num_vms)] - self.drv.delete_vm_bulk(tenant_id, vm_ids) - - cmd1 = ['show openstack agent uuid'] - cmd2 = ['enable', - 'configure', - 'cvx', - 'service openstack', - 'region RegionOne', - 'tenant ten-2'] - - for vm_id in range(1, num_vms): - cmd2.append('no vm id vm-id-%d' % vm_id) - self._verify_send_eapi_request_calls(mock_send_eapi_req, [cmd1, cmd2]) - - @patch(EAPI_SEND_FUNC) - def test_create_port_bulk(self, mock_send_eapi_req): - tenant_id = 'ten-3' - num_devices = 10 - num_ports_per_device = 2 - - device_count = 0 - devices = {} - for device_id in range(1, num_devices): - device_count += 1 - dev_id = 'dev-id-%d' % device_id - devices[dev_id] = {'vmId': dev_id, - 'baremetal_instance': False, - 'ports': [] - } - for port_id in range(1, num_ports_per_device): - port_id = 'port-id-%d-%d' % (device_id, port_id) - port = { - 'device_id': 'dev-id-%d' % device_id, - 'hosts': ['host_%d' % (device_count)], - 'portId': port_id - } - devices[dev_id]['ports'].append(port) - - device_owners = [n_const.DEVICE_OWNER_DHCP, 'compute', - n_const.DEVICE_OWNER_DVR_INTERFACE] - port_list = [] - - net_count = 1 - for device_id in range(1, num_devices): - for port_id in range(1, num_ports_per_device): - port = { - 'portId': 'port-id-%d-%d' % (device_id, port_id), - 'device_id': 'dev-id-%d' % device_id, - 'device_owner': device_owners[(device_id + port_id) % 3], - 'network_id': 'network-id-%d' % net_count, - 'name': 'port-%d-%d' % (device_id, port_id), - 'tenant_id': tenant_id - } - port_list.append(port) - net_count += 1 - - create_ports = {} - port_profiles = {} - for port in port_list: - create_ports.update(port_dict_representation(port)) - port_profiles[port['portId']] = {'vnic_type': 'normal'} - - self.drv.cli_commands[arista_ml2.CMD_INSTANCE] = 'instance' - self.drv.create_instance_bulk(tenant_id, create_ports, devices, - port_profiles=port_profiles) - cmd1 = ['show openstack agent uuid'] - cmd2 = ['enable', - 'configure', - 'cvx', - 'service openstack', - 'region RegionOne', - 'tenant ten-3'] - - for device in devices.values(): - for v_port in device['ports']: - port_id = v_port['portId'] - port = create_ports[port_id] - host = v_port['hosts'][0] - device_owner = port['device_owner'] - port_name = port['name'] - network_id = port['network_id'] - device_id = port['device_id'] - if device_owner == 'network:dhcp': - cmd2.append('network id %s' % network_id) - cmd2.append('dhcp id %s hostid %s port-id %s name "%s"' % ( - device_id, host, port_id, port_name)) - elif device_owner == 'compute': - cmd2.append('vm id %s hostid %s' % (device_id, host)) - cmd2.append('port id %s name "%s" network-id %s' % ( - port_id, port_name, network_id)) - elif device_owner == n_const.DEVICE_OWNER_DVR_INTERFACE: - cmd2.append('instance id %s type router' % device_id) - cmd2.append('port id %s network-id %s hostid %s' % ( - port_id, network_id, host)) - net_count += 1 - - self._verify_send_eapi_request_calls(mock_send_eapi_req, [cmd1, cmd2]) - - @patch(EAPI_SEND_FUNC) - def test_delete_tenant(self, mock_send_eapi_req): - tenant_id = 'ten-1' - self.drv.delete_tenant(tenant_id) - cmd1 = ['show openstack agent uuid'] - cmd2 = ['enable', 'configure', 'cvx', 'service openstack', - 'region RegionOne', 'no tenant ten-1', - ] - self._verify_send_eapi_request_calls(mock_send_eapi_req, [cmd1, cmd2]) - - @patch(EAPI_SEND_FUNC) - def test_delete_tenant_bulk(self, mock_send_eapi_req): - num_tenants = 10 - tenant_list = ['ten-%d' % t_id for t_id in range(1, num_tenants)] - self.drv.delete_tenant_bulk(tenant_list) - cmd1 = ['show openstack agent uuid'] - cmd2 = ['enable', - 'configure', - 'cvx', - 'service openstack', - 'region RegionOne'] - for ten_id in range(1, num_tenants): - cmd2.append('no tenant ten-%d' % ten_id) - - self._verify_send_eapi_request_calls(mock_send_eapi_req, [cmd1, cmd2]) - - def test_get_network_info_returns_none_when_no_such_net(self): - expected = [] - self.drv.get_tenants = mock.MagicMock() - self.drv.get_tenants.return_value = [] - - net_info = self.drv.get_tenants() - - self.drv.get_tenants.assert_called_once_with() - self.assertEqual(net_info, expected, ('Network info must be "None"' - 'for unknown network')) - - def test_get_network_info_returns_info_for_available_net(self): - valid_network_id = '12345' - valid_net_info = {'network_id': valid_network_id, - 'some_info': 'net info'} - known_nets = valid_net_info - - self.drv.get_tenants = mock.MagicMock() - self.drv.get_tenants.return_value = known_nets - - net_info = self.drv.get_tenants() - self.assertEqual(net_info, valid_net_info, - ('Must return network info for a valid net')) - - @patch(EAPI_SEND_FUNC) - def test_check_supported_features(self, mock_send_eapi_req): - self.drv._get_random_name = mock.MagicMock() - self.drv._get_random_name.return_value = 'RegionOne' - - self.drv.check_supported_features() - - get_eos_master_cmd = ['show openstack agent uuid'] - instance_command = ['show openstack instances'] - cmds = [get_eos_master_cmd, instance_command] - - calls = [] - calls.extend(mock.call(cmds=cmd, commands_to_log=cmd) for cmd in cmds) - mock_send_eapi_req.assert_has_calls(calls) - - @patch(EAPI_SEND_FUNC) - def test_register_with_eos(self, mock_send_eapi_req): - self.drv.register_with_eos() - auth = utils.fake_keystone_info_class() - keystone_url = '%s://%s:%s/v3/' % (auth.auth_protocol, - auth.auth_host, - auth.auth_port) - auth_cmd = ('auth url %s user %s password %s tenant %s' % ( - keystone_url, - auth.admin_user, - auth.admin_password, - auth.admin_tenant_name)) - cmd1 = ['show openstack agent uuid'] - cmd2 = ['enable', - 'configure', - 'cvx', - 'service openstack', - 'region %s' % self.region, - auth_cmd, - 'sync interval %d' % cfg.CONF.ml2_arista.sync_interval, - ] - - clean_cmd2 = list(cmd2) - idx = clean_cmd2.index(auth_cmd) - clean_cmd2[idx] = clean_cmd2[idx].replace(auth.admin_password, - '******') - - self._verify_send_eapi_request_calls( - mock_send_eapi_req, - [cmd1, cmd2], - commands_to_log=[cmd1, clean_cmd2]) - - def _enable_sync_cmds(self): - self.drv.cli_commands[ - arista_ml2.CMD_REGION_SYNC] = 'region RegionOne sync' - self.drv.cli_commands[arista_ml2.CMD_SYNC_HEARTBEAT] = 'sync heartbeat' - self.drv.cli_commands['baremetal'] = '' - - @patch(EAPI_SEND_FUNC) - def test_create_network_bulk_during_sync(self, mock_send_eapi_req): - self._enable_sync_cmds() - self.drv.cli_commands['features'] = {'hierarchical-port-binding': 1} - tenant_id = 'ten-10' - num_networks = 101 - segments = [{'segmentation_id': 101, - 'physical_network': 'default', - 'id': 'segment_id_1', - 'network_type': 'vlan'}] - networks = [{ - 'network_id': 'net-id-%d' % net_id, - 'network_name': 'net-name-%d' % net_id, - 'segments': segments, - 'shared': True, - } for net_id in range(1, num_networks + 1) - ] - - self.drv.create_network_bulk(tenant_id, networks, sync=True) - cmd1 = ['show openstack agent uuid'] - cmd2 = ['enable', - 'configure', - 'cvx', - 'service openstack', - 'region RegionOne sync', - 'tenant ten-10'] - - # Send 100 create network commands - for net_id in range(1, 101): - cmd2.append('network id net-id-%d name "net-name-%d"' % - (net_id, net_id)) - for seg in segments: - is_dynamic = seg.get('is_dynamic', False) - cmd2.append('segment %s type %s id %d %s' % (seg['id'], - seg['network_type'], seg['segmentation_id'], - 'dynamic' if is_dynamic else 'static')) - cmd2.append('shared') - - # Send heartbeat - cmd2.append('sync heartbeat') - # Send the remaining network - cmd2.append('network id net-id-101 name "net-name-101"') - for seg in segments: - is_dynamic = seg.get('is_dynamic', False) - cmd2.append('segment %s type %s id %d %s' % (seg['id'], - seg['network_type'], seg['segmentation_id'], - 'dynamic' if is_dynamic else 'static')) - cmd2.append('shared') - # Send the final heartbeat - cmd2.append('sync heartbeat') - - self._verify_send_eapi_request_calls(mock_send_eapi_req, [cmd1, cmd2]) - - @patch(EAPI_SEND_FUNC) - def test_delete_network_bulk_during_sync(self, mock_send_eapi_req): - self._enable_sync_cmds() - tenant_id = 'ten-10' - num_networks = 101 - networks = ['nid-%d' % net_id for net_id in range(1, num_networks + 1)] - - self.drv.delete_network_bulk(tenant_id, networks, sync=True) - cmd1 = ['show openstack agent uuid'] - cmd2 = ['enable', - 'configure', - 'cvx', - 'service openstack', - 'region RegionOne sync', - 'tenant ten-10'] - - # Send 100 create network commands - for net_id in range(1, 101): - cmd2.append('no network id nid-%d' % (net_id)) - - # Send heartbeat - cmd2.append('sync heartbeat') - # Send the remaining network - cmd2.append('no network id nid-101') - # Send the final heartbeat - cmd2.append('sync heartbeat') - - self._verify_send_eapi_request_calls(mock_send_eapi_req, [cmd1, cmd2]) - - @patch(EAPI_SEND_FUNC) - def test_delete_vm_bulk_during_sync(self, mock_send_eapi_req): - self._enable_sync_cmds() - tenant_id = 'ten-2' - num_vms = 101 - vm_ids = ['vm-id-%d' % vm_id for vm_id in range(1, num_vms + 1)] - self.drv.delete_vm_bulk(tenant_id, vm_ids, sync=True) - - cmd1 = ['show openstack agent uuid'] - cmd2 = ['enable', - 'configure', - 'cvx', - 'service openstack', - 'region RegionOne sync', - 'tenant ten-2'] - - for vm_id in range(1, 101): - cmd2.append('no vm id vm-id-%d' % vm_id) - - # Send heartbeat - cmd2.append('sync heartbeat') - # Send the remaining vm - cmd2.append('no vm id vm-id-101') - # Send the final heartbeat - cmd2.append('sync heartbeat') - - self._verify_send_eapi_request_calls(mock_send_eapi_req, [cmd1, cmd2]) - - @patch(EAPI_SEND_FUNC) - def test_create_port_bulk_during_sync(self, mock_send_eapi_req): - self._enable_sync_cmds() - tenant_id = 'ten-3' - num_devices = 101 - num_ports_per_device = 2 - - device_count = 0 - devices = {} - for device_id in range(1, num_devices): - device_count += 1 - dev_id = 'dev-id-%d' % device_id - devices[dev_id] = {'vmId': dev_id, - 'baremetal_instance': False, - 'ports': [] - } - for port_id in range(1, num_ports_per_device + 1): - port_id = 'port-id-%d-%d' % (device_id, port_id) - port = { - 'device_id': 'dev-id-%d' % device_id, - 'hosts': ['host_%d' % (device_count)], - 'portId': port_id - } - devices[dev_id]['ports'].append(port) - - device_owners = [n_const.DEVICE_OWNER_DHCP, 'compute', - n_const.DEVICE_OWNER_DVR_INTERFACE] - - port_list = [] - net_count = 1 - for device_id in range(1, num_devices): - for port_id in range(1, num_ports_per_device + 1): - port = { - 'portId': 'port-id-%d-%d' % (device_id, port_id), - 'device_id': 'dev-id-%d' % device_id, - 'device_owner': device_owners[(device_id + port_id) % 3], - 'network_id': 'network-id-%d' % net_count, - 'name': 'port-%d-%d' % (device_id, port_id), - 'tenant_id': tenant_id - } - port_list.append(port) - net_count += 1 - - create_ports = {} - port_profiles = {} - for port in port_list: - create_ports.update(port_dict_representation(port)) - port_profiles[port['portId']] = {'vnic_type': 'normal'} - - self.drv.cli_commands[arista_ml2.CMD_INSTANCE] = 'instance' - self.drv.create_instance_bulk(tenant_id, create_ports, devices, - port_profiles=port_profiles, sync=True) - cmd1 = ['show openstack agent uuid'] - cmd2 = ['enable', - 'configure', - 'cvx', - 'service openstack', - 'region RegionOne sync', - 'tenant ten-3'] - - for count, device in enumerate(devices.values(), 1): - for v_port in device['ports']: - port_id = v_port['portId'] - port = create_ports[port_id] - host = v_port['hosts'][0] - vm_id = device['vmId'] - port_name = port['name'] - network_id = port['network_id'] - device_owner = port['device_owner'] - device_id = port['device_id'] - - if device_owner == n_const.DEVICE_OWNER_DHCP: - cmd2.append('network id %s' % network_id) - cmd2.append('dhcp id %s hostid %s port-id %s name "%s"' % ( - vm_id, host, port_id, port_name)) - elif device_owner == 'compute': - cmd2.append('vm id %s hostid %s' % (vm_id, host)) - cmd2.append('port id %s name "%s" network-id %s' % ( - port_id, port_name, network_id)) - elif device_owner == n_const.DEVICE_OWNER_DVR_INTERFACE: - cmd2.append('instance id %s type router' % device_id) - cmd2.append('port id %s network-id %s hostid %s' % ( - port_id, network_id, host)) - if count == (num_devices - 1): - # Send heartbeat - cmd2.append('sync heartbeat') - - # Send the final heartbeat - cmd2.append('sync heartbeat') - - self._verify_send_eapi_request_calls(mock_send_eapi_req, [cmd1, cmd2]) - - @patch(EAPI_SEND_FUNC) - def test_delete_tenant_bulk_during_sync(self, mock_send_eapi_req): - self._enable_sync_cmds() - num_tenants = 101 - tenant_list = ['ten-%d' % t_id for t_id in range(1, num_tenants + 1)] - self.drv.delete_tenant_bulk(tenant_list, sync=True) - cmd1 = ['show openstack agent uuid'] - cmd2 = ['enable', - 'configure', - 'cvx', - 'service openstack', - 'region RegionOne sync'] - for ten_id in range(1, num_tenants + 1): - cmd2.append('no tenant ten-%d' % ten_id) - - cmd2.append('sync heartbeat') - - self._verify_send_eapi_request_calls(mock_send_eapi_req, [cmd1, cmd2]) - - -class AristaRPCWrapperInvalidConfigTestCase(base.BaseTestCase): - """Negative test cases to test the Arista Driver configuration.""" - - def setUp(self): - super(AristaRPCWrapperInvalidConfigTestCase, self).setUp() - self.setup_invalid_config() # Invalid config, required options not set - - def setup_invalid_config(self): - utils.setup_arista_wrapper_config(cfg, host='', user='') - - def test_raises_exception_on_wrong_configuration(self): - ndb = db_lib.NeutronNets() - self.assertRaises(arista_exc.AristaConfigError, - arista_ml2.AristaRPCWrapperEapi, ndb) - - -class NegativeRPCWrapperTestCase(testlib_api.SqlTestCase): - """Negative test cases to test the RPC between Arista Driver and EOS.""" - - def setUp(self): - super(NegativeRPCWrapperTestCase, self).setUp() - setup_valid_config() - - def test_exception_is_raised_on_json_server_error(self): - ndb = db_lib.NeutronNets() - drv = arista_ml2.AristaRPCWrapperEapi(ndb) - - drv._send_api_request = mock.MagicMock( - side_effect=Exception('server error') - ) - with mock.patch.object(arista_ml2.LOG, 'error') as log_err: - self.assertRaises(arista_exc.AristaRpcError, drv.get_tenants) - log_err.assert_called_once_with(mock.ANY) - - -class RealNetStorageAristaDriverTestCase(testlib_api.SqlTestCase): - """Main test cases for Arista Mechanism driver. - - Tests all mechanism driver APIs supported by Arista Driver. It invokes - all the APIs as they would be invoked in real world scenarios and - verifies the functionality. - """ - def setUp(self): - super(RealNetStorageAristaDriverTestCase, self).setUp() - setup_valid_config() - self.fake_rpc = mock.MagicMock() - self.drv = mechanism_arista.AristaDriver(self.fake_rpc) - - def tearDown(self): - super(RealNetStorageAristaDriverTestCase, self).tearDown() - - def test_create_and_delete_network(self): - tenant_id = 'ten-1' - network_id = 'net1-id' - segmentation_id = 1001 - - network_context = self._get_network_context(tenant_id, - network_id, - segmentation_id) - self.drv.create_network_precommit(network_context) - net_provisioned = db_lib.is_network_provisioned(tenant_id, network_id) - self.assertTrue(net_provisioned, 'The network should be created') - - expected_num_nets = 1 - num_nets_provisioned = db_lib.num_nets_provisioned(tenant_id) - self.assertEqual(expected_num_nets, num_nets_provisioned, - 'There should be %d nets, not %d' % - (expected_num_nets, num_nets_provisioned)) - - # Now test the delete network - self.drv.delete_network_precommit(network_context) - net_provisioned = db_lib.is_network_provisioned(tenant_id, network_id) - self.assertFalse(net_provisioned, 'The network should be created') - - expected_num_nets = 0 - num_nets_provisioned = db_lib.num_nets_provisioned(tenant_id) - self.assertEqual(expected_num_nets, num_nets_provisioned, - 'There should be %d nets, not %d' % - (expected_num_nets, num_nets_provisioned)) - - def test_create_and_delete_multiple_networks(self): - tenant_id = 'ten-1' - expected_num_nets = 100 - segmentation_id = 1001 - nets = ['id%s' % n for n in range(expected_num_nets)] - for net_id in nets: - network_context = self._get_network_context(tenant_id, - net_id, - segmentation_id) - self.drv.create_network_precommit(network_context) - - num_nets_provisioned = db_lib.num_nets_provisioned(tenant_id) - self.assertEqual(expected_num_nets, num_nets_provisioned, - 'There should be %d nets, not %d' % - (expected_num_nets, num_nets_provisioned)) - - # Now test the delete networks - for net_id in nets: - network_context = self._get_network_context(tenant_id, - net_id, - segmentation_id) - self.drv.delete_network_precommit(network_context) - - num_nets_provisioned = db_lib.num_nets_provisioned(tenant_id) - expected_num_nets = 0 - self.assertEqual(expected_num_nets, num_nets_provisioned, - 'There should be %d nets, not %d' % - (expected_num_nets, num_nets_provisioned)) - - def test_create_and_delete_ports(self): - tenant_id = 'ten-1' - network_id = 'net1-id' - segmentation_id = 1001 - vms = ['vm1', 'vm2', 'vm3'] - - network_context = self._get_network_context(tenant_id, - network_id, - segmentation_id) - self.drv.create_network_precommit(network_context) - - for vm_id in vms: - port_id = '%s_%s' % (vm_id, 101) - port_context = self._get_port_context(port_id, - tenant_id, - network_id, - vm_id, - network_context) - self.drv.update_port_precommit(port_context) - - vm_list = db_lib.get_vms(tenant_id) - provisioned_vms = len(vm_list) - expected_vms = len(vms) - self.assertEqual(expected_vms, provisioned_vms, - 'There should be %d ' - 'hosts, not %d' % (expected_vms, provisioned_vms)) - - # Now test the delete ports - for vm_id in vms: - port_id = '%s_%s' % (vm_id, 101) - port_context = self._get_port_context(port_id, - tenant_id, - network_id, - vm_id, - network_context) - self.drv.delete_port_precommit(port_context) - - vm_list = db_lib.get_vms(tenant_id) - provisioned_vms = len(vm_list) - expected_vms = 0 - self.assertEqual(expected_vms, provisioned_vms, - 'There should be %d ' - 'VMs, not %d' % (expected_vms, provisioned_vms)) - - def test_cleanup_on_start(self): - """Ensures that the driver cleans up the arista database on startup.""" - ndb = db_lib.NeutronNets() - - # create a shared session - session = db_api.get_writer_session() - - # Create some networks in neutron db - n1_context = self._get_network_context('t1', 'n1', 10, session) - ndb.create_network(n1_context, {'network': n1_context.current}) - n2_context = self._get_network_context('t2', 'n2', 20, session) - ndb.create_network(n2_context, {'network': n2_context.current}) - n3_context = self._get_network_context('', 'ha-network', 100, session) - ndb.create_network(n3_context, {'network': n3_context.current}) - - # Objects were created in different sessions, but Neutron no longer - # implicitly flushes subtransactions. - session.flush() - - # Create some networks in Arista db - db_lib.remember_network_segment('t1', 'n1', 10, 'segment_id_10') - db_lib.remember_network_segment('t2', 'n2', 20, 'segment_id_20') - db_lib.remember_network_segment('admin', - 'ha-network', 100, 'segment_id_100') - db_lib.remember_network_segment('t3', 'n3', 30, 'segment_id_30') - - # Initialize the driver which should clean up the extra networks - self.drv.initialize() - - worker = self.drv.get_workers()[0] - - with mock.patch.object(worker.sync_service, 'do_synchronize') as ds: - worker.start() - adb_networks = db_lib.get_networks(tenant_id='any') - - # 'n3' should now be deleted from the Arista DB - self.assertEqual( - set(('n1', 'n2', 'ha-network')), - set(adb_networks.keys()) - ) - - ds.assert_called_once_with() - - def _get_network_context(self, tenant_id, net_id, seg_id, session=None): - network = {'id': net_id, - 'tenant_id': tenant_id, - 'name': net_id, - 'admin_state_up': True, - 'shared': False, - } - network_segments = [{'segmentation_id': seg_id, - 'id': 'segment_%s' % net_id, - 'network_type': 'vlan'}] - return FakeNetworkContext(network, network_segments, network, session) - - def _get_port_context(self, port_id, tenant_id, net_id, vm_id, network): - port = {'device_id': vm_id, - 'device_owner': 'compute', - 'binding:host_id': 'ubuntu1', - 'binding:vnic_type': 'normal', - 'tenant_id': tenant_id, - 'id': port_id, - 'network_id': net_id, - 'name': '', - 'status': 'ACTIVE', - } - binding_levels = [] - for level, segment in enumerate(network.network_segments): - binding_levels.append(FakePortBindingLevel(port['id'], - level, - 'vendor-1', - segment['id'])) - return FakePortContext(port, port, network, port['status'], - binding_levels) - - -class FakeNetworkContext(object): - """To generate network context for testing purposes only.""" - - def __init__(self, network, segments=None, original_network=None, - session=None): - self._network = network - self._original_network = original_network - self._segments = segments - self.is_admin = False - self.tenant_id = network['tenant_id'] - self.session = session or db_api.get_reader_session() - - @property - def current(self): - return self._network - - @property - def original(self): - return self._original_network - - @property - def network_segments(self): - return self._segments - - -class FakePluginContext(object): - """Plugin context for testing purposes only.""" - - def __init__(self, tenant_id): - self.tenant_id = tenant_id - self.session = mock.MagicMock() - - -class FakePortContext(object): - """To generate port context for testing purposes only.""" - - def __init__(self, port, original_port, network, status, - binding_levels): - self._plugin_context = None - self._port = port - self._original_port = original_port - self._network_context = network - self._status = status - self._binding_levels = binding_levels - - @property - def current(self): - return self._port - - @property - def original(self): - return self._original_port - - @property - def network(self): - return self._network_context - - @property - def host(self): - return self._port.get(portbindings.HOST_ID) - - @property - def original_host(self): - return self._original_port.get(portbindings.HOST_ID) - - @property - def status(self): - return self._status - - @property - def original_status(self): - if self._original_port: - return self._original_port['status'] - - @property - def binding_levels(self): - if self._binding_levels: - return [{ - driver_api.BOUND_DRIVER: level.driver, - driver_api.BOUND_SEGMENT: - self._expand_segment(level.segment_id) - } for level in self._binding_levels] - - @property - def bottom_bound_segment(self): - if self._binding_levels: - return self._expand_segment(self._binding_levels[-1].segment_id) - - def _expand_segment(self, segment_id): - for segment in self._network_context.network_segments: - if segment[driver_api.ID] == segment_id: - return segment - - -class FakePortBindingLevel(object): - """Port binding object for testing purposes only.""" - - def __init__(self, port_id, level, driver, segment_id): - self.port_id = port_id - self.level = level - self.driver = driver - self.segment_id = segment_id - - -class SyncServiceTest(testlib_api.SqlTestCase): - """Test cases for the sync service.""" - - def setUp(self): - super(SyncServiceTest, self).setUp() - plugin_klass = importutils.import_class( - "neutron.db.db_base_plugin_v2.NeutronDbPluginV2") - directory.add_plugin(plugin_constants.CORE, plugin_klass()) - self.rpc = mock.MagicMock() - ndb = db_lib.NeutronNets() - self.sync_service = arista_ml2.SyncService(self.rpc, ndb) - self.sync_service._force_sync = False - - def test_region_in_sync(self): - """Tests whether the region_in_sync() behaves as expected.""" - region_updated_time = { - 'regionName': 'RegionOne', - 'regionTimestamp': '12345' - } - self.rpc.get_region_updated_time.return_value = region_updated_time - self.sync_service._region_updated_time = None - assert not self.sync_service._region_in_sync() - self.sync_service._region_updated_time = region_updated_time - assert self.sync_service._region_in_sync() - - def test_synchronize_required(self): - """Tests whether synchronize() sends the right commands. - - This test verifies a scenario when the sync is required. - """ - region_updated_time = { - 'regionName': 'RegionOne', - 'regionTimestamp': '12345' - } - self.rpc.get_region_updated_time.return_value = region_updated_time - self.sync_service._region_updated_time = { - 'regionName': 'RegionOne', - 'regionTimestamp': '0', - } - - tenant_id = 'tenant-1' - network_id = 'net-1' - segmentation_id = 42 - segment_id = 'segment_id_1' - db_lib.remember_tenant(tenant_id) - db_lib.remember_network_segment(tenant_id, network_id, segmentation_id, - segment_id) - - self.rpc.get_tenants.return_value = {} - - self.rpc.sync_start.return_value = True - self.rpc.sync_end.return_value = True - self.rpc.check_cvx_availability.return_value = True - - self.rpc._baremetal_supported.return_value = False - self.rpc.get_all_baremetal_hosts.return_value = {} - - self.sync_service.do_synchronize() - - expected_calls = [ - mock.call.perform_sync_of_sg(), - mock.call.check_cvx_availability(), - mock.call.get_region_updated_time(), - mock.call.sync_start(), - mock.call.register_with_eos(sync=True), - mock.call.check_supported_features(), - mock.call.get_tenants(), - mock.call.create_network_bulk( - tenant_id, - [{'network_id': network_id, - 'segments': [], - 'network_name': '', - 'shared': False}], - sync=True), - mock.call.sync_end(), - mock.call.get_region_updated_time() - ] - self.assertTrue(self.rpc.mock_calls == expected_calls, - "Seen: %s\nExpected: %s" % ( - self.rpc.mock_calls, - expected_calls, - ) - ) - - db_lib.forget_network_segment(tenant_id, network_id) - db_lib.forget_tenant(tenant_id) - - def test_synchronize_not_required(self): - """Tests whether synchronize() sends the right commands. - - This test verifies a scenario when the sync is not required. - """ - region_updated_time = { - 'regionName': 'RegionOne', - 'regionTimestamp': '424242' - } - self.rpc.get_region_updated_time.return_value = region_updated_time - self.rpc.check_cvx_availability.return_value = True - self.sync_service._region_updated_time = { - 'regionName': 'RegionOne', - 'regionTimestamp': '424242', - } - - self.rpc.sync_start.return_value = True - self.rpc.sync_end.return_value = True - - self.sync_service.do_synchronize() - - # If the timestamps do match, then the sync should not be executed. - expected_calls = [ - mock.call.perform_sync_of_sg(), - mock.call.check_cvx_availability(), - mock.call.get_region_updated_time(), - ] - self.assertTrue(self.rpc.mock_calls[:4] == expected_calls, - "Seen: %s\nExpected: %s" % ( - self.rpc.mock_calls, - expected_calls, - ) - ) - - def test_synchronize_one_network(self): - """Test to ensure that only the required resources are sent to EOS.""" - - # Store two tenants in a db and a single tenant in EOS. - # The sync should send details of the second tenant to EOS - tenant_1_id = 'tenant-1' - tenant_1_net_1_id = 'ten-1-net-1' - tenant_1_net_1_seg_id = 11 - db_lib.remember_tenant(tenant_1_id) - db_lib.remember_network_segment(tenant_1_id, tenant_1_net_1_id, - tenant_1_net_1_seg_id, 'segment_id_11') - - tenant_2_id = 'tenant-2' - tenant_2_net_1_id = 'ten-2-net-1' - tenant_2_net_1_seg_id = 21 - db_lib.remember_tenant(tenant_2_id) - db_lib.remember_network_segment(tenant_2_id, tenant_2_net_1_id, - tenant_2_net_1_seg_id, 'segment_id_21') - - self.rpc.get_tenants.return_value = { - tenant_1_id: { - 'tenantVmInstances': {}, - 'tenantBaremetalInstances': {}, - 'tenantNetworks': { - tenant_1_net_1_id: { - 'networkId': tenant_1_net_1_id, - 'shared': False, - 'networkName': 'Net1', - 'segmenationType': 'vlan', - 'segmentationTypeId': tenant_1_net_1_seg_id, - } - } - } - } - - self.rpc.sync_start.return_value = True - self.rpc.sync_end.return_value = True - self.rpc.check_cvx_availability.return_value = True - self.rpc.get_region_updated_time.return_value = {'regionTimestamp': 1} - - self.rpc._baremetal_supported.return_value = False - self.rpc.get_all_baremetal_hosts.return_value = {} - - self.sync_service.do_synchronize() - - expected_calls = [ - mock.call.perform_sync_of_sg(), - mock.call.check_cvx_availability(), - mock.call.get_region_updated_time(), - mock.call.sync_start(), - mock.call.register_with_eos(sync=True), - mock.call.check_supported_features(), - mock.call.get_tenants(), - - mock.call.create_network_bulk( - tenant_2_id, - [{'network_id': tenant_2_net_1_id, - 'segments': [], - 'network_name': '', - 'shared': False}], - sync=True), - - mock.call.sync_end(), - mock.call.get_region_updated_time() - ] - - self.rpc.assert_has_calls(expected_calls) - - db_lib.forget_network_segment(tenant_1_id, tenant_1_net_1_id) - db_lib.forget_network_segment(tenant_2_id, tenant_2_net_1_id) - db_lib.forget_tenant(tenant_1_id) - db_lib.forget_tenant(tenant_2_id) - - def test_synchronize_all_networks(self): - """Test to ensure that only the required resources are sent to EOS.""" - - # Store two tenants in a db and none on EOS. - # The sync should send details of all tenants to EOS - tenant_1_id = u'tenant-1' - tenant_1_net_1_id = u'ten-1-net-1' - tenant_1_net_1_seg_id = 11 - db_lib.remember_tenant(tenant_1_id) - db_lib.remember_network_segment(tenant_1_id, tenant_1_net_1_id, - tenant_1_net_1_seg_id, 'segment_id_11') - - tenant_2_id = u'tenant-2' - tenant_2_net_1_id = u'ten-2-net-1' - tenant_2_net_1_seg_id = 21 - db_lib.remember_tenant(tenant_2_id) - db_lib.remember_network_segment(tenant_2_id, tenant_2_net_1_id, - tenant_2_net_1_seg_id, 'segment_id_21') - - self.rpc.get_tenants.return_value = {} - - self.rpc.sync_start.return_value = True - self.rpc.sync_end.return_value = True - self.rpc.check_cvx_availability.return_value = True - self.rpc.get_region_updated_time.return_value = {'regionTimestamp': 1} - - self.rpc._baremetal_supported.return_value = False - self.rpc.get_all_baremetal_hosts.return_value = {} - - self.sync_service.do_synchronize() - - expected_calls = [ - mock.call.perform_sync_of_sg(), - mock.call.check_cvx_availability(), - mock.call.get_region_updated_time(), - mock.call.sync_start(), - mock.call.register_with_eos(sync=True), - mock.call.check_supported_features(), - mock.call.get_tenants(), - - mock.call.create_network_bulk( - tenant_1_id, - [{'network_id': tenant_1_net_1_id, - 'segments': [], - 'network_name': '', - 'shared': False}], - sync=True), - - mock.call.create_network_bulk( - tenant_2_id, - [{'network_id': tenant_2_net_1_id, - 'segments': [], - 'network_name': '', - 'shared': False}], - sync=True), - mock.call.sync_end(), - mock.call.get_region_updated_time() - ] - - # The create_network_bulk() can be called in different order. So split - # it up. The first part checks if the initial set of methods are - # invoked. - idx = expected_calls.index(mock.call.get_tenants()) + 1 - self.assertTrue(self.rpc.mock_calls[:idx] == expected_calls[:idx], - "Seen: %s\nExpected: %s" % ( - self.rpc.mock_calls, - expected_calls, - ) - ) - # Check if tenant 1 networks are created. It must be one of the two - # methods. - self.assertTrue(self.rpc.mock_calls[idx] in - expected_calls[idx:idx + 2], - "Seen: %s\nExpected: %s" % ( - self.rpc.mock_calls, - expected_calls, - ) - ) - # Check if tenant 2 networks are created. It must be one of the two - # methods. - self.assertTrue(self.rpc.mock_calls[idx + 1] in - expected_calls[idx:idx + 2], - "Seen: %s\nExpected: %s" % ( - self.rpc.mock_calls, - expected_calls, - ) - ) - # Check if the sync end methods are invoked. - self.assertTrue(self.rpc.mock_calls[idx + 2:] == - expected_calls[idx + 2:], - "Seen: %s\nExpected: %s" % ( - self.rpc.mock_calls, - expected_calls, - ) - ) - - db_lib.forget_network_segment(tenant_1_id, tenant_1_net_1_id) - db_lib.forget_network_segment(tenant_2_id, tenant_2_net_1_id) - db_lib.forget_tenant(tenant_1_id) - db_lib.forget_tenant(tenant_2_id) diff --git a/networking_arista/tests/unit/ml2/test_arista_type_driver.py b/networking_arista/tests/unit/ml2/test_arista_type_driver.py deleted file mode 100644 index c3324e5..0000000 --- a/networking_arista/tests/unit/ml2/test_arista_type_driver.py +++ /dev/null @@ -1,111 +0,0 @@ -# Copyright (c) 2016 OpenStack Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import itertools - -import mock -from mock import patch -from neutron_lib.db import api as db_api -from oslo_config import cfg - -from neutron.db.models.plugins.ml2 import vlanallocation -from neutron.tests.unit import testlib_api - -from networking_arista.ml2.drivers.driver_helpers import VlanSyncService -from networking_arista.ml2.drivers.type_arista_vlan import AristaVlanTypeDriver -import networking_arista.tests.unit.ml2.utils as utils - - -EAPI_SEND_FUNC = ('networking_arista.ml2.arista_ml2.AristaRPCWrapperEapi' - '._send_eapi_req') - - -class AristaTypeDriverTest(testlib_api.SqlTestCase): - - def setUp(self): - super(AristaTypeDriverTest, self).setUp() - utils.setup_arista_wrapper_config(cfg) - - @patch(EAPI_SEND_FUNC) - def test_initialize_type_driver(self, mock_send_eapi_req): - type_driver = AristaVlanTypeDriver() - type_driver.sync_service._force_sync = False - type_driver.sync_service._vlan_assignment_uuid = {'uuid': 1} - type_driver.sync_service._rpc = mock.MagicMock() - rpc = type_driver.sync_service._rpc - rpc.get_vlan_assignment_uuid.return_value = {'uuid': 1} - type_driver.initialize() - - cmds = ['show openstack agent uuid', - 'show openstack instances', - 'show openstack agent uuid', - 'show openstack features'] - - calls = [mock.call(cmds=[cmd], commands_to_log=[cmd]) - for cmd in cmds] - mock_send_eapi_req.assert_has_calls(calls) - type_driver.timer.cancel() - - -class VlanSyncServiceTest(testlib_api.SqlTestCase): - """Test that VLANs are synchronized between EOS and Neutron.""" - - def _ensure_in_db(self, assigned, allocated, available): - session = db_api.get_reader_session() - with session.begin(): - vlans = session.query(vlanallocation.VlanAllocation).all() - for vlan in vlans: - self.assertIn(vlan.vlan_id, assigned) - - if vlan.vlan_id in available: - self.assertFalse(vlan.allocated) - elif vlan.vlan_id in allocated: - self.assertTrue(vlan.allocated) - - def test_synchronization_test(self): - rpc = mock.MagicMock() - - rpc.get_vlan_allocation.return_value = { - 'assignedVlans': '1-10,21-30', - 'availableVlans': '1-5,21,23,25,27,29', - 'allocatedVlans': '6-10,22,24,26,28,30' - } - - assigned = list(itertools.chain(range(1, 11), range(21, 31))) - - available = [1, 2, 3, 4, 5, 21, 23, 25, 27, 29] - allocated = list(set(assigned) - set(available)) - - sync_service = VlanSyncService(rpc) - sync_service.synchronize() - - self._ensure_in_db(assigned, allocated, available) - - # Call synchronize again which returns different data - rpc.get_vlan_allocation.return_value = { - 'assignedVlans': '51-60,71-80', - 'availableVlans': '51-55,71,73,75,77,79', - 'allocatedVlans': '56-60,72,74,76,78,80' - } - - assigned = list(itertools.chain(range(51, 61), range(71, 81))) - - available = [51, 52, 53, 54, 55, 71, 73, 75, 77, 79] - allocated = list(set(assigned) - set(available)) - - sync_service = VlanSyncService(rpc) - sync_service.synchronize() - - self._ensure_in_db(assigned, allocated, available) diff --git a/networking_arista/tests/unit/ml2/test_mechanism_arista.py b/networking_arista/tests/unit/ml2/test_mechanism_arista.py deleted file mode 100644 index 4424fe0..0000000 --- a/networking_arista/tests/unit/ml2/test_mechanism_arista.py +++ /dev/null @@ -1,1488 +0,0 @@ -# Copyright (c) 2013 OpenStack Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import mock -from neutron_lib.api.definitions import portbindings -from neutron_lib import constants as n_const -from neutron_lib.plugins.ml2 import api as driver_api - -from neutron.common import constants as neutron_const -from neutron.tests.unit import testlib_api - -from networking_arista.ml2 import mechanism_arista - -INTERNAL_TENANT_ID = 'INTERNAL-TENANT-ID' - - -class AristaDriverTestCase(testlib_api.SqlTestCase): - """Main test cases for Arista Mechanism driver. - - Tests all mechanism driver APIs supported by Arista Driver. It invokes - all the APIs as they would be invoked in real world scenarios and - verifies the functionality. - """ - def setUp(self): - super(AristaDriverTestCase, self).setUp() - self.fake_rpc = mock.MagicMock() - mechanism_arista.db_lib = self.fake_rpc - self.drv = mechanism_arista.AristaDriver(self.fake_rpc) - self.drv.ndb = mock.MagicMock() - - def tearDown(self): - super(AristaDriverTestCase, self).tearDown() - - def test_create_network_precommit(self): - tenant_id = 'ten-1' - network_id = 'net1-id' - segmentation_id = 1001 - - self.drv.rpc.hpb_supported.return_value = True - network_context = self._get_network_context(tenant_id, - network_id, - segmentation_id, - False) - self.drv.create_network_precommit(network_context) - segment_id = network_context.network_segments[0]['id'] - - expected_calls = [ - mock.call.hpb_supported(), - mock.call.remember_tenant(tenant_id), - mock.call.remember_network_segment(tenant_id, - network_id, - segmentation_id, - segment_id) - ] - - mechanism_arista.db_lib.assert_has_calls(expected_calls) - - # If there is no tenant id associated with the network, then the - # network should be created under the tenant id in the context. - tenant_id = 'ten-2' - network_id = 'net2-id' - segmentation_id = 1002 - - network_context = self._get_network_context(tenant_id, - network_id, - segmentation_id, - False) - network_context.current['tenant_id'] = '' - self.drv.create_network_precommit(network_context) - segment_id = network_context.network_segments[0]['id'] - - expected_calls += [ - mock.call.hpb_supported(), - mock.call.remember_tenant(INTERNAL_TENANT_ID), - mock.call.remember_network_segment(INTERNAL_TENANT_ID, - network_id, - segmentation_id, - segment_id) - ] - - mechanism_arista.db_lib.assert_has_calls(expected_calls) - - def test_create_network_postcommit(self): - tenant_id = 'ten-1' - network_id = 'net1-id' - segmentation_id = 1001 - - network_context = self._get_network_context(tenant_id, - network_id, - segmentation_id, - False) - mechanism_arista.db_lib.is_network_provisioned.return_value = True - network = network_context.current - segments = network_context.network_segments - net_dict = { - 'network_id': network['id'], - 'segments': segments, - 'network_name': network['name'], - 'shared': network['shared']} - - self.drv.create_network_postcommit(network_context) - - expected_calls = [ - mock.call.is_network_provisioned(tenant_id, network_id), - mock.call.create_network(tenant_id, net_dict), - ] - - mechanism_arista.db_lib.assert_has_calls(expected_calls) - - # If there is no tenant id associated with the network, then the - # network should be created under the tenant id in the context. - tenant_id = 'ten-2' - network_id = 'net2-id' - segmentation_id = 1002 - - network_context = self._get_network_context(tenant_id, - network_id, - segmentation_id, - False) - network_context.current['tenant_id'] = '' - mechanism_arista.db_lib.is_network_provisioned.return_value = True - network = network_context.current - segments = network_context.network_segments - net_dict = { - 'network_id': network['id'], - 'segments': segments, - 'network_name': network['name'], - 'shared': network['shared']} - - self.drv.create_network_postcommit(network_context) - - expected_calls += [ - mock.call.is_network_provisioned(INTERNAL_TENANT_ID, network_id), - mock.call.create_network(INTERNAL_TENANT_ID, net_dict), - ] - - mechanism_arista.db_lib.assert_has_calls(expected_calls) - - def test_delete_network_precommit(self): - tenant_id = 'ten-1' - network_id = 'net1-id' - segmentation_id = 1001 - - network_context = self._get_network_context(tenant_id, - network_id, - segmentation_id, - False) - mechanism_arista.db_lib.is_network_provisioned.return_value = True - mechanism_arista.db_lib.num_nets_provisioned.return_value = 0 - mechanism_arista.db_lib.num_vms_provisioned.return_value = 0 - mechanism_arista.db_lib.are_ports_attached_to_network.return_value = ( - False) - self.drv.delete_network_precommit(network_context) - - expected_calls = [ - mock.call.is_network_provisioned(tenant_id, network_id), - mock.call.are_ports_attached_to_network(network_id), - mock.call.forget_network_segment(tenant_id, network_id), - ] - - mechanism_arista.db_lib.assert_has_calls(expected_calls) - - # If there is no tenant id associated with the network, then the - # network should be created under the tenant id in the context. - tenant_id = 'ten-2' - network_id = 'net2-id' - segmentation_id = 1002 - - network_context = self._get_network_context(tenant_id, - network_id, - segmentation_id, - False) - network_context.current['tenant_id'] = '' - mechanism_arista.db_lib.is_network_provisioned.return_value = True - mechanism_arista.db_lib.num_nets_provisioned.return_value = 0 - mechanism_arista.db_lib.num_vms_provisioned.return_value = 0 - mechanism_arista.db_lib.are_ports_attached_to_network.return_value = ( - False) - self.drv.delete_network_precommit(network_context) - - expected_calls += [ - mock.call.is_network_provisioned(INTERNAL_TENANT_ID, network_id), - mock.call.are_ports_attached_to_network(network_id), - mock.call.forget_network_segment(INTERNAL_TENANT_ID, network_id), - ] - - mechanism_arista.db_lib.assert_has_calls(expected_calls) - - def test_delete_network_precommit_with_ports(self): - tenant_id = 'ten-1' - network_id = 'net1-id' - segmentation_id = 1001 - - network_context = self._get_network_context(tenant_id, - network_id, - segmentation_id, - False) - mechanism_arista.db_lib.is_network_provisioned.return_value = True - mechanism_arista.db_lib.num_nets_provisioned.return_value = 0 - mechanism_arista.db_lib.num_vms_provisioned.return_value = 0 - mechanism_arista.db_lib.are_ports_attached_to_network.return_value = ( - True) - try: - self.drv.delete_network_precommit(network_context) - except Exception: - # exception is expeted in this case - as network is not - # deleted in this case and exception is raised - pass - - expected_calls = [ - mock.call.is_network_provisioned(tenant_id, network_id), - mock.call.are_ports_attached_to_network(network_id), - ] - - mechanism_arista.db_lib.assert_has_calls(expected_calls) - - def test_delete_network_postcommit(self): - tenant_id = 'ten-1' - network_id = 'net1-id' - segmentation_id = 1001 - - self.drv.rpc.hpb_supported.return_value = True - network_context = self._get_network_context(tenant_id, - network_id, - segmentation_id, - False) - - mechanism_arista.db_lib.num_nets_provisioned.return_value = 0 - mechanism_arista.db_lib.num_vms_provisioned.return_value = 0 - self.drv.delete_network_postcommit(network_context) - expected_calls = [ - mock.call.hpb_supported(), - mock.call.delete_network(tenant_id, network_id, - network_context.network_segments), - mock.call.num_nets_provisioned(tenant_id), - mock.call.num_vms_provisioned(tenant_id), - mock.call.forget_tenant(tenant_id), - mock.call.delete_tenant(tenant_id), - ] - - mechanism_arista.db_lib.assert_has_calls(expected_calls) - - # If there is no tenant id associated with the network, then the - # network should be created under the tenant id in the context. - tenant_id = 'ten-2' - network_id = 'net2-id' - segmentation_id = 1002 - - network_context = self._get_network_context(tenant_id, - network_id, - segmentation_id, - False) - network_context.current['tenant_id'] = '' - - self.drv.delete_network_postcommit(network_context) - expected_calls += [ - mock.call.hpb_supported(), - mock.call.delete_network(INTERNAL_TENANT_ID, network_id, - network_context.network_segments), - mock.call.num_nets_provisioned(INTERNAL_TENANT_ID), - mock.call.num_vms_provisioned(INTERNAL_TENANT_ID), - mock.call.forget_tenant(INTERNAL_TENANT_ID), - mock.call.delete_tenant(INTERNAL_TENANT_ID), - ] - - mechanism_arista.db_lib.assert_has_calls(expected_calls) - - def _test_create_port_precommit(self): - tenant_id = 'ten-1' - network_id = 'net1-id' - segmentation_id = 1001 - vm_id = 'vm1' - - network_context = self._get_network_context(tenant_id, - network_id, - segmentation_id, - False) - - port_context = self._get_port_context(tenant_id, - network_id, - vm_id, - network_context) - mechanism_arista.db_lib.is_network_provisioned.return_value = True - - network = {'tenant_id': tenant_id} - self.drv.ndb.get_network_from_net_id.return_value = [network] - - host_id = port_context.current['binding:host_id'] - port_id = port_context.current['id'] - self.drv.create_port_precommit(port_context) - - expected_calls = [ - mock.call.is_network_provisioned(tenant_id, network_id, None), - mock.call.remember_tenant(tenant_id), - mock.call.remember_vm(vm_id, host_id, port_id, - network_id, tenant_id) - ] - - mechanism_arista.db_lib.assert_has_calls(expected_calls) - - # If there is no tenant id associated with the network, then the - # network should be created under the tenant id in the context. - tenant_id = 'ten-2' - network_id = 'net2-id' - segmentation_id = 1002 - vm_id = 'vm2' - - network_context = self._get_network_context(tenant_id, - network_id, - segmentation_id, - False) - port_context = self._get_port_context(tenant_id, - network_id, - vm_id, - network_context) - port_context.current['tenant_id'] = '' - mechanism_arista.db_lib.is_network_provisioned.return_value = True - - network = {'tenant_id': ''} - self.drv.ndb.get_network_from_net_id.return_value = [network] - - host_id = port_context.current['binding:host_id'] - port_id = port_context.current['id'] - self.drv.create_port_precommit(port_context) - - expected_calls += [ - mock.call.is_network_provisioned(INTERNAL_TENANT_ID, network_id, - None), - mock.call.remember_tenant(INTERNAL_TENANT_ID), - mock.call.remember_vm(vm_id, host_id, port_id, - network_id, INTERNAL_TENANT_ID) - ] - mechanism_arista.db_lib.assert_has_calls(expected_calls) - - def _test_create_port_postcommit(self): - tenant_id = 'ten-1' - network_id = 'net1-id' - segmentation_id = 1001 - vm_id = 'vm1' - - network_context = self._get_network_context(tenant_id, - network_id, - segmentation_id, - False) - port_context = self._get_port_context(tenant_id, - network_id, - vm_id, - network_context) - mechanism_arista.db_lib.is_port_provisioned.return_value = True - mechanism_arista.db_lib.is_network_provisioned.return_value = True - mechanism_arista.db_lib.get_shared_network_owner_id.return_value = 1 - - network = {'tenant_id': tenant_id} - self.drv.ndb.get_network_from_net_id.return_value = [network] - - port = port_context.current - device_id = port['device_id'] - device_owner = port['device_owner'] - host_id = port['binding:host_id'] - port_id = port['id'] - port_name = port['name'] - profile = port['binding:profile'] - - self.drv.create_port_postcommit(port_context) - - expected_calls = [ - mock.call.NeutronNets(), - mock.call.is_port_provisioned(port_id), - mock.call.is_network_provisioned(tenant_id, network_id, None), - mock.call.plug_port_into_network(device_id, host_id, port_id, - network_id, tenant_id, - port_name, device_owner, None, - [], None, switch_bindings=profile) - ] - - mechanism_arista.db_lib.assert_has_calls(expected_calls) - - # If there is no tenant id associated with the network, then the - # network should be created under the tenant id in the context. - tenant_id = 'ten-2' - network_id = 'net2-id' - segmentation_id = 1002 - vm_id = 'vm2' - - network_context = self._get_network_context(tenant_id, - network_id, - segmentation_id, - False) - port_context = self._get_port_context(tenant_id, - network_id, - vm_id, - network_context) - port_context.current['tenant_id'] = '' - mechanism_arista.db_lib.is_port_provisioned.return_value = True - mechanism_arista.db_lib.is_network_provisioned.return_value = True - mechanism_arista.db_lib.get_shared_network_owner_id.return_value = 1 - - network = {'tenant_id': ''} - self.drv.ndb.get_network_from_net_id.return_value = [network] - - port = port_context.current - device_id = port['device_id'] - device_owner = port['device_owner'] - host_id = port['binding:host_id'] - port_id = port['id'] - port_name = port['name'] - - self.drv.create_port_postcommit(port_context) - - expected_calls += [ - mock.call.is_port_provisioned(port_id), - mock.call.is_network_provisioned(INTERNAL_TENANT_ID, network_id, - None), - mock.call.plug_port_into_network(device_id, host_id, port_id, - network_id, INTERNAL_TENANT_ID, - port_name, device_owner, None, - [], None, switch_bindings=profile) - ] - - mechanism_arista.db_lib.assert_has_calls(expected_calls) - - # Now test the delete ports - def test_delete_port_precommit(self): - tenant_id = 'ten-1' - network_id = 'net1-id' - segmentation_id = 1001 - vm_id = 'vm1' - - network_context = self._get_network_context(tenant_id, - network_id, - segmentation_id, - False) - - port_context = self._get_port_context(tenant_id, - network_id, - vm_id, - network_context) - mechanism_arista.db_lib.is_port_provisioned.return_value = True - mechanism_arista.db_lib.num_nets_provisioned.return_value = 0 - mechanism_arista.db_lib.num_vms_provisioned.return_value = 0 - self.drv.delete_port_precommit(port_context) - - port_id = port_context.current['id'] - expected_calls = [ - mock.call.is_port_provisioned(port_id, port_context.host), - mock.call.forget_port(port_id, port_context.host), - ] - - mechanism_arista.db_lib.assert_has_calls(expected_calls) - - # If there is no tenant id associated with the network, then the - # network should be created under the tenant id in the context. - tenant_id = 'ten-2' - network_id = 'net2-id' - segmentation_id = 1002 - vm_id = 'vm2' - - network_context = self._get_network_context(tenant_id, - network_id, - segmentation_id, - False) - port_context = self._get_port_context(tenant_id, - network_id, - vm_id, - network_context) - port_context.current['tenant_id'] = '' - mechanism_arista.db_lib.is_port_provisioned.return_value = True - mechanism_arista.db_lib.num_nets_provisioned.return_value = 0 - mechanism_arista.db_lib.num_vms_provisioned.return_value = 0 - self.drv.delete_port_precommit(port_context) - - port_id = port_context.current['id'] - expected_calls += [ - mock.call.is_port_provisioned(port_id, port_context.host), - mock.call.forget_port(port_id, port_context.host), - ] - - mechanism_arista.db_lib.assert_has_calls(expected_calls) - - def test_delete_port_postcommit(self): - tenant_id = 'ten-1' - network_id = 'net1-id' - segmentation_id = 1001 - vm_id = 'vm1' - - network_context = self._get_network_context(tenant_id, - network_id, - segmentation_id, - False) - port_context = self._get_port_context(tenant_id, - network_id, - vm_id, - network_context) - mechanism_arista.db_lib.num_nets_provisioned.return_value = 0 - mechanism_arista.db_lib.num_vms_provisioned.return_value = 0 - mechanism_arista.db_lib.is_network_provisioned.return_value = True - port = port_context.current - device_id = port['device_id'] - host_id = port['binding:host_id'] - port_id = port['id'] - vnic_type = port['binding:vnic_type'] - profile = port['binding:profile'] - - network = {'tenant_id': tenant_id} - self.drv.ndb.get_network_from_net_id.return_value = [network] - physnet = dict(physnet='default') - self.fake_rpc.get_physical_network.return_value = physnet - self.drv.rpc.hpb_supported.return_value = True - - self.drv.delete_port_postcommit(port_context) - - expected_calls = [ - mock.call.NeutronNets(), - mock.call.get_physical_network(host_id), - mock.call.is_network_provisioned(tenant_id, network_id, None, - None), - mock.call.unplug_port_from_network(device_id, 'compute', host_id, - port_id, network_id, tenant_id, - None, vnic_type, - switch_bindings=profile), - mock.call.remove_security_group(None, profile), - mock.call.num_nets_provisioned(tenant_id), - mock.call.num_vms_provisioned(tenant_id), - mock.call.forget_tenant(tenant_id), - mock.call.delete_tenant(tenant_id), - mock.call.hpb_supported(), - ] - for binding_level in port_context.binding_levels: - expected_calls.append(mock.call.is_network_provisioned(tenant_id, - network_id, None, - binding_level['bound_segment']['id'])) - mechanism_arista.db_lib.assert_has_calls(expected_calls) - - # If there is no tenant id associated with the network, then the - # network should be created under the tenant id in the context. - tenant_id = 'ten-2' - network_id = 'net2-id' - segmentation_id = 1002 - vm_id = 'vm2' - - network_context = self._get_network_context(tenant_id, - network_id, - segmentation_id, - False) - port_context = self._get_port_context(tenant_id, - network_id, - vm_id, - network_context) - port_context.current['tenant_id'] = '' - port = port_context.current - device_id = port['device_id'] - host_id = port['binding:host_id'] - port_id = port['id'] - - network = {'tenant_id': ''} - self.drv.ndb.get_network_from_net_id.return_value = [network] - physnet = dict(physnet='default') - self.fake_rpc.get_physical_network.return_value = physnet - - self.drv.delete_port_postcommit(port_context) - - expected_calls += [ - mock.call.get_physical_network(host_id), - mock.call.is_network_provisioned(INTERNAL_TENANT_ID, network_id, - None, None), - mock.call.unplug_port_from_network(device_id, 'compute', host_id, - port_id, network_id, - INTERNAL_TENANT_ID, None, - vnic_type, - switch_bindings=profile), - mock.call.remove_security_group(None, profile), - mock.call.num_nets_provisioned(INTERNAL_TENANT_ID), - mock.call.num_vms_provisioned(INTERNAL_TENANT_ID), - mock.call.forget_tenant(INTERNAL_TENANT_ID), - mock.call.delete_tenant(INTERNAL_TENANT_ID), - mock.call.hpb_supported(), - ] - for binding_level in port_context.binding_levels: - expected_calls.append(mock.call.is_network_provisioned( - INTERNAL_TENANT_ID, network_id, None, - binding_level['bound_segment']['id'])) - - mechanism_arista.db_lib.assert_has_calls(expected_calls) - - def test_update_port_precommit(self): - # Test the case where the port was not provisioned previsouly - # If port is not provisioned, we should bail out - mechanism_arista.db_lib.is_port_provisioned.return_value = False - mechanism_arista.db_lib.is_network_provisioned.return_value = False - - tenant_id = 'ten-1' - network_id = 'net1-id' - segmentation_id = 1001 - vm_id = 'vm1' - - network_context = self._get_network_context(tenant_id, - network_id, - segmentation_id, - False) - - port_context = self._get_port_context(tenant_id, - network_id, - vm_id, - network_context) - host_id = port_context.current['binding:host_id'] - port_id = port_context.current['id'] - - network = {'tenant_id': tenant_id} - self.drv.ndb.get_network_from_net_id.return_value = [network] - - # Make sure the port is not found - mechanism_arista.db_lib.is_port_provisioned.return_value = False - - self.drv.update_port_precommit(port_context) - segment_id = network_context.network_segments[-1]['id'] - - expected_calls = [ - mock.call.NeutronNets(), - mock.call.is_network_provisioned(tenant_id, network_id, - segmentation_id, - segment_id), - mock.call.is_port_provisioned(port_id, None), - mock.call.remember_tenant(tenant_id), - mock.call.remember_vm(vm_id, host_id, port_id, - network_id, tenant_id) - ] - - mechanism_arista.db_lib.assert_has_calls(expected_calls) - - # Test the case where the port was provisioned, but it was not on - # correct network. We should bail out in this case as well - tenant_id = 'ten-2' - network_id = 'net2-id' - segmentation_id = 1002 - vm_id = 'vm2' - - network_context = self._get_network_context(tenant_id, - network_id, - segmentation_id, - False) - port_context = self._get_port_context(tenant_id, - network_id, - vm_id, - network_context) - host_id = port_context.current['binding:host_id'] - port_id = port_context.current['id'] - - # Force the check to return port found, but, network was not found - mechanism_arista.db_lib.is_port_provisioned.return_value = True - mechanism_arista.db_lib.is_network_provisioned.return_value = False - network = {'tenant_id': tenant_id} - self.drv.ndb.get_network_from_net_id.return_value = [network] - - try: - self.drv.update_port_precommit(port_context) - except Exception: - # This shoud raise an exception as this is not permitted - # operation - pass - - segment_id = network_context.network_segments[-1]['id'] - expected_calls += [ - mock.call.is_network_provisioned(tenant_id, network_id, - segmentation_id, segment_id), - mock.call.is_port_provisioned(port_id, None), - ] - - mechanism_arista.db_lib.assert_has_calls(expected_calls) - - # If the tenant id is not specified, then the port should be created - # with internal tenant id. - tenant_id = 'ten-3' - network_id = 'net3-id' - segmentation_id = 1003 - vm_id = 'vm3' - - network_context = self._get_network_context(tenant_id, - network_id, - segmentation_id, - False) - port_context = self._get_port_context(tenant_id, - network_id, - vm_id, - network_context) - # Port does not contain a tenant - port_context.current['tenant_id'] = None - host_id = port_context.current['binding:host_id'] - port_id = port_context.current['id'] - - # Force the check to return port and network were found - mechanism_arista.db_lib.is_port_provisioned.return_value = True - mechanism_arista.db_lib.is_network_provisioned.return_value = True - network = {'tenant_id': None} - self.drv.ndb.get_network_from_net_id.return_value = [network] - - self.drv.update_port_precommit(port_context) - - segment_id = network_context.network_segments[-1]['id'] - expected_calls += [ - mock.call.is_network_provisioned(INTERNAL_TENANT_ID, network_id, - segmentation_id, segment_id), - mock.call.is_port_provisioned(port_id, None), - mock.call.update_port(vm_id, host_id, port_id, network_id, - INTERNAL_TENANT_ID) - ] - - mechanism_arista.db_lib.assert_has_calls(expected_calls) - - router_id = 'r1' - # DVR ports - # does not exist. It should be added to the DB - owner = n_const.DEVICE_OWNER_DVR_INTERFACE - port_context = self._get_port_context(tenant_id, - network_id, - router_id, - network_context, - device_owner=owner) - mechanism_arista.db_lib.is_port_provisioned.return_value = False - self.drv.update_port_precommit(port_context) - expected_calls += [ - mock.call.is_network_provisioned(tenant_id, network_id, - segmentation_id, segment_id), - mock.call.is_port_provisioned(port_id, host_id), - mock.call.remember_tenant(tenant_id), - mock.call.remember_vm(router_id, host_id, port_id, - network_id, tenant_id) - ] - mechanism_arista.db_lib.assert_has_calls(expected_calls) - - # Unbind the port. It should be removed from the DB - port_context._port['binding:host_id'] = None - self.drv.update_port_precommit(port_context) - expected_calls += [ - mock.call.is_network_provisioned(tenant_id, network_id, - segmentation_id, segment_id), - mock.call.forget_port(port_id, 'ubuntu1'), - ] - mechanism_arista.db_lib.assert_has_calls(expected_calls) - - def test_update_port_postcommit(self): - tenant_id = 'ten-1' - network_id = 'net1-id' - segmentation_id = 1001 - vm_id = 'vm1' - - network_context = self._get_network_context(tenant_id, - network_id, - segmentation_id, - False) - segments = network_context.network_segments - port_context = self._get_port_context(tenant_id, - network_id, - vm_id, - network_context) - - mechanism_arista.db_lib.is_port_provisioned.return_value = True - mechanism_arista.db_lib.is_network_provisioned.return_value = True - mechanism_arista.db_lib.get_shared_network_owner_id.return_value = 1 - mechanism_arista.db_lib.num_nets_provisioned.return_value = 1 - mechanism_arista.db_lib.num_vms_provisioned.return_value = 1 - self.drv.ndb.get_all_network_segments.return_value = segments - - network = {'tenant_id': tenant_id} - self.drv.ndb.get_network_from_net_id.return_value = [network] - port = port_context.current - device_id = port['device_id'] - device_owner = port['device_owner'] - host_id = port['binding:host_id'] - orig_host_id = port_context.original_host - port_id = port['id'] - port_name = port['name'] - vnic_type = port['binding:vnic_type'] - profile = port['binding:profile'] - network_name = network_context.current['name'] - - self.drv.rpc.hpb_supported.return_value = True - self.drv.update_port_postcommit(port_context) - - expected_calls = [ - mock.call.NeutronNets(), - mock.call.is_port_provisioned(port_id, None), - mock.call.is_network_provisioned(tenant_id, network_id, - None, None), - mock.call.is_network_provisioned(tenant_id, network_id, - segmentation_id, None), - mock.call.hpb_supported(), - mock.call.create_network_segments(tenant_id, network_id, - network_name, - segments), - mock.call.plug_port_into_network(device_id, host_id, port_id, - network_id, tenant_id, - port_name, device_owner, None, - None, vnic_type, - segments=segments, - switch_bindings=profile) - ] - - mechanism_arista.db_lib.assert_has_calls(expected_calls) - - # If there is no tenant id associated with the network, then the - # network should be created under the tenant id in the context. - tenant_id = 'ten-2' - network_id = 'net2-id' - segmentation_id = 1002 - vm_id = 'vm2' - - network_context = self._get_network_context(tenant_id, - network_id, - segmentation_id, - False) - segments = network_context.network_segments - port_context = self._get_port_context(tenant_id, - network_id, - vm_id, - network_context) - port_context.current['tenant_id'] = '' - - mechanism_arista.db_lib.is_port_provisioned.return_value = True - mechanism_arista.db_lib.is_network_provisioned.return_value = True - mechanism_arista.db_lib.get_shared_network_owner_id.return_value = 1 - mechanism_arista.db_lib.num_nets_provisioned.return_value = 1 - mechanism_arista.db_lib.num_vms_provisioned.return_value = 1 - self.drv.ndb.get_all_network_segments.return_value = segments - - network = {'tenant_id': ''} - self.drv.ndb.get_network_from_net_id.return_value = [network] - - port = port_context.current - device_id = port['device_id'] - device_owner = port['device_owner'] - host_id = port['binding:host_id'] - orig_host_id = port_context.original_host - port_id = port['id'] - port_name = port['name'] - network_name = network_context.current['name'] - - self.drv.update_port_postcommit(port_context) - - expected_calls += [ - mock.call.is_port_provisioned(port_id, None), - mock.call.is_network_provisioned(INTERNAL_TENANT_ID, network_id, - None, None), - mock.call.is_network_provisioned(INTERNAL_TENANT_ID, network_id, - segmentation_id, None), - mock.call.hpb_supported(), - mock.call.create_network_segments(INTERNAL_TENANT_ID, network_id, - network_name, - segments), - mock.call.plug_port_into_network(device_id, host_id, port_id, - network_id, INTERNAL_TENANT_ID, - port_name, device_owner, None, - None, vnic_type, - segments=segments, - switch_bindings=profile) - ] - - mechanism_arista.db_lib.assert_has_calls(expected_calls) - - # DVR ports - tenant_id = 'ten-3' - network_id = 'net3-id' - segmentation_id = 1003 - router_id = 'r1' - - network_context = self._get_network_context(tenant_id, - network_id, - segmentation_id, - False) - segments = network_context.network_segments - network_name = network_context.current['name'] - owner = n_const.DEVICE_OWNER_DVR_INTERFACE - port_context = self._get_port_context(tenant_id, - network_id, - router_id, - network_context, - device_owner=owner) - - mechanism_arista.db_lib.is_port_provisioned.return_value = True - mechanism_arista.db_lib.is_network_provisioned.return_value = True - mechanism_arista.db_lib.get_shared_network_owner_id.return_value = 1 - mechanism_arista.db_lib.num_nets_provisioned.return_value = 1 - mechanism_arista.db_lib.num_vms_provisioned.return_value = 1 - self.drv.ndb.get_all_network_segments.return_value = segments - - # New DVR port - context.original_host is not set and status is ACTIVE - # port should be plugged into the network - port = port_context.current - device_id = port['device_id'] - device_owner = port['device_owner'] - host_id = port['binding:host_id'] - orig_host_id = 'ubuntu1' - port_id = port['id'] - port_name = port['name'] - vnic_type = port['binding:vnic_type'] - profile = port['binding:profile'] - - self.drv.update_port_postcommit(port_context) - - expected_calls += [ - mock.call.is_port_provisioned(port_id, port_context.host), - mock.call.is_network_provisioned(tenant_id, network_id, - None, None), - mock.call.is_network_provisioned(tenant_id, network_id, - segmentation_id, None), - mock.call.hpb_supported(), - mock.call.create_network_segments(tenant_id, network_id, - network_name, - segments), - mock.call.plug_port_into_network(device_id, host_id, port_id, - network_id, tenant_id, - port_name, device_owner, None, - None, vnic_type, - segments=segments, - switch_bindings=profile) - ] - - mechanism_arista.db_lib.assert_has_calls(expected_calls) - - # Delete DVR port - context.original is set and the status is DOWN. - # port should be deleted - port_context._status = n_const.PORT_STATUS_DOWN - self.drv.update_port_postcommit(port_context) - - expected_calls += [ - mock.call.is_port_provisioned(port_id, port_context.host), - mock.call.is_network_provisioned(tenant_id, network_id, - None, None), - mock.call.is_network_provisioned(tenant_id, network_id, - segmentation_id, None), - mock.call.hpb_supported(), - mock.call.create_network_segments(tenant_id, network_id, - network_name, - segments), - mock.call.is_network_provisioned(tenant_id, network_id, - None, None), - mock.call.unplug_port_from_network(device_id, owner, - orig_host_id, - port_id, network_id, - tenant_id, - None, vnic_type, - switch_bindings=profile), - mock.call.remove_security_group(None, profile), - mock.call.num_nets_provisioned(tenant_id), - mock.call.num_vms_provisioned(tenant_id), - ] - - mechanism_arista.db_lib.assert_has_calls(expected_calls) - - def test_update_port_precommit_dhcp_reserved_port(self): - '''Test to ensure the dhcp port migration is handled correctly. - - Whenever a DHCP agent dies, the port is attached to a dummy device - identified by DEVICE_ID_RESERVED_DHCP_PORT. Once the dhcp agent is - respawned, the port is reattached to the newly created DHCP instance. - This deletes the old dhcp port from the old host and creates the port - on the new host. The dhcp port transitions from - - (Active ) to - (Active ) to - (Down ) to - (Down ) to - (Build ) to - (Active ) - - When the port is updated to (Active ), - the port needs to be removed from old host and when the port is updated - to (Down ), it should be created on - the new host. Removal and creation should take place in two updates - because when the port is updated to - (Down ), the original port would have - the device id set to 'reserved_dhcp_port' and so it can't be removed - from CVX at that point. - - ''' - - tenant_id = 't1' - network_id = 'n1' - old_device_id = 'old_device_id' - new_device_id = 'new_device_id' - reserved_device = neutron_const.DEVICE_ID_RESERVED_DHCP_PORT - old_host = 'ubuntu1' - new_host = 'ubuntu2' - port_id = 101 - segmentation_id = 1000 - network_context = self._get_network_context(tenant_id, - network_id, - segmentation_id, - False) - segment_id = network_context.network_segments[-1]['id'] - - # (Active ) to - # (Active ) - context = self._get_port_context( - tenant_id, network_id, old_device_id, - network_context, device_owner=n_const.DEVICE_OWNER_DHCP) - context.current['device_id'] = reserved_device - - network = {'tenant_id': tenant_id} - self.drv.ndb.get_network_from_net_id.return_value = [network] - - mechanism_arista.db_lib.is_port_provisioned.return_value = True - mechanism_arista.db_lib.is_network_provisioned.return_value = True - - mechanism_arista.db_lib.reset_mock() - self.drv.update_port_precommit(context) - - expected_calls = [ - mock.call.is_network_provisioned(tenant_id, network_id, - segmentation_id, - segment_id), - mock.call.is_port_provisioned(port_id, None), - mock.call.update_port(reserved_device, - old_host, port_id, - network_id, tenant_id) - ] - - mechanism_arista.db_lib.assert_has_calls(expected_calls) - - # (Active ) to - # (Down ) - context = self._get_port_context( - tenant_id, network_id, reserved_device, network_context, - device_owner=n_const.DEVICE_OWNER_DHCP) - context.current['device_id'] = new_device_id - context.current['binding:host_id'] = new_host - context.current['status'] = 'DOWN' - - mechanism_arista.db_lib.reset_mock() - self.drv.update_port_precommit(context) - - expected_calls = [ - mock.call.is_port_provisioned(port_id, old_host), - mock.call.update_port(new_device_id, - new_host, port_id, - network_id, tenant_id) - ] - - mechanism_arista.db_lib.assert_has_calls(expected_calls) - - # (Down ) to - # (Down ) to - context = self._get_port_context( - tenant_id, network_id, new_device_id, network_context, - device_owner=n_const.DEVICE_OWNER_DHCP, status='DOWN') - - mechanism_arista.db_lib.reset_mock() - self.drv.update_port_precommit(context) - - expected_calls = [ - mock.call.is_network_provisioned(tenant_id, network_id, - segmentation_id, - segment_id), - mock.call.is_port_provisioned(port_id, None), - ] - - mechanism_arista.db_lib.assert_has_calls(expected_calls) - - # (Down ) to - # (Build ) to - context = self._get_port_context( - tenant_id, network_id, new_device_id, network_context, - device_owner=n_const.DEVICE_OWNER_DHCP, status='DOWN') - - context.current['status'] = 'BUILD' - - mechanism_arista.db_lib.reset_mock() - self.drv.update_port_precommit(context) - - expected_calls = [ - mock.call.is_network_provisioned(tenant_id, network_id, - segmentation_id, - segment_id), - mock.call.is_port_provisioned(port_id, None), - ] - - mechanism_arista.db_lib.assert_has_calls(expected_calls) - - # (Build ) to - # (Active ) - context = self._get_port_context( - tenant_id, network_id, new_device_id, network_context, - device_owner=n_const.DEVICE_OWNER_DHCP, status='BUILD') - - context.current['status'] = 'ACTIVE' - - mechanism_arista.db_lib.reset_mock() - self.drv.update_port_precommit(context) - - expected_calls = [ - mock.call.is_network_provisioned(tenant_id, network_id, - segmentation_id, - segment_id), - mock.call.is_port_provisioned(port_id, None), - ] - - mechanism_arista.db_lib.assert_has_calls(expected_calls) - - def test_update_port_postcommit_dhcp_reserved_port(self): - '''Test to ensure the dhcp port migration is handled correctly. - - Whenever a DHCP agent dies, the port is attached to a dummy device - identified by DEVICE_ID_RESERVED_DHCP_PORT. Once the dhcp agent is - respawned, the port is reattached to the newly created DHCP instance. - This deletes the old dhcp port from the old host and creates the port - on the new host. The dhcp port transitions from - - (Active ) to - (Active ) to - (Down ) to - (Down ) to - (Build ) to - (Active ) - - When the port is updated to (Active ), - the port needs to be removed from old host and when the port is updated - to (Down ), it should be created on - the new host. Removal and creation should take place in two updates - because when the port is updated to - (Down ), the original port would have - the device id set to 'reserved_dhcp_port' and so it can't be removed - from CVX at that point. - - ''' - - tenant_id = 't1' - network_id = 'n1' - old_device_id = 'old_device_id' - new_device_id = 'new_device_id' - reserved_device = neutron_const.DEVICE_ID_RESERVED_DHCP_PORT - old_host = 'ubuntu1' - new_host = 'ubuntu2' - port_id = 101 - segmentation_id = 1000 - network_context = self._get_network_context(tenant_id, - network_id, - segmentation_id, - False) - segments = network_context.network_segments - - # (Active ) to - # (Active ) - context = self._get_port_context( - tenant_id, network_id, old_device_id, network_context, - device_owner=n_const.DEVICE_OWNER_DHCP) - context.current['device_id'] = reserved_device - vnic_type = context.current['binding:vnic_type'] - profile = context.current['binding:profile'] - port_name = context.current['name'] - - network = {'tenant_id': tenant_id} - self.drv.ndb.get_network_from_net_id.return_value = [network] - - mechanism_arista.db_lib.is_port_provisioned.return_value = True - mechanism_arista.db_lib.is_network_provisioned.return_value = True - mechanism_arista.db_lib.get_shared_network_owner_id.return_value = 1 - mechanism_arista.db_lib.num_nets_provisioned.return_value = 1 - mechanism_arista.db_lib.num_vms_provisioned.return_value = 1 - - self.drv.rpc.hpb_supported.return_value = False - self.drv.ndb.get_network_segments.return_value = segments - - mechanism_arista.db_lib.reset_mock() - self.drv.update_port_postcommit(context) - - expected_calls = [ - mock.call.is_port_provisioned(port_id, None), - mock.call.is_network_provisioned(tenant_id, network_id, - None, None), - mock.call.is_network_provisioned(tenant_id, network_id, - segmentation_id, - None), - mock.call.hpb_supported(), - mock.call.is_network_provisioned(tenant_id, network_id, - None, None), - mock.call.unplug_port_from_network(old_device_id, - n_const.DEVICE_OWNER_DHCP, - old_host, - port_id, network_id, - tenant_id, - None, vnic_type, - switch_bindings=profile), - mock.call.remove_security_group(None, profile), - mock.call.num_nets_provisioned(tenant_id), - mock.call.num_vms_provisioned(tenant_id), - ] - - mechanism_arista.db_lib.assert_has_calls(expected_calls) - - # (Active ) to - # (Down ) - context = self._get_port_context( - tenant_id, network_id, reserved_device, network_context, - device_owner=n_const.DEVICE_OWNER_DHCP) - context.current['device_id'] = new_device_id - context.current['binding:host_id'] = new_host - context.current['status'] = 'DOWN' - - physnet = dict(physnet='default') - self.fake_rpc.get_physical_network.return_value = physnet - context._original_binding_levels = context._binding_levels - - mechanism_arista.db_lib.reset_mock() - self.drv.update_port_postcommit(context) - - expected_calls = [] - expected_calls.extend( - mock.call.is_network_provisioned(tenant_id, network_id, None, - binding_level.segment_id) - for binding_level in context._original_binding_levels) - expected_calls += [ - mock.call.is_network_provisioned(tenant_id, network_id, - None, None), - mock.call.unplug_port_from_network(reserved_device, - n_const.DEVICE_OWNER_DHCP, - old_host, - port_id, network_id, - tenant_id, - None, vnic_type, - switch_bindings=profile), - mock.call.remove_security_group(None, profile), - mock.call.num_nets_provisioned(tenant_id), - mock.call.num_vms_provisioned(tenant_id), - ] - - mechanism_arista.db_lib.assert_has_calls(expected_calls) - - # (Down ) to - # (Down ) to - context = self._get_port_context( - tenant_id, network_id, new_device_id, network_context, - device_owner=n_const.DEVICE_OWNER_DHCP, status='DOWN') - context.current['binding:host_id'] = new_host - context.original['binding:host_id'] = new_host - - mechanism_arista.db_lib.reset_mock() - self.drv.update_port_postcommit(context) - - expected_calls = [ - mock.call.is_port_provisioned(port_id, None), - mock.call.is_network_provisioned(tenant_id, network_id, - None, None), - mock.call.is_network_provisioned(tenant_id, network_id, - segmentation_id, - None), - mock.call.hpb_supported(), - mock.call.plug_port_into_network(new_device_id, - new_host, - port_id, network_id, - tenant_id, port_name, - n_const.DEVICE_OWNER_DHCP, - None, None, vnic_type, - segments=[], - switch_bindings=profile), - ] - - mechanism_arista.db_lib.assert_has_calls(expected_calls) - - # (Down ) to - # (Build ) to - context = self._get_port_context( - tenant_id, network_id, new_device_id, network_context, - device_owner=n_const.DEVICE_OWNER_DHCP, status='DOWN') - context.current['binding:host_id'] = new_host - context.original['binding:host_id'] = new_host - context.current['status'] = 'BUILD' - - mechanism_arista.db_lib.reset_mock() - self.drv.update_port_postcommit(context) - - expected_calls = [ - mock.call.is_port_provisioned(port_id, None), - mock.call.is_network_provisioned(tenant_id, network_id, - None, None), - mock.call.is_network_provisioned(tenant_id, network_id, - segmentation_id, - None), - mock.call.hpb_supported(), - mock.call.plug_port_into_network(new_device_id, - new_host, - port_id, network_id, - tenant_id, port_name, - n_const.DEVICE_OWNER_DHCP, - None, None, vnic_type, - segments=[], - switch_bindings=profile), - ] - - mechanism_arista.db_lib.assert_has_calls(expected_calls) - - # (Build ) to - # (Active ) - context = self._get_port_context( - tenant_id, network_id, new_device_id, network_context, - device_owner=n_const.DEVICE_OWNER_DHCP, status='BUILD') - context.current['binding:host_id'] = new_host - context.original['binding:host_id'] = new_host - context.current['status'] = 'ACTIVE' - - mechanism_arista.db_lib.reset_mock() - self.drv.update_port_postcommit(context) - - expected_calls = [ - mock.call.is_port_provisioned(port_id, None), - mock.call.is_network_provisioned(tenant_id, network_id, - None, None), - mock.call.is_network_provisioned(tenant_id, network_id, - segmentation_id, - None), - mock.call.hpb_supported(), - ] - - mechanism_arista.db_lib.assert_has_calls(expected_calls) - - def _get_network_context(self, tenant_id, net_id, - segmentation_id, shared): - network = {'id': net_id, - 'tenant_id': tenant_id, - 'name': 'test-net', - 'shared': shared} - network_segments = [{'segmentation_id': segmentation_id, - 'physical_network': u'default', - 'id': 'segment-id-for-%s' % segmentation_id, - 'network_type': 'vlan'}] - return FakeNetworkContext(tenant_id, network, network_segments, - network) - - def _get_port_context(self, tenant_id, net_id, device_id, network, - device_owner='compute', status='ACTIVE'): - port = {'device_id': device_id, - 'device_owner': device_owner, - 'binding:host_id': 'ubuntu1', - 'name': 'test-port', - 'tenant_id': tenant_id, - 'id': 101, - 'network_id': net_id, - 'binding:vnic_type': None, - 'binding:profile': [], - 'security_groups': None, - 'status': 'ACTIVE', - } - orig_port = {'device_id': device_id, - 'device_owner': device_owner, - 'binding:host_id': 'ubuntu1', - 'name': 'test-port', - 'tenant_id': tenant_id, - 'id': 101, - 'network_id': net_id, - 'binding:vnic_type': None, - 'binding:profile': [], - 'security_groups': None, - 'status': 'ACTIVE', - } - binding_levels = [] - for level, segment in enumerate(network.network_segments): - binding_levels.append(FakePortBindingLevel(port['id'], - level, - 'vendor-1', - segment['id'])) - return FakePortContext(port, dict(orig_port), network, status, - binding_levels) - - -class fake_keystone_info_class(object): - """To generate fake Keystone Authentication token information - - Arista Driver expects Keystone auth info. This fake information - is for testing only - """ - auth_uri = 'abc://host:35357/v3/' - identity_uri = 'abc://host:5000' - admin_user = 'neutron' - admin_password = 'fun' - admin_tenant_name = 'tenant_name' - - -class FakeNetworkContext(object): - """To generate network context for testing purposes only.""" - - def __init__(self, tenant_id, network, segments=None, - original_network=None): - self._network = network - self._original_network = original_network - self._segments = segments - self._plugin_context = FakePluginContext(tenant_id) - - @property - def current(self): - return self._network - - @property - def original(self): - return self._original_network - - @property - def network_segments(self): - return self._segments - - -class FakePortContext(object): - """To generate port context for testing purposes only.""" - - def __init__(self, port, original_port, network, status, - binding_levels): - self._plugin_context = FakePluginContext('test') - self._port = port - self._original_port = original_port - self._network_context = network - self._status = status - self._binding_levels = binding_levels - self._original_binding_levels = [] - - @property - def current(self): - return self._port - - @property - def original(self): - return self._original_port - - @property - def network(self): - return self._network_context - - @property - def host(self): - return self._port.get(portbindings.HOST_ID) - - @property - def original_host(self): - return self._original_port.get(portbindings.HOST_ID) - - @property - def status(self): - return self._status - - @property - def original_status(self): - if self._original_port: - return self._original_port['status'] - - @property - def binding_levels(self): - if self._binding_levels: - return [{ - driver_api.BOUND_DRIVER: level.driver, - driver_api.BOUND_SEGMENT: - self._expand_segment(level.segment_id) - } for level in self._binding_levels] - - @property - def bottom_bound_segment(self): - if self._binding_levels: - return self._expand_segment(self._binding_levels[-1].segment_id) - - def _expand_segment(self, segment_id): - for segment in self._network_context.network_segments: - if segment[driver_api.ID] == segment_id: - return segment - - -class FakePluginContext(object): - """Plugin context for testing purposes only.""" - - def __init__(self, tenant_id): - self.tenant_id = tenant_id - self.session = mock.MagicMock() - - -class FakePortBindingLevel(object): - """Port binding object for testing purposes only.""" - - def __init__(self, port_id, level, driver, segment_id): - self.port_id = port_id - self.level = level - self.driver = driver - self.segment_id = segment_id diff --git a/networking_arista/tests/unit/ml2/utils.py b/networking_arista/tests/unit/ml2/utils.py deleted file mode 100644 index dde65db..0000000 --- a/networking_arista/tests/unit/ml2/utils.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright (c) 2016 OpenStack Foundation -# -# 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. - - -def setup_arista_wrapper_config(cfg, host='host', user='user'): - cfg.CONF.keystone_authtoken = fake_keystone_info_class() - cfg.CONF.set_override('eapi_host', host, "ml2_arista") - cfg.CONF.set_override('eapi_username', user, "ml2_arista") - cfg.CONF.set_override('sync_interval', 10, "ml2_arista") - cfg.CONF.set_override('conn_timeout', 20, "ml2_arista") - cfg.CONF.set_override('switch_info', ['switch1:user:pass'], "ml2_arista") - cfg.CONF.set_override('sec_group_support', False, "ml2_arista") - - -class fake_keystone_info_class(object): - """To generate fake Keystone Authentication token information - - Arista Driver expects Keystone auth info. This fake information - is for testing only - """ - auth_uri = False - auth_protocol = 'abc' - auth_host = 'host' - auth_port = 5000 - admin_user = 'neutron' - admin_password = 'fun' - admin_tenant_name = 'tenant_name' diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index f24af8f..0000000 --- a/requirements.txt +++ /dev/null @@ -1,15 +0,0 @@ -# The order of packages is significant, because pip processes them in the order -# of appearance. Changing the order has an impact on the overall integration -# process, which may cause wedges in the gate later. -pbr!=2.1.0,>=2.0.0 # Apache-2.0 - -alembic>=0.8.10 # MIT -neutron-lib>=1.9.0 # Apache-2.0 -oslo.i18n!=3.15.2,>=2.1.0 # Apache-2.0 -oslo.config!=4.3.0,!=4.4.0,>=4.0.0 # Apache-2.0 -oslo.log>=3.22.0 # Apache-2.0 -oslo.service>=1.10.0 # Apache-2.0 -oslo.utils>=3.20.0 # Apache-2.0 -requests>=2.14.2 # Apache-2.0 -six>=1.9.0 # MIT -SQLAlchemy!=1.1.5,!=1.1.6,!=1.1.7,!=1.1.8,>=1.0.10 # MIT diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index c7cf038..0000000 --- a/setup.cfg +++ /dev/null @@ -1,66 +0,0 @@ -[metadata] -name = networking_arista -summary = Arista Networking drivers -description-file = - README.rst -author = Arista Networks -author-email = openstack-dev@arista.com -home-page = https://github.com/openstack/networking-arista/ -classifier = - Environment :: OpenStack - Intended Audience :: Information Technology - Intended Audience :: System Administrators - License :: OSI Approved :: Apache Software License - Operating System :: POSIX :: Linux - Programming Language :: Python - Programming Language :: Python :: 2 - Programming Language :: Python :: 2.7 - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.5 - -[files] -packages = - networking_arista -data_files = - /etc/neutron/plugins/ml2 = - etc/ml2_conf_arista.ini - -[global] -setup-hooks = - pbr.hooks.setup_hook - -[entry_points] -neutron.ml2.mechanism_drivers = - arista = networking_arista.ml2.mechanism_arista:AristaDriver - arista_ml2 = networking_arista.ml2.mechanism_arista:AristaDriver -neutron.service_plugins = - arista_l3 = networking_arista.l3Plugin.l3_arista:AristaL3ServicePlugin -neutron.db.alembic_migrations = - networking-arista = networking_arista.db.migration:alembic_migrations -neutron.ml2.type_drivers = - arista_vlan = networking_arista.ml2.drivers.type_arista_vlan:AristaVlanTypeDriver - -[build_sphinx] -source-dir = doc/source -build-dir = doc/build -all_files = 1 - -[upload_sphinx] -upload-dir = doc/build/html - -[compile_catalog] -directory = networking_arista/locale -domain = networking-arista - -[update_catalog] -domain = networking-arista -output_dir = networking_arista/locale -input_file = networking_arista/locale/networking-arista.pot - -[extract_messages] -keywords = _ gettext ngettext l_ lazy_gettext -mapping_file = babel.cfg -output_file = networking_arista/locale/networking-arista.pot - -[wheel] -universal = 1 diff --git a/setup.py b/setup.py deleted file mode 100644 index 566d844..0000000 --- a/setup.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. -# -# 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. - -# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT -import setuptools - -# In python < 2.7.4, a lazy loading of package `pbr` will break -# setuptools if some other modules registered functions in `atexit`. -# solution from: http://bugs.python.org/issue15881#msg170215 -try: - import multiprocessing # noqa -except ImportError: - pass - -setuptools.setup( - setup_requires=['pbr>=2.0.0'], - pbr=True) diff --git a/test-requirements.txt b/test-requirements.txt deleted file mode 100644 index e9686a6..0000000 --- a/test-requirements.txt +++ /dev/null @@ -1,15 +0,0 @@ -# The order of packages is significant, because pip processes them in the order -# of appearance. Changing the order has an impact on the overall integration -# process, which may cause wedges in the gate later. -hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0 - -coverage!=4.4,>=4.0 # Apache-2.0 -mock>=2.0 # BSD -python-subunit>=0.0.18 # Apache-2.0/BSD -sphinx>=1.6.2 # BSD -oslosphinx>=4.7.0 # Apache-2.0 -oslotest>=1.10.0 # Apache-2.0 -testrepository>=0.0.18 # Apache-2.0/BSD -testtools>=1.4.0 # MIT -testresources>=0.2.4 # Apache-2.0/BSD -testscenarios>=0.4 # Apache-2.0/BSD diff --git a/tox.ini b/tox.ini deleted file mode 100644 index e4a5822..0000000 --- a/tox.ini +++ /dev/null @@ -1,40 +0,0 @@ -[tox] -envlist = py27,py35,pep8 -minversion = 1.6 -skipsdist = True - -[testenv] -usedevelop = True -install_command = pip install -c {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} -r requirements.txt -U {opts} {packages} -setenv = VIRTUAL_ENV={envdir} - PYTHONWARNINGS=default::DeprecationWarning -deps = -r{toxinidir}/test-requirements.txt - -egit+https://git.openstack.org/openstack/neutron.git#egg=neutron -whitelist_externals = sh -commands = python setup.py testr --slowest --testr-args='{posargs}' - -[testenv:pep8] -commands = - flake8 - neutron-db-manage --subproject networking-arista check_migration - -[testenv:venv] -commands = {posargs} - -[testenv:cover] -commands = python setup.py testr --coverage --testr-args='{posargs}' - -[testenv:docs] -commands = python setup.py build_sphinx - -[flake8] -# H803 skipped on purpose per list discussion. -# E123, E125 skipped as they are invalid PEP-8. - -show-source = True -ignore = E123,E125,H803 -builtins = _ -exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build - -[hacking] -import_exceptions = networking_arista._i18n