From 482a491a78d1a930983497c1c632b278bd469bef Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 17 Dec 2023 11:37:50 +0100 Subject: Merging upstream version 1.3.2+dfsg. Signed-off-by: Daniel Baumann --- README.md | 40 ++++++++++++++++++++++------- VERSION | 2 +- cvprac/__init__.py | 2 +- cvprac/cvp_api.py | 61 +++++++++++++++++++++++++++++++++++--------- cvprac/cvp_client.py | 20 ++++++++++++--- docs/labs/README.md | 8 ++++-- docs/release-notes-1.3.2.rst | 23 +++++++++++++++++ 7 files changed, 127 insertions(+), 29 deletions(-) create mode 100644 docs/release-notes-1.3.2.rst diff --git a/README.md b/README.md index bb71888..d74808e 100644 --- a/README.md +++ b/README.md @@ -4,25 +4,31 @@ ## Table of Contents -1. [Overview](#overview) +- [Arista Cloudvision® Portal RESTful API Client](#arista-cloudvision-portal-restful-api-client) + - [Table of Contents](#table-of-contents) + - [Overview](#overview) - [Requirements](#requirements) -1. [Installation](#installation) + - [Installation](#installation) - [Development: Run from Source](#development-run-from-source) -1. [Getting Started](#getting-started) + - [Step 1: Clone the cvprac Github repo](#step-1-clone-the-cvprac-github-repo) + - [Step 2: Check out the desired version or branch](#step-2-check-out-the-desired-version-or-branch) + - [Step 3: Install cvprac using Pip with -e switch](#step-3-install-cvprac-using-pip-with--e-switch) + - [Step 4: Install cvprac development requirements](#step-4-install-cvprac-development-requirements) + - [Getting Started](#getting-started) - [Connecting](#connecting) - [CVP On Premises](#cvp-on-premises) - [CVaaS](#cvaas) - [CVP Version Handling](#cvp-version-handling) - [Examples](#examples) -1. [Notes For API Class Usage](#notes-for-api-class-usage) + - [Notes for API Class Usage](#notes-for-api-class-usage) - [Containers](#containers) -1. [Testing](#testing) -1. [Contact or Questions](#contact-or-questions) -1. [Contributing](#contributing) + - [Testing](#testing) + - [Contact or Questions](#contact-or-questions) + - [Contributing](#contributing) - [Working With Git](#working-with-git) - [Submitting Pull Requests](#submitting-pull-requests) - [Pull Request Semantics](#pull-request-semantics) -1. [License](#license) + - [License](#license) ## Overview @@ -151,7 +157,7 @@ examples below demonstrate connecting to CVP On Premises setups. ### CVaaS CVaaS is CloudVision as a Service. Users with CVaaS must use a REST API -token for accessing CVP with REST APIs. +token (service account tokens) for accessing CVP with REST APIs. - In the case where users authenticate with CVP (CVaaS) using Oauth a - REST API token is required to be generated and used for running REST @@ -170,6 +176,22 @@ generic in this sense. If you are using the cvaas\_token parameter please convert to api\_token because the cvaas\_token parameter will be deprecated in the future. +Please note that the correct regional URL where the CVaaS tenant is deployed must be used. The following are the +cluster URLs used in production: + +| Region | URL | +|--------|-----| +| United States 1a | [www.arista.io](https://www.arista.io) | +| United States 1c| [www.cv-prod-us-central1-c.arista.io](https://www.cv-prod-us-central1-c.arista.io)| +| Canada | [www.cv-prod-na-northeast1-b.arista.io](https://www.cv-prod-na-northeast1-b.arista.io)| +| Europe West 2| [www.cv-prod-euwest-2.arista.io](https://www.cv-prod-euwest-2.arista.io)| +| Japan| [www.cv-prod-apnortheast-1.arista.io](https://www.cv-prod-apnortheast-1.arista.io)| +| Australia | [www.cv-prod-ausoutheast-1.arista.io](https://www.cv-prod-ausoutheast-1.arista.io)| + +!!! Warning + + URLs without `www` are not supported. + ### CVP Version Handling The CVP RESTful APIs often change between releases of CVP. Cvprac diff --git a/VERSION b/VERSION index 3a3cd8c..1892b92 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.3.1 +1.3.2 diff --git a/cvprac/__init__.py b/cvprac/__init__.py index 33ab12d..8d62fa0 100644 --- a/cvprac/__init__.py +++ b/cvprac/__init__.py @@ -32,5 +32,5 @@ ''' RESTful API Client class for Cloudvision(R) Portal ''' -__version__ = '1.3.1' +__version__ = '1.3.2' __author__ = 'Arista Networks, Inc.' diff --git a/cvprac/cvp_api.py b/cvprac/cvp_api.py index dc3fda1..c390989 100644 --- a/cvprac/cvp_api.py +++ b/cvprac/cvp_api.py @@ -572,7 +572,7 @@ class CvpApi(object): '%s&queryparam=&startIndex=%d&endIndex=%d' % (key, start, end), timeout=self.request_timeout) - def get_inventory(self, start=0, end=0, query=''): + def get_inventory(self, start=0, end=0, query='', provisioned=True): ''' Returns the a dict of the net elements known to CVP. Args: @@ -595,7 +595,7 @@ class CvpApi(object): timeout=self.request_timeout) return data['netElementList'] self.log.debug('v2 Inventory API Call') - data = self.clnt.get('/inventory/devices?provisioned=true', + data = self.clnt.get('/inventory/devices?provisioned=%s' % provisioned, timeout=self.request_timeout) containers = self.get_containers() for dev in data: @@ -1312,12 +1312,12 @@ class CvpApi(object): def sanitize_warnings(self, data): ''' Sanitize the warnings returned after validation. - + In some cases where the configlets has both errors - and warnings, CVP may split any warnings that have + and warnings, CVP may split any warnings that have `,` across multiple strings. This method concats the strings back into one string - per warning, and correct the warningCount. + per warning, and correct the warningCount. Args: data (dict): A dict that contians the result @@ -1330,11 +1330,11 @@ class CvpApi(object): # nothing to do here, we can return as is return data # Since there may be warnings incorrectly split on - # ', ' within the warning text by CVP, we join all the + # ', ' within the warning text by CVP, we join all the # warnings together using ', ' into one large string temp_warnings = ", ".join(data['warnings']).strip() - # To split the large string again we match on the + # To split the large string again we match on the # 'at line XXX' that should indicate the end of the warning. # We capture as well the remaining \\n or whitespace and include # the extra ', ' added in the previous step in the matching criteria. @@ -1463,7 +1463,7 @@ class CvpApi(object): return self.clnt.post(url, data=data, timeout=self.request_timeout) def apply_configlets_to_device(self, app_name, dev, new_configlets, - create_task=True, reorder_configlets=False): + create_task=True, reorder_configlets=False, validate=False): ''' Apply the configlets to the device. Args: @@ -1484,6 +1484,12 @@ class CvpApi(object): directly. Set this parameter to True only with the full list of configlets being applied to the device provided via the new_configlets parameter. + validate (bool): Defaults to False. If set to True, the function + will validate and compare the configlets to be attached and + populate the configCompareCount field in the data dict. In case + all keys are 0, ie there is no difference between designed-config + and running-config after applying the configlets, no task will be + generated. Returns: response (dict): A dict that contains a status and a list of @@ -1536,6 +1542,16 @@ class CvpApi(object): 'nodeTargetIpAddress': dev['ipAddress'], 'childTasks': [], 'parentTask': ''}]} + if validate: + validation_result = self.validate_configlets_for_device(dev['systemMacAddress'], ckeys) + data['data'][0].update({ + "configCompareCount": { + "mismatch": validation_result['mismatch'], + "reconcile": validation_result['reconcile'], + "new": validation_result['new'] + } + } + ) self.log.debug('apply_configlets_to_device: saveTopology data:\n%s' % data['data']) self._add_temp_action(data) @@ -1545,7 +1561,7 @@ class CvpApi(object): # pylint: disable=too-many-locals def remove_configlets_from_device(self, app_name, dev, del_configlets, - create_task=True): + create_task=True, validate=False): ''' Remove the configlets from the device. Args: @@ -1554,6 +1570,12 @@ class CvpApi(object): del_configlets (list): List of configlet name and key pairs create_task (bool): Determines whether or not to execute a save and create the tasks (if any) + validate (bool): Defaults to False. If set to True, the function + will validate and compare the configlets to be attached and + populate the configCompareCount field in the data dict. In case + all keys are 0, ie there is no difference between designed-config + and running-config after applying the configlets, no task will be + generated. Returns: response (dict): A dict that contains a status and a list of @@ -1612,6 +1634,16 @@ class CvpApi(object): 'nodeTargetIpAddress': dev['ipAddress'], 'childTasks': [], 'parentTask': ''}]} + if validate: + validation_result = self.validate_configlets_for_device(dev['systemMacAddress'], keep_keys) + data['data'][0].update({ + "configCompareCount": { + "mismatch": validation_result['mismatch'], + "reconcile": validation_result['reconcile'], + "new": validation_result['new'] + } + } + ) self.log.debug('remove_configlets_from_device: saveTopology data:\n%s' % data['data']) self._add_temp_action(data) @@ -2952,7 +2984,7 @@ class CvpApi(object): from_id = parent_cont['key'] else: from_id = '' - + data = {'data': [{'info': info, 'infoPreview': info, 'action': 'reset', @@ -3775,8 +3807,13 @@ class CvpApi(object): 'deviceId': 'BAD032986065E8DC14CBB6472EC314A6'}, 'time': '2022-02-12T02:58:30.765459650Z'} ''' - device_info = self.get_device_by_serial(device_id) - if device_info is not None and 'serialNumber' in device_info: + device_exists = False + inventory = self.get_inventory(provisioned=False) + for device in inventory: + if device['serialNumber'] == device_id: + device_exists = True + break + if device_exists: msg = 'Decommissioning via Resource APIs are supported from 2021.3.0 or newer.' # For on-prem check the version as it is only supported from 2021.3.0+ if self.cvp_version_compare('>=', 7.0, msg): diff --git a/cvprac/cvp_client.py b/cvprac/cvp_client.py index 0d901b7..602f21a 100644 --- a/cvprac/cvp_client.py +++ b/cvprac/cvp_client.py @@ -114,7 +114,7 @@ class CvpClient(object): # Maximum number of times to retry a get or post to the same # CVP node. NUM_RETRY_REQUESTS = 3 - LATEST_API_VERSION = 8.0 + LATEST_API_VERSION = 9.0 def __init__(self, logger='cvprac', syslog=False, filename=None, log_level='INFO'): @@ -212,7 +212,8 @@ class CvpClient(object): self.version = version self.log.info('Version %s', version) # Set apiversion to latest available API version for CVaaS - # Set apiversion to 8.0 for 2022.1.x + # Set apiversion to 9.0 for 2023.1.x + # Set apiversion to 8.0 for 2022.1.x - 2022.3.x # Set apiversion to 7.0 for 2021.3.x # Set apiversion to 6.0 for 2021.2.x # Set apiversion to 5.0 for 2020.2.4 through 2021.1.x @@ -232,7 +233,10 @@ class CvpClient(object): ' Appending 0. Updated Version String - %s', ".".join(version_components)) full_version = ".".join(version_components) - if parse_version(full_version) >= parse_version('2022.1.0'): + if parse_version(full_version) >= parse_version('2023.1.0'): + self.log.info('Setting API version to v9') + self.apiversion = 9.0 + elif parse_version(full_version) >= parse_version('2022.1.0'): self.log.info('Setting API version to v8') self.apiversion = 8.0 elif parse_version(full_version) >= parse_version('2021.3.0'): @@ -561,6 +565,14 @@ class CvpClient(object): # Alternative to adding token to headers it can be added to # cookies as shown below. # self.cookies = {'access_token': self.api_token} + url = self.url_prefix_short + '/api/v1/rest/' + response = self.session.get(url, + cookies=self.cookies, + headers=self.headers, + timeout=self.connect_timeout, + verify=self.cert) + # Verify that the generic request was successful + self._is_good_response(response, 'Authenticate: %s' % url) def logout(self): ''' @@ -710,7 +722,7 @@ class CvpClient(object): err_str) if 'Extra data' in str(error): self.log.debug('Found multiple objects or NO objects in' - 'response data. Attempt to decode') + ' response data. Attempt to decode') decoded_data = json_decoder(response.text) return {'data': decoded_data} else: diff --git a/docs/labs/README.md b/docs/labs/README.md index 132ee64..d91a81a 100644 --- a/docs/labs/README.md +++ b/docs/labs/README.md @@ -5,10 +5,12 @@ to help users interact with Arista CloudVision easily and automate the provision ## Table of Contents -1. [Authentication](#authentication) +- [cvprac labs](#cvprac-labs) + - [Table of Contents](#table-of-contents) + - [Authentication](#authentication) - [Password Authentication](#password-authentication) - [Service Account Token Authentication](#service-account-token-authentication) -1. [Known Limitations](#known-limitations) + - [Known Limitations](#known-limitations) ## Authentication @@ -60,6 +62,8 @@ clnt = CvpClient() clnt.connect(nodes=['10.83.13.33'], username='',password='',api_token=token) ``` +> Note that for CVaaS the correct regional URL must be used including `www.`. Please refer to the main page's [README.md](../../README.md#cvaas) + ## Known Limitations - for any APIs that interact with EOS devices, the service account name must match the name of the username diff --git a/docs/release-notes-1.3.2.rst b/docs/release-notes-1.3.2.rst new file mode 100644 index 0000000..91958d5 --- /dev/null +++ b/docs/release-notes-1.3.2.rst @@ -0,0 +1,23 @@ +###### +v1.3.2 +###### + +2023-12-14 + +Enhancements +^^^^^^^^^^^^ + +* Add handling of new password change logout functionality in 2023.1.0. (`254 `_) [`mharista `_] +* Add support for config validation during config assign. (`255 `_) [`noredistribution `_] +* Add support for config validation during config removal. (`256 `_) [`noredistribution `_] + +Fixed +^^^^^ + +* Add ability to use device_decommissioning for unprovisioned devices. (`253 `_) [`noredistribution `_] +* Add check to connect() to ensure token works. (`258 `_) [`chetryan `_] + +Documentation +^^^^^^^^^^^^^ + +* Add documentation for CVaaS regional URLs. (`259 `_) [`noredistribution `_] -- cgit v1.2.3