From 07c0ae2e49665961af4a8b9734218e9c5f8cd3ca Mon Sep 17 00:00:00 2001 From: Tibor Frank Date: Thu, 28 Apr 2016 15:44:25 +0200 Subject: Honeycomb API keywords - interface, bridge domain - add keywords to manipulate data using Honeycomb REST API to configure interfaces and bridge domains - remove "add_vpp_to_honeycomb_network_topology" method from HoneycombSetup.py - remove "parse_json_response" from HoneycombUtil.py - add methods to manipulate data from Honeycomb REST API Change-Id: I5e6f87097fe9bfccffa3d4aae21f63281353cf29 Signed-off-by: Tibor Frank --- resources/libraries/python/HcAPIKwBridgeDomain.py | 329 ++++++++++ resources/libraries/python/HcAPIKwInterfaces.py | 712 +++++++++++++++++++++ resources/libraries/python/HoneycombAPIKeywords.py | 543 ---------------- resources/libraries/python/HoneycombSetup.py | 92 --- resources/libraries/python/HoneycombUtil.py | 183 ++++-- 5 files changed, 1186 insertions(+), 673 deletions(-) create mode 100644 resources/libraries/python/HcAPIKwBridgeDomain.py create mode 100644 resources/libraries/python/HcAPIKwInterfaces.py delete mode 100644 resources/libraries/python/HoneycombAPIKeywords.py (limited to 'resources/libraries') diff --git a/resources/libraries/python/HcAPIKwBridgeDomain.py b/resources/libraries/python/HcAPIKwBridgeDomain.py new file mode 100644 index 0000000000..2f7c149684 --- /dev/null +++ b/resources/libraries/python/HcAPIKwBridgeDomain.py @@ -0,0 +1,329 @@ +# Copyright (c) 2016 Cisco and/or its affiliates. +# 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. + +"""Keywords to manipulate bridge domain configuration using Honeycomb REST API. + +The keywords make possible to put and get configuration data and to get +operational data. +""" + + +from resources.libraries.python.HTTPRequest import HTTPCodes +from resources.libraries.python.HoneycombSetup import HoneycombError +from resources.libraries.python.HoneycombUtil import HoneycombUtil as HcUtil +from resources.libraries.python.HoneycombUtil import DataRepresentation + + +class BridgeDomainKeywords(object): + """Keywords to manipulate bridge domain configuration. + + Implements keywords which get configuration and operational data about + bridge domains and put the bridge domains' parameters using Honeycomb REST + API. + """ + + PARAMS = ("flood", "forward", "learn", "unknown-unicast-flood", + "arp-termination") + + def __init__(self): + pass + + @staticmethod + def _configure_bd(node, bd_name, data, + data_representation=DataRepresentation.JSON): + """Send bridge domain configuration data and check the response. + + :param node: Honeycomb node. + :param bd_name: The name of bridge domain. + :param data: Configuration data to be sent in PUT request. + :param data_representation: How the data is represented. + :type node: dict + :type bd_name: str + :type data: dict + :type data_representation: DataRepresentation + :return: Content of response. + :rtype: bytearray + :raises HoneycombError: If the status code in response on PUT is not + 200 = OK. + """ + + status_code, resp = HcUtil.\ + put_honeycomb_data(node, "config_bridge_domain", data, + data_representation=data_representation) + if status_code != HTTPCodes.OK: + raise HoneycombError( + "The configuration of bridge domain '{0}' was not successful. " + "Status code: {1}.".format(bd_name, status_code)) + return resp + + @staticmethod + def _set_bd_properties(node, bd_name, path, new_value=None): + """Set bridge domain properties. + + This method reads bridge domain configuration data, creates, changes or + removes the requested data and puts it back to Honeycomb. + + :param node: Honeycomb node. + :param bd_name: The name of bridge domain. + :param path: Path to data we want to change, create or remove. + :param new_value: The new value to be set. If None, the item will be + removed. + :type node: dict + :type bd_name: str + :type path: tuple + :type new_value: str, dict or list + :return: Content of response. + :rtype: bytearray + :raises HoneycombError: If it is not possible to get or set the data. + """ + + status_code, resp = HcUtil.\ + get_honeycomb_data(node, "config_bridge_domain") + if status_code != HTTPCodes.OK: + raise HoneycombError( + "Not possible to get configuration information about the " + "bridge domains. Status code: {0}.".format(status_code)) + + if new_value: + new_data = HcUtil.set_item_value(resp, path, new_value) + else: + new_data = HcUtil.remove_item(resp, path) + return BridgeDomainKeywords._configure_bd(node, bd_name, new_data) + + @staticmethod + def _create_bd_structure(bd_name, **kwargs): + """Create the bridge domain data structure as it is expected by + Honeycomb REST API. + + :param bd_name: Bridge domain name. + :param kwargs: Parameters and their values. The accepted parameters are + defined in BridgeDomainKeywords.PARAMS. + :type bd_name: str + :type kwargs: dict + :return: Bridge domain data structure. + :rtype: dict + """ + + bd_structure = {"name": bd_name} + + for param, value in kwargs.items(): + if param not in BridgeDomainKeywords.PARAMS: + raise HoneycombError("The parameter {0} is invalid.". + format(param)) + bd_structure[param] = str(value) + + return bd_structure + + @staticmethod + def get_all_bds_cfg_data(node): + """Get configuration data about all bridge domains from Honeycomb. + + :param node: Honeycomb node. + :type node: dict + :return: Configuration data about all bridge domains from Honeycomb. + :rtype: list + :raises HoneycombError: If it is not possible to get configuration data. + """ + + status_code, resp = HcUtil.\ + get_honeycomb_data(node, "config_bridge_domain") + if status_code != HTTPCodes.OK: + raise HoneycombError( + "Not possible to get configuration information about the " + "bridge domains. Status code: {0}.".format(status_code)) + try: + return resp["bridge-domains"]["bridge-domain"] + + except (KeyError, TypeError): + return [] + + @staticmethod + def get_bd_cfg_data(node, bd_name): + """Get configuration data about the given bridge domain from Honeycomb. + + :param node: Honeycomb node. + :param bd_name: The name of bridge domain. + :type node: dict + :type bd_name: str + :return: Configuration data about the given bridge domain from + Honeycomb. + :rtype: dict + """ + + intfs = BridgeDomainKeywords.get_all_bds_cfg_data(node) + for intf in intfs: + if intf["name"] == bd_name: + return intf + return {} + + @staticmethod + def get_all_bds_oper_data(node): + """Get operational data about all bridge domains from Honeycomb. + + :param node: Honeycomb node. + :type node: dict + :return: Operational data about all bridge domains from Honeycomb. + :rtype: list + :raises HoneycombError: If it is not possible to get operational data. + """ + + status_code, resp = HcUtil.\ + get_honeycomb_data(node, "oper_bridge_domains") + if status_code != HTTPCodes.OK: + raise HoneycombError( + "Not possible to get operational information about the " + "bridge domains. Status code: {0}.".format(status_code)) + try: + return resp["bridge-domains"]["bridge-domain"] + + except (KeyError, TypeError): + return [] + + @staticmethod + def get_bd_oper_data(node, bd_name): + """Get operational data about the given bridge domain from Honeycomb. + + :param node: Honeycomb node. + :param bd_name: The name of bridge domain. + :type node: dict + :type bd_name: str + :return: Operational data about the given bridge domain from Honeycomb. + :rtype: dict + """ + + intfs = BridgeDomainKeywords.get_all_bds_oper_data(node) + for intf in intfs: + if intf["name"] == bd_name: + return intf + return {} + + @staticmethod + def add_first_bd(node, bd_name, **kwargs): + """Add the first bridge domain. + + If there are any other bridge domains configured, they will be removed. + + :param node: Honeycomb node. + :param bd_name: Bridge domain name. + :param kwargs: Parameters and their values. The accepted parameters are + defined in BridgeDomainKeywords.PARAMS + :type node: dict + :type bd_name: str + :type kwargs: dict + :return: Bridge domain data structure. + :rtype: dict + """ + + path = ("bridge-domains", ) + new_bd = BridgeDomainKeywords._create_bd_structure(bd_name, **kwargs) + bridge_domain = {"bridge-domain": [new_bd, ]} + return BridgeDomainKeywords._set_bd_properties(node, bd_name, path, + bridge_domain) + + @staticmethod + def add_bd(node, bd_name, **kwargs): + """Add a bridge domain. + + :param node: Honeycomb node. + :param bd_name: Bridge domain name. + :param kwargs: Parameters and their values. The accepted parameters are + defined in BridgeDomainKeywords.PARAMS + :type node: dict + :type bd_name: str + :type kwargs: dict + :return: Bridge domain data structure. + :rtype: dict + """ + + path = ("bridge-domains", "bridge-domain") + new_bd = BridgeDomainKeywords._create_bd_structure(bd_name, **kwargs) + bridge_domain = [new_bd, ] + return BridgeDomainKeywords._set_bd_properties(node, bd_name, path, + bridge_domain) + + @staticmethod + def remove_all_bds(node): + """Remove all bridge domains. + + :param node: Honeycomb node. + :type node: dict + :return: Content of response. + :rtype: bytearray + :raises HoneycombError: If it is not possible to remove all bridge + domains. + """ + + data = {"bridge-domains": {"bridge-domain": []}} + status_code, resp = HcUtil.\ + put_honeycomb_data(node, "config_bridge_domain", data) + if status_code != HTTPCodes.OK: + raise HoneycombError("Not possible to remove all bridge domains. " + "Status code: {0}.".format(status_code)) + return resp + + @staticmethod + def remove_bridge_domain(node, bd_name): + """Remove a bridge domain. + + :param node: Honeycomb node. + :param bd_name: The name of bridge domain to be removed. + :type node: dict + :type bd_name: str + :return: Content of response. + :rtype: bytearray + :raises HoneycombError:If it is not possible to remove the bridge + domain. + """ + + path = ("bridge-domains", ("bridge-domain", "name", bd_name)) + + status_code, resp = HcUtil.\ + get_honeycomb_data(node, "config_bridge_domain") + if status_code != HTTPCodes.OK: + raise HoneycombError( + "Not possible to get configuration information about the " + "bridge domains. Status code: {0}.".format(status_code)) + + new_data = HcUtil.remove_item(resp, path) + status_code, resp = HcUtil.\ + put_honeycomb_data(node, "config_bridge_domain", new_data) + if status_code != HTTPCodes.OK: + raise HoneycombError("Not possible to remove bridge domain {0}. " + "Status code: {1}.". + format(bd_name, status_code)) + return resp + + @staticmethod + def configure_bridge_domain(node, bd_name, param, value): + """Configure a bridge domain. + + :param node: Honeycomb node. + :param bd_name: Bridge domain name. + :param param: Parameter to set, change or remove. The accepted + parameters are defined in BridgeDomainKeywords.PARAMS + :param value: The new value to be set, change or remove. If None, the + item will be removed. + :type node: dict + :type bd_name: str + :type param: str + :type value: str + :return: Content of response. + :rtype: bytearray + """ + + if param not in BridgeDomainKeywords.PARAMS: + raise HoneycombError("The parameter {0} is invalid.".format(param)) + + path = ("bridge-domains", ("bridge-domain", "name", bd_name), param) + return BridgeDomainKeywords.\ + _set_bd_properties(node, bd_name, path, value) diff --git a/resources/libraries/python/HcAPIKwInterfaces.py b/resources/libraries/python/HcAPIKwInterfaces.py new file mode 100644 index 0000000000..ccdb5ddefd --- /dev/null +++ b/resources/libraries/python/HcAPIKwInterfaces.py @@ -0,0 +1,712 @@ +# Copyright (c) 2016 Cisco and/or its affiliates. +# 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. + +"""Keywords to manipulate interface configuration using Honeycomb REST API. + +The keywords make possible to put and get configuration data and to get +operational data. +""" + + +from resources.libraries.python.HTTPRequest import HTTPCodes +from resources.libraries.python.HoneycombSetup import HoneycombError +from resources.libraries.python.HoneycombUtil import HoneycombUtil as HcUtil +from resources.libraries.python.HoneycombUtil import DataRepresentation + + +class InterfaceKeywords(object): + """Keywords for Interface manipulation. + + Implements keywords which get configuration and operational data about + vpp interfaces and set the interface's parameters using Honeycomb REST API. + """ + + INTF_PARAMS = ("name", "description", "type", "enabled", + "link-up-down-trap-enable") + IPV4_PARAMS = ("enabled", "forwarding", "mtu") + IPV6_PARAMS = ("enabled", "forwarding", "mtu", "dup-addr-detect-transmits") + IPV6_AUTOCONF_PARAMS = ("create-global-addresses", + "create-temporary-addresses", + "temporary-valid-lifetime", + "temporary-preferred-lifetime") + ETH_PARAMS = ("mtu", ) + ROUTING_PARAMS = ("vrf-id", ) + VXLAN_PARAMS = ("src", "dst", "vni", "encap-vrf-id") + L2_PARAMS = ("bridge-domain", "split-horizon-group", + "bridged-virtual-interface") + + def __init__(self): + pass + + @staticmethod + def _configure_interface(node, interface, data, + data_representation=DataRepresentation.JSON): + """Send interface configuration data and check the response. + + :param node: Honeycomb node. + :param interface: The name of interface. + :param data: Configuration data to be sent in PUT request. + :param data_representation: How the data is represented. + :type node: dict + :type interface: str + :type data: dict + :type data_representation: DataRepresentation + :return: Content of response. + :rtype: bytearray + :raises HoneycombError: If the status code in response on PUT is not + 200 = OK. + """ + + status_code, resp = HcUtil.\ + put_honeycomb_data(node, "config_vpp_interfaces", data, + data_representation=data_representation) + if status_code != HTTPCodes.OK: + raise HoneycombError( + "The configuration of interface '{0}' was not successful. " + "Status code: {1}.".format(interface, status_code)) + return resp + + @staticmethod + def get_all_interfaces_cfg_data(node): + """Get configuration data about all interfaces from Honeycomb. + + :param node: Honeycomb node. + :type node: dict + :return: Configuration data about all interfaces from Honeycomb. + :rtype: list + :raises HoneycombError: If it is not possible to get configuration data. + """ + + status_code, resp = HcUtil.\ + get_honeycomb_data(node, "config_vpp_interfaces") + if status_code != HTTPCodes.OK: + raise HoneycombError( + "Not possible to get configuration information about the " + "interfaces. Status code: {0}.".format(status_code)) + try: + return resp["interfaces"]["interface"] + + except (KeyError, TypeError): + return [] + + @staticmethod + def get_interface_cfg_data(node, interface): + """Get configuration data about the given interface from Honeycomb. + + :param node: Honeycomb node. + :param interface: The name of interface. + :type node: dict + :type interface: str + :return: Configuration data about the given interface from Honeycomb. + :rtype: dict + """ + + intfs = InterfaceKeywords.get_all_interfaces_cfg_data(node) + for intf in intfs: + if intf["name"] == interface: + return intf + return {} + + @staticmethod + def get_all_interfaces_oper_data(node): + """Get operational data about all interfaces from Honeycomb. + + :param node: Honeycomb node. + :type node: dict + :return: Operational data about all interfaces from Honeycomb. + :rtype: list + :raises HoneycombError: If it is not possible to get operational data. + """ + + status_code, resp = HcUtil.\ + get_honeycomb_data(node, "oper_vpp_interfaces") + if status_code != HTTPCodes.OK: + raise HoneycombError( + "Not possible to get operational information about the " + "interfaces. Status code: {0}.".format(status_code)) + try: + return resp["interfaces-state"]["interface"] + + except (KeyError, TypeError): + return [] + + @staticmethod + def get_interface_oper_data(node, interface): + """Get operational data about the given interface from Honeycomb. + + :param node: Honeycomb node. + :param interface: The name of interface. + :type node: dict + :type interface: str + :return: Operational data about the given interface from Honeycomb. + :rtype: dict + """ + + intfs = InterfaceKeywords.get_all_interfaces_oper_data(node) + for intf in intfs: + if intf["name"] == interface: + return intf + return {} + + @staticmethod + def _set_interface_properties(node, interface, path, new_value=None): + """Set interface properties. + + This method reads interface configuration data, creates, changes or + removes the requested data and puts it back to Honeycomb. + + :param node: Honeycomb node. + :param interface: The name of interface. + :param path: Path to data we want to change / create / remove. + :param new_value: The new value to be set. If None, the item will be + removed. + :type node: dict + :type interface: str + :type path: tuple + :type new_value: str, dict or list + :return: Content of response. + :rtype: bytearray + :raises HoneycombError: If it is not possible to get or set the data. + """ + + status_code, resp = HcUtil.\ + get_honeycomb_data(node, "config_vpp_interfaces") + if status_code != HTTPCodes.OK: + raise HoneycombError( + "Not possible to get configuration information about the " + "interfaces. Status code: {0}.".format(status_code)) + + if new_value: + new_data = HcUtil.set_item_value(resp, path, new_value) + else: + new_data = HcUtil.remove_item(resp, path) + return InterfaceKeywords._configure_interface(node, interface, new_data) + + @staticmethod + def set_interface_state(node, interface, state="up"): + """Set VPP interface state. + + The keyword changes the administration state of interface to up or down + depending on the parameter "state". + + :param node: Honeycomb node. + :param interface: The name of interface. + :param state: The requested state, only "up" and "down" are valid + values. + :type node: dict + :type interface: str + :type state: str + :return: Content of response. + :rtype: bytearray + :raises KeyError: If the argument "state" is nor "up" or "down". + :raises HoneycombError: If the interface is not present on the node. + """ + + intf_state = {"up": "true", + "down": "false"} + + path = ("interfaces", ("interface", "name", str(interface)), "enabled") + return InterfaceKeywords._set_interface_properties( + node, interface, path, intf_state[state.lower()]) + + @staticmethod + def set_interface_up(node, interface): + """Set the administration state of VPP interface to up. + + :param node: Honeycomb node. + :param interface: The name of interface. + :type node: dict + :type interface: str + :return: Content of response + :rtype: bytearray + """ + + return InterfaceKeywords.set_interface_state(node, interface, "up") + + @staticmethod + def set_interface_down(node, interface): + """Set the administration state of VPP interface to down. + + :param node: Honeycomb node. + :param interface: The name of interface. + :type node: dict + :type interface: str + :return: Content of response. + :rtype: bytearray + """ + + return InterfaceKeywords.set_interface_state(node, interface, "down") + + @staticmethod + def add_bridge_domain_to_interface(node, interface, bd_name, + split_horizon_group=None, bvi=None): + """Add a new bridge domain to an interface and set its parameters. + + :param node: Honeycomb node. + :param interface: The name of interface. + :param bd_name: Bridge domain name. + :param split_horizon_group: Split-horizon group name. + :param bvi: The bridged virtual interface. + :type node: dict + :type interface: str + :type bd_name: str + :type split_horizon_group: str + :type bvi: str + :return: Content of response. + :rtype: bytearray + :raises HoneycombError: If the interface is not present on the node. + """ + + v3po_l2 = {"bridge-domain": str(bd_name)} + if split_horizon_group: + v3po_l2["split-horizon-group"] = str(split_horizon_group) + if bvi: + v3po_l2["bridged-virtual-interface"] = str(bvi) + + path = ("interfaces", ("interface", "name", str(interface)), "v3po:l2") + + return InterfaceKeywords._set_interface_properties( + node, interface, path, v3po_l2) + + @staticmethod + def configure_interface_base(node, interface, param, value): + """Configure the base parameters of interface. + + :param node: Honeycomb node. + :param interface: The name of interface. + :param param: Parameter to configure (set, change, remove) + :param value: The value of parameter. If None, the parameter will be + removed. + :type node: dict + :type interface: str + :type param: str + :type value: str + :return: Content of response. + :rtype: bytearray + :raises HoneycombError: If the parameter is not valid. + """ + + if param not in InterfaceKeywords.INTF_PARAMS: + raise HoneycombError("The parameter {0} is invalid.".format(param)) + + path = ("interfaces", ("interface", "name", interface), param) + return InterfaceKeywords._set_interface_properties( + node, interface, path, value) + + @staticmethod + def configure_interface_ipv4(node, interface, param, value): + """Configure IPv4 parameters of interface + + :param node: Honeycomb node. + :param interface: The name of interface. + :param param: Parameter to configure (set, change, remove) + :param value: The value of parameter. If None, the parameter will be + removed. + :type node: dict + :type interface: str + :type param: str + :type value: str + :return: Content of response. + :rtype: bytearray + :raises HoneycombError: If the parameter is not valid. + """ + + if param not in InterfaceKeywords.IPV4_PARAMS: + raise HoneycombError("The parameter {0} is invalid.".format(param)) + + path = ("interfaces", ("interface", "name", interface), + "ietf-ip:ipv4", param) + return InterfaceKeywords._set_interface_properties( + node, interface, path, value) + + @staticmethod + def add_first_ipv4_address(node, interface, ip_addr, netmask): + """Add the first IPv4 address. + + If there are any other addresses configured, they will be removed. + + :param node: Honeycomb node. + :param interface: The name of interface. + :param ip_addr: IPv4 address to be set. + :param netmask: Netmask. + :type node: dict + :type interface: str + :type ip_addr: str + :type netmask: str + :return: Content of response. + :rtype: bytearray + """ + + path = ("interfaces", ("interface", "name", interface), "ietf-ip:ipv4") + address = {"address": [{"ip": ip_addr, "netmask": netmask}, ]} + return InterfaceKeywords._set_interface_properties( + node, interface, path, address) + + @staticmethod + def add_ipv4_address(node, interface, ip_addr, netmask): + """Add IPv4 address. + + :param node: Honeycomb node. + :param interface: The name of interface. + :param ip_addr: IPv4 address to be set. + :param netmask: Netmask. + :type node: dict + :type interface: str + :type ip_addr: str + :type netmask: str + :return: Content of response. + :rtype: bytearray + """ + + path = ("interfaces", ("interface", "name", interface), "ietf-ip:ipv4", + "address") + address = [{"ip": ip_addr, "prefix-length": netmask}, ] + return InterfaceKeywords._set_interface_properties( + node, interface, path, address) + + @staticmethod + def remove_all_ipv4_addresses(node, interface): + """Remove all IPv4 addresses from interface. + + :param node: Honeycomb node. + :param interface: The name of interface. + :type node: dict + :type interface: str + :return: Content of response. + :rtype: bytearray + """ + + path = ("interfaces", ("interface", "name", interface), "ietf-ip:ipv4", + "address") + return InterfaceKeywords._set_interface_properties( + node, interface, path, None) + + @staticmethod + def add_first_ipv4_neighbor(node, interface, ip_addr, link_layer_address): + """Add the first IPv4 neighbour. + + If there are any other neighbours configured, they will be removed. + + :param node: Honeycomb node. + :param interface: The name of interface. + :param ip_addr: IPv4 address of neighbour to be set. + :param link_layer_address: Link layer address. + :type node: dict + :type interface: str + :type ip_addr: str + :type link_layer_address: str + :return: Content of response. + :rtype: bytearray + """ + + path = ("interfaces", ("interface", "name", interface), "ietf-ip:ipv4") + neighbor = {"neighbor": [{"ip": ip_addr, + "link-layer-address": link_layer_address}, ]} + return InterfaceKeywords._set_interface_properties( + node, interface, path, neighbor) + + @staticmethod + def add_ipv4_neighbor(node, interface, ip_addr, link_layer_address): + """Add the IPv4 neighbour. + + :param node: Honeycomb node. + :param interface: The name of interface. + :param ip_addr: IPv4 address of neighbour to be set. + :param link_layer_address: Link layer address. + :type node: dict + :type interface: str + :type ip_addr: str + :type link_layer_address: str + :return: Content of response. + :rtype: bytearray + """ + + path = ("interfaces", ("interface", "name", interface), "ietf-ip:ipv4", + "neighbor") + neighbor = [{"ip": ip_addr, "link-layer-address": link_layer_address}, ] + return InterfaceKeywords._set_interface_properties( + node, interface, path, neighbor) + + @staticmethod + def remove_all_ipv4_neighbors(node, interface): + """Remove all IPv4 neighbours. + + :param node: Honeycomb node. + :param interface: The name of interface. + :type node: dict + :type interface: str + :return: Content of response. + :rtype: bytearray + """ + + path = ("interfaces", ("interface", "name", interface), "ietf-ip:ipv4", + "neighbor") + return InterfaceKeywords._set_interface_properties( + node, interface, path, None) + + @staticmethod + def configure_interface_ipv6(node, interface, param, value): + """Configure IPv6 parameters of interface + + :param node: Honeycomb node. + :param interface: The name of interface. + :param param: Parameter to configure (set, change, remove) + :param value: The value of parameter. If None, the parameter will be + removed. + :type node: dict + :type interface: str + :type param: str + :type value: str + :return: Content of response. + :rtype: bytearray + :raises HoneycombError: If the parameter is not valid. + """ + + if param in InterfaceKeywords.IPV6_PARAMS: + path = ("interfaces", ("interface", "name", interface), + "ietf-ip:ipv6", param) + elif param in InterfaceKeywords.IPV6_AUTOCONF_PARAMS: + path = ("interfaces", ("interface", "name", interface), + "ietf-ip:ipv6", "autoconf", param) + else: + raise HoneycombError("The parameter {0} is invalid.".format(param)) + + return InterfaceKeywords._set_interface_properties( + node, interface, path, value) + + @staticmethod + def add_first_ipv6_address(node, interface, ip_addr, prefix_len): + """Add the first IPv6 address. + + If there are any other addresses configured, they will be removed. + + :param node: Honeycomb node. + :param interface: The name of interface. + :param ip_addr: IPv6 address to be set. + :param prefix_len: Prefix length. + :type node: dict + :type interface: str + :type ip_addr: str + :type prefix_len: str + :return: Content of response. + :rtype: bytearray + """ + + path = ("interfaces", ("interface", "name", interface), "ietf-ip:ipv6") + address = {"address": [{"ip": ip_addr, "prefix-length": prefix_len}, ]} + return InterfaceKeywords._set_interface_properties( + node, interface, path, address) + + @staticmethod + def add_ipv6_address(node, interface, ip_addr, prefix_len): + """Add IPv6 address. + + :param node: Honeycomb node. + :param interface: The name of interface. + :param ip_addr: IPv6 address to be set. + :param prefix_len: Prefix length. + :type node: dict + :type interface: str + :type ip_addr: str + :type prefix_len: str + :return: Content of response. + :rtype: bytearray + """ + + path = ("interfaces", ("interface", "name", interface), "ietf-ip:ipv6", + "address") + address = [{"ip": ip_addr, "prefix-length": prefix_len}, ] + return InterfaceKeywords._set_interface_properties( + node, interface, path, address) + + @staticmethod + def remove_all_ipv6_addresses(node, interface): + """Remove all IPv6 addresses from interface. + + :param node: Honeycomb node. + :param interface: The name of interface. + :type node: dict + :type interface: str + :return: Content of response. + :rtype: bytearray + """ + + path = ("interfaces", ("interface", "name", interface), "ietf-ip:ipv6", + "address") + return InterfaceKeywords._set_interface_properties( + node, interface, path, None) + + @staticmethod + def add_first_ipv6_neighbor(node, interface, ip_addr, link_layer_address): + """Add the first IPv6 neighbour. + + If there are any other neighbours configured, they will be removed. + + :param node: Honeycomb node. + :param interface: The name of interface. + :param ip_addr: IPv6 address of neighbour to be set. + :param link_layer_address: Link layer address. + :type node: dict + :type interface: str + :type ip_addr: str + :type link_layer_address: str + :return: Content of response. + :rtype: bytearray + """ + + path = ("interfaces", ("interface", "name", interface), "ietf-ip:ipv6") + neighbor = {"neighbor": [{"ip": ip_addr, + "link-layer-address": link_layer_address}, ]} + return InterfaceKeywords._set_interface_properties( + node, interface, path, neighbor) + + @staticmethod + def add_ipv6_neighbor(node, interface, ip_addr, link_layer_address): + """Add the IPv6 neighbour. + + :param node: Honeycomb node. + :param interface: The name of interface. + :param ip_addr: IPv6 address of neighbour to be set. + :param link_layer_address: Link layer address. + :type node: dict + :type interface: str + :type ip_addr: str + :type link_layer_address: str + :return: Content of response. + :rtype: bytearray + """ + + path = ("interfaces", ("interface", "name", interface), "ietf-ip:ipv6", + "neighbor") + neighbor = [{"ip": ip_addr, "link-layer-address": link_layer_address}, ] + return InterfaceKeywords._set_interface_properties( + node, interface, path, neighbor) + + @staticmethod + def remove_all_ipv6_neighbors(node, interface): + """Remove all IPv6 neighbours. + + :param node: Honeycomb node. + :param interface: The name of interface. + :type node: dict + :type interface: str + :return: Content of response. + :rtype: bytearray + """ + + path = ("interfaces", ("interface", "name", interface), "ietf-ip:ipv6", + "neighbor") + return InterfaceKeywords._set_interface_properties( + node, interface, path, None) + + @staticmethod + def configure_interface_ethernet(node, interface, param, value): + """Configure the ethernet parameters of interface. + + :param node: Honeycomb node. + :param interface: The name of interface. + :param param: Parameter to configure (set, change, remove) + :param value: The value of parameter. If None, the parameter will be + removed. + :type node: dict + :type interface: str + :type param: str + :type value: str + :return: Content of response. + :rtype: bytearray + :raises HoneycombError: If the parameter is not valid. + """ + + if param not in InterfaceKeywords.ETH_PARAMS: + raise HoneycombError("The parameter {0} is invalid.".format(param)) + path = ("interfaces", ("interface", "name", interface), "v3po:ethernet", + param) + return InterfaceKeywords._set_interface_properties( + node, interface, path, value) + + @staticmethod + def configure_interface_routing(node, interface, param, value): + """Configure the routing parameters of interface. + + :param node: Honeycomb node. + :param interface: The name of interface. + :param param: Parameter to configure (set, change, remove) + :param value: The value of parameter. If None, the parameter will be + removed. + :type node: dict + :type interface: str + :type param: str + :type value: str + :return: Content of response. + :rtype: bytearray + :raises HoneycombError: If the parameter is not valid. + """ + + if param not in InterfaceKeywords.ROUTING_PARAMS: + raise HoneycombError("The parameter {0} is invalid.".format(param)) + + path = ("interfaces", ("interface", "name", interface), "v3po:routing", + param) + return InterfaceKeywords._set_interface_properties( + node, interface, path, value) + + @staticmethod + def configure_interface_vxlan(node, interface, param, value): + """Configure the VxLAN parameters of interface. + + :param node: Honeycomb node. + :param interface: The name of interface. + :param param: Parameter to configure (set, change, remove) + :param value: The value of parameter. If None, the parameter will be + removed. + :type node: dict + :type interface: str + :type param: str + :type value: str + :return: Content of response. + :rtype: bytearray + :raises HoneycombError: If the parameter is not valid. + """ + + if param not in InterfaceKeywords.VXLAN_PARAMS: + raise HoneycombError("The parameter {0} is invalid.".format(param)) + + path = ("interfaces", ("interface", "name", interface), "v3po:vxlan", + param) + return InterfaceKeywords._set_interface_properties( + node, interface, path, value) + + @staticmethod + def configure_interface_l2(node, interface, param, value): + """Configure the L2 parameters of interface. + + :param node: Honeycomb node. + :param interface: The name of interface. + :param param: Parameter to configure (set, change, remove) + :param value: The value of parameter. If None, the parameter will be + removed. + :type node: dict + :type interface: str + :type param: str + :type value: str + :return: Content of response. + :rtype: bytearray + :raises HoneycombError: If the parameter is not valid. + """ + + if param not in InterfaceKeywords.L2_PARAMS: + raise HoneycombError("The parameter {0} is invalid.".format(param)) + path = ("interfaces", ("interface", "name", interface), "v3po:l2", + param) + return InterfaceKeywords._set_interface_properties( + node, interface, path, value) diff --git a/resources/libraries/python/HoneycombAPIKeywords.py b/resources/libraries/python/HoneycombAPIKeywords.py deleted file mode 100644 index 0369a32d5b..0000000000 --- a/resources/libraries/python/HoneycombAPIKeywords.py +++ /dev/null @@ -1,543 +0,0 @@ -# Copyright (c) 2016 Cisco and/or its affiliates. -# 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. - -"""Keywords used with Honeycomb. - -There are implemented keywords which work with: -- Honeycomb operations -- VPP Interfaces -- Bridge domains - -The keywords make possible to put and get configuration data and to get -operational data. -""" - -from json import dumps - -from robot.api import logger - -from resources.libraries.python.HTTPRequest import HTTPCodes -from resources.libraries.python.HoneycombSetup import HoneycombError -from resources.libraries.python.HoneycombUtil import HoneycombUtil as HcUtil -from resources.libraries.python.HoneycombUtil import DataRepresentation - - -class OperationsKeywords(object): - """Keywords which perform "operations" in Honeycomb. - - The keywords in this class are not a part of a specific area in Honeycomb, - e.g.: interfaces or bridge domains, but they perform "operations" in any - area of Honeycomb. - """ - - def __init__(self): - pass - - @staticmethod - def poll_oper_data(node): - """Poll operational data. - - You can use this keyword when you configure something in Honeycomb and - you want configuration data to make effect immediately, e.g.: - - | | Create Bridge Domain | .... - | | Add Bridge Domain To Interface | .... - | | Poll Oper Data | .... - | | ${br}= | Get Oper Info About Bridge Domain | .... - - ..note:: This is not very reliable way how to poll operational data. - This keyword is only temporary workaround and will be removed when this - problem is solved in Honeycomb. - :param node: Honeycomb node. - :type: dict - :raises HoneycombError: If it is not possible to poll operational data. - """ - - status_code, _ = HcUtil.\ - post_honeycomb_data(node, "poll_oper_data", data='', - data_representation=DataRepresentation.NO_DATA, - timeout=30) - if status_code != HTTPCodes.OK: - raise HoneycombError("It was not possible to poll operational data " - "on node {0}.".format(node['host'])) - - -class InterfaceKeywords(object): - """Keywords for Interface manipulation. - - Implements keywords which get configuration and operational data about - vpp interfaces and set the interface's parameters using Honeycomb REST API. - """ - - def __init__(self): - pass - - @staticmethod - def _configure_interface(node, interface, data, - data_representation=DataRepresentation.JSON): - """Send interface configuration data and check the response. - - :param node: Honeycomb node. - :param interface: The name of interface. - :param data: Configuration data to be sent in PUT request. - :param data_representation: How the data is represented. - :type node: dict - :type interface: str - :type data: str - :type data_representation: DataRepresentation - :return: Content of response. - :rtype: bytearray - :raises HoneycombError: If the status code in response on PUT is not - 200 = OK. - """ - - status_code, resp = HcUtil.\ - put_honeycomb_data(node, "config_vpp_interfaces", data, - data_representation=data_representation) - if status_code != HTTPCodes.OK: - raise HoneycombError( - "The configuration of interface '{0}' was not successful. " - "Status code: {1}.".format(interface, status_code)) - return resp - - @staticmethod - def get_all_interfaces_cfg_data(node): - """Get configuration data about all interfaces from Honeycomb. - - :param node: Honeycomb node. - :type node: dict - :return: Configuration data about all interfaces from Honeycomb. - :rtype: list - :raises HoneycombError: If it is not possible to get configuration data. - """ - - status_code, resp = HcUtil.get_honeycomb_data(node, - "config_vpp_interfaces") - if status_code != HTTPCodes.OK: - raise HoneycombError( - "Not possible to get configuration information about the " - "interfaces. Status code: {0}.".format(status_code)) - try: - intf = HcUtil.parse_json_response(resp, ("interfaces", "interface")) - return intf - except KeyError: - return [] - - @staticmethod - def get_interface_cfg_info(node, interface): - """Get configuration data about the given interface from Honeycomb. - - :param node: Honeycomb node. - :param interface: The name of interface. - :type node: dict - :type interface: str - :return: Configuration data about the given interface from Honeycomb. - :rtype: dict - """ - - intfs = InterfaceKeywords.get_all_interfaces_cfg_data(node) - for intf in intfs: - if intf["name"] == interface: - return intf - return {} - - @staticmethod - def get_all_interfaces_oper_info(node): - """Get operational data about all interfaces from Honeycomb. - - :param node: Honeycomb node. - :type node: dict - :return: Operational data about all interfaces from Honeycomb. - :rtype: list - :raises HoneycombError: If it is not possible to get operational data. - """ - - status_code, resp = HcUtil.get_honeycomb_data(node, - "oper_vpp_interfaces") - if status_code != HTTPCodes.OK: - raise HoneycombError( - "Not possible to get operational information about the " - "interfaces. Status code: {0}.".format(status_code)) - try: - intf = HcUtil.parse_json_response(resp, ("interfaces-state", - "interface")) - return intf - except KeyError: - return [] - - @staticmethod - def get_interface_oper_info(node, interface): - """Get operational data about the given interface from Honeycomb. - - :param node: Honeycomb node. - :param interface: The name of interface. - :type node: dict - :type interface: str - :return: Operational data about the given interface from Honeycomb. - :rtype: dict - """ - - intfs = InterfaceKeywords.get_all_interfaces_oper_info(node) - for intf in intfs: - if intf["name"] == interface: - return intf - return {} - - @staticmethod - def set_interface_state(node, interface, state="up"): - """Set VPP interface state. - - The keyword changes the administration state of interface to up or down - depending on the parameter "state". - - :param node: Honeycomb node. - :param interface: The name of interface. - :param state: The requested state, only "up" and "down" are valid - values. - :type node: dict - :type interface: str - :type state: str - :return: Content of response. - :rtype: bytearray - :raises KeyError: If the argument "state" is nor "up" or "down". - :raises HoneycombError: If the interface is not present on the node. - """ - - intf_state = {"up": "true", - "down": "false"} - intfs = InterfaceKeywords.get_all_interfaces_cfg_data(node) - for intf in intfs: - if intf["name"] == interface: - intf["enabled"] = intf_state[state.lower()] - new_intf = {"interfaces": {"interface": intfs}} - return InterfaceKeywords._configure_interface(node, interface, - dumps(new_intf)) - raise HoneycombError("The interface '{0}' is not present on node " - "'{1}'.".format(interface, node['host'])) - - @staticmethod - def set_interface_up(node, interface): - """Set the administration state of VPP interface to up. - - :param node: Honeycomb node. - :param interface: The name of interface. - :type node: dict - :type interface: str - :return: Content of response - :rtype: bytearray - """ - - return InterfaceKeywords.set_interface_state(node, interface, "up") - - @staticmethod - def set_interface_down(node, interface): - """Set the administration state of VPP interface to down. - - :param node: Honeycomb node. - :param interface: The name of interface. - :type node: dict - :type interface: str - :return: Content of response. - :rtype: bytearray - """ - - return InterfaceKeywords.set_interface_state(node, interface, "down") - - @staticmethod - def add_bridge_domain_to_interface(node, interface, bd_name, - split_horizon_group=None, bvi=None): - """Add a new bridge domain to an interface and set its parameters. - - :param node: Honeycomb node. - :param interface: The name of interface. - :param bd_name: Bridge domain name. - :param split_horizon_group: Split-horizon group name. - :param bvi: The bridged virtual interface. - :type node: dict - :type interface: str - :type bd_name: str - :type split_horizon_group: str - :type bvi: str - :return: Content of response. - :rtype: bytearray - :raises HoneycombError: If the interface is not present on the node. - """ - - intfs = InterfaceKeywords.get_all_interfaces_cfg_data(node) - v3po_l2 = {"bridge-domain": str(bd_name)} - if split_horizon_group: - v3po_l2["split-horizon-group"] = str(split_horizon_group) - if bvi: - v3po_l2["bridged-virtual-interface"] = str(bvi) - for intf in intfs: - if intf["name"] == interface: - intf["v3po:l2"] = v3po_l2 - new_intf = {"interfaces": {"interface": intfs}} - return InterfaceKeywords._configure_interface(node, interface, - dumps(new_intf)) - raise HoneycombError("The interface '{0}' is not present on node " - "'{1}'.".format(interface, node['host'])) - - -class BridgeDomainKeywords(object): - """Keywords for Bridge domain manipulation. - - Implements keywords which get configuration and operational data about - bridge domains and put the bridge domains' parameters using Honeycomb REST - API. - """ - - def __init__(self): - pass - - @staticmethod - def _create_json_bridge_domain_info(name, **kwargs): - """Generate bridge domain information in the structure as it is expected - by Honeycomb. - - The generated data structure is as follows: - { - "bridge-domains": { - "bridge-domain": [ - { - "name": "bd_name", - "flood": "false", - "forward": "false", - "learn": "false", - "unknown-unicast-flood": "false", - "arp-termination": "false" - } - ] - } - } - - :param name: The name of new bridge-domain. - :param kwargs: named arguments: - flood (bool): If True, flooding is enabled. - forward (bool): If True, packet forwarding is enabled. - learn (bool): If True, learning is enabled. - uu_flood (bool): If True, unknown unicast flooding is enabled. - arp_termination (bool): If True, ARP termination is enabled. - :type name: str - :type kwargs: dict - :return: Bridge domain information in format suitable for Honeycomb. - :rtype: dict - :raises KeyError: If at least one of kwargs items is missing. - """ - - brd_info = { - "bridge-domains": { - "bridge-domain": [ - {"name": name, - "flood": str(kwargs["flood"]).lower(), - "forward": str(kwargs["forward"]).lower(), - "learn": str(kwargs["learn"]).lower(), - "unknown-unicast-flood": str(kwargs["uu_flood"]).lower(), - "arp-termination": str(kwargs["arp_termination"]).lower()}, - ] - } - } - - return brd_info - - @staticmethod - def create_bridge_domain(node, name, flood=True, forward=True, learn=True, - uu_flood=True, arp_termination=False): - """Create a bridge domain using Honeycomb. - - This keyword adds a new bridge domain to the list of bridge domains and - sets its parameters. The existing bridge domains are untouched. - :param node: Node with Honeycomb where the bridge domain should be - created. - :param name: The name of new bridge-domain. - :param flood: If True, flooding is enabled. - :param forward: If True, packet forwarding is enabled. - :param learn: If True, learning is enabled. - :param uu_flood: If True, unknown unicast flooding is enabled. - :param arp_termination: If True, ARP termination is enabled. - :type node: dict - :type name: str - :type flood: bool - :type forward: bool - :type learn: bool - :type uu_flood: bool - :type arp_termination: bool - :raises HoneycombError: If the bridge domain already exists or it has - not been created. - """ - - existing_brds = BridgeDomainKeywords.\ - get_all_bds_cfg_data(node, ignore_404=True) - - for brd in existing_brds: - if brd["name"] == name: - raise HoneycombError("Bridge domain {0} already exists.". - format(name)) - - brd_info = BridgeDomainKeywords._create_json_bridge_domain_info( - name, flood=flood, forward=forward, learn=learn, uu_flood=uu_flood, - arp_termination=arp_termination) - for brd in existing_brds: - brd_info["bridge-domains"]["bridge-domain"].append(brd) - - status_code, _ = HcUtil.put_honeycomb_data(node, "config_bridge_domain", - dumps(brd_info)) - if status_code != HTTPCodes.OK: - raise HoneycombError( - "Bridge domain {0} was not created. " - "Status code: {01}.".format(name, status_code)) - - @staticmethod - def get_all_bds_oper_data(node): - """Get operational data about all bridge domains from Honeycomb. - - :param node: Honeycomb node. - :type node: dict - :return: Operational data about all bridge domains from Honeycomb. - :rtype: list - :raises HoneycombError: If it is not possible to get information about - the bridge domains. - """ - - status_code, resp = HcUtil.get_honeycomb_data(node, - "oper_bridge_domains") - if status_code != HTTPCodes.OK: - raise HoneycombError( - "Not possible to get information about the bridge domains. " - "Status code: {0}.".format(status_code)) - try: - br_domains = HcUtil.parse_json_response(resp, ("bridge-domains", - "bridge-domain")) - except KeyError: - return [] - return br_domains - - @staticmethod - def get_bd_oper_data(node, name): - """Get operational data about the given bridge domain from Honeycomb. - - :param node: Honeycomb node. - :param name: The name of bridge domain. - :type node: dict - :type name: str - :return: Operational data about the given bridge domain from Honeycomb. - :rtype: dict - """ - - br_domains = BridgeDomainKeywords.get_all_bds_oper_data(node) - for br_domain in br_domains: - if br_domain["name"] == name: - br_domain["name"] = br_domain["name"] - return br_domain - return {} - - @staticmethod - def get_all_bds_cfg_data(node, ignore_404=False): - """Get configuration data about all bridge domains from Honeycomb. - - :param node: Honeycomb node. - :param ignore_404: If True, the error 404 is ignored. - :type node: dict - :type ignore_404: bool - :return: Configuration data about all bridge domains from Honeycomb. - :rtype: list - :raises HoneycombError: If it is not possible to get information about - the bridge domains. - """ - - status_code, resp = HcUtil.get_honeycomb_data(node, - "config_bridge_domain") - if status_code != HTTPCodes.OK: - if ignore_404 and status_code == HTTPCodes.NOT_FOUND: - br_domains = list() - logger.debug("Error 404 ignored") - else: - raise HoneycombError( - "Not possible to get information about the bridge domains. " - "Status code: {0}.".format(status_code)) - else: - try: - br_domains = HcUtil.parse_json_response(resp, ("bridge-domains", - "bridge-domain")) - except KeyError: - return [] - return br_domains - - @staticmethod - def get_bd_cfg_data(node, name): - """Get configuration data about the given bridge domain from Honeycomb. - - :param node: Honeycomb node. - :param name: The name of bridge domain. - :type node: dict - :type name: str - :return: Configuration data about the given bridge domain from - Honeycomb. - :rtype: dict - """ - - br_domains = BridgeDomainKeywords.get_all_bds_cfg_data(node) - for br_domain in br_domains: - if br_domain["name"] == name: - return br_domain - return {} - - @staticmethod - def delete_all_bridge_domains(node): - """Delete all bridge domains on Honeycomb node. - - :param node: Honeycomb node. - :type node: dict - :return: Response from DELETE request. - :rtype: str - :raises HoneycombError: If it is not possible to delete all bridge - domains. - """ - - status_code, resp = HcUtil.delete_honeycomb_data(node, - "config_bridge_domain") - if status_code != HTTPCodes.OK: - raise HoneycombError( - "Not possible to delete all bridge domains. " - "Status code: {0}.".format(status_code)) - return resp - - @staticmethod - def remove_bridge_domain(node, name): - """Remove one bridge domain from Honeycomb. - - :param node: Honeycomb node. - :param name: Name of the bridge domain to be removed. - :type node: dict - :type name: str - :return: True if the bridge domain was removed. - :rtype: bool - :raises HoneycombError: If it is not possible to remove the bridge - domain. - """ - - br_domains = BridgeDomainKeywords.get_all_bds_cfg_data(node) - for br_domain in br_domains: - if br_domain["name"] == name: - br_domains.remove(br_domain) - brd_info = {"bridge-domains": {"bridge-domain": br_domains}} - status_code, _ = HcUtil.put_honeycomb_data( - node, "config_bridge_domain", dumps(brd_info)) - if status_code != HTTPCodes.OK: - raise HoneycombError( - "Bridge domain '{0}' was not deleted. " - "Status code: {1}.".format(name, status_code)) - return True - - raise HoneycombError("Not possible to delete bridge domain '{0}'. The " - "bridge domain was not found".format(name)) diff --git a/resources/libraries/python/HoneycombSetup.py b/resources/libraries/python/HoneycombSetup.py index bd2f3087c4..979b501522 100644 --- a/resources/libraries/python/HoneycombSetup.py +++ b/resources/libraries/python/HoneycombSetup.py @@ -13,8 +13,6 @@ """Implementation of keywords for Honeycomb setup.""" -from xml.etree import ElementTree as ET - from robot.api import logger from resources.libraries.python.topology import NodeType @@ -186,93 +184,3 @@ class HoneycombSetup(object): logger.info("Honeycomb on node {0} has stopped". format(node['host'])) return True - - @staticmethod - def add_vpp_to_honeycomb_network_topology(nodes): - """Add vpp node to Honeycomb network topology. - - :param nodes: All nodes in test topology. - :type nodes: dict - :return: Status code and response content from PUT requests. - :rtype: tuple - :raises HoneycombError: If a node was not added to Honeycomb topology. - - Reads HTML path from template file config_topology_node.url. - Path to the node to be added, e.g.: - ("/restconf/config/network-topology:network-topology" - "/topology/topology-netconf/node/") - There must be "/" at the end, as generated node name is added at the - end. - - Reads payload data from template file add_vpp_to_topology.xml. - Information about node as XML structure, e.g.: - - - {vpp_host} - - - {vpp_ip} - - - {vpp_port} - - - {user} - - - {passwd} - - - false - - - 0 - - - NOTE: The placeholders: - {vpp_host} - {vpp_ip} - {vpp_port} - {user} - {passwd} - MUST be there as they are replaced by correct values. - """ - path = HcUtil.read_path_from_url_file("config_topology_node") - try: - xml_data = ET.parse("{0}/add_vpp_to_topology.xml". - format(Const.RESOURCES_TPL_HC)) - except ET.ParseError as err: - raise HoneycombError(repr(err)) - data = ET.tostring(xml_data.getroot()) - - headers = {"Content-Type": "application/xml"} - - status_codes = [] - responses = [] - for node_name, node in nodes.items(): - if node['type'] == NodeType.DUT: - try: - payload = data.format( - vpp_host=node_name, - vpp_ip=node["host"], - vpp_port=node['honeycomb']["netconf_port"], - user=node['honeycomb']["user"], - passwd=node['honeycomb']["passwd"]) - status_code, resp = HTTPRequest.put( - node=node, - path="{0}/{1}".format(path, node_name), - headers=headers, - payload=payload) - if status_code != HTTPCodes.OK: - raise HoneycombError( - "VPP {0} was not added to topology. " - "Status code: {1}.".format(node["host"], - status_code)) - - status_codes.append(status_code) - responses.append(resp) - - except HTTPRequestError as err: - raise HoneycombError("VPP {0} was not added to topology.". - format(node["host"]), repr(err)) - return status_codes, responses diff --git a/resources/libraries/python/HoneycombUtil.py b/resources/libraries/python/HoneycombUtil.py index 7ffa6430ff..644cf62c43 100644 --- a/resources/libraries/python/HoneycombUtil.py +++ b/resources/libraries/python/HoneycombUtil.py @@ -22,7 +22,7 @@ be used directly in tests. Use keywords implemented in the module HoneycombAPIKeywords instead. """ -from json import loads +from json import loads, dumps from enum import Enum, unique from robot.api import logger @@ -42,7 +42,7 @@ class DataRepresentation(Enum): # Headers used in requests. Key - content representation, value - header. HEADERS = {DataRepresentation.NO_DATA: - {}, # Must be empty. + {}, # It must be empty dictionary. DataRepresentation.JSON: {"Content-Type": "application/json", "Accept": "text/plain"}, @@ -136,51 +136,153 @@ class HoneycombUtil(object): return path @staticmethod - def parse_json_response(response, path=None): - """Parse data from response string in JSON format according to given - path. - - :param response: JSON formatted string. - :param path: Path to navigate down the data structure. - :type response: str + def find_item(data, path): + """Find a data item (single leaf or sub-tree) in data received from + Honeycomb REST API. + + Path format: + The path is a tuple with items navigating to requested data. The items + can be strings or tuples: + - string item represents a dictionary key in data, + - tuple item represents list item in data. + + Example: + data = \ + { + "interfaces": { + "interface": [ + { + "name": "GigabitEthernet0/8/0", + "enabled": "true", + "type": "iana-if-type:ethernetCsmacd", + }, + { + "name": "local0", + "enabled": "false", + "type": "iana-if-type:ethernetCsmacd", + } + ] + } + } + + path = ("interfaces", ("interface", "name", "local0"), "enabled") + This path points to "false". + + The tuple ("interface", "name", "local0") consists of: + index 0 - dictionary key pointing to a list, + index 1 - key which identifies an item in the list, it is also marked as + the key in corresponding yang file. + index 2 - key value. + + :param data: Data received from Honeycomb REST API. + :param path: Path to data we want to find. + :type data: dict :type path: tuple - :return: JSON dictionary/list tree. - :rtype: list + :return: Data represented by path. + :rtype: str, dict, or list + :raises HoneycombError: If the data has not been found. """ - data = loads(response) - if path: - data = HoneycombUtil._parse_json_tree(data, path) - if not isinstance(data, list): - data = [data, ] + for path_item in path: + try: + if isinstance(path_item, str): + data = data[path_item] + elif isinstance(path_item, tuple): + for data_item in data[path_item[0]]: + if data_item[path_item[1]] == path_item[2]: + data = data_item + except KeyError as err: + raise HoneycombError("Data not found: {0}".format(err)) return data @staticmethod - def _parse_json_tree(data, path): - """Retrieve data addressed by path from python representation of JSON - object. + def remove_item(data, path): + """Remove a data item (single leaf or sub-tree) in data received from + Honeycomb REST API. - :param data: Parsed JSON dictionary tree. - :param path: Path to navigate down the dictionary tree. + :param data: Data received from Honeycomb REST API. + :param path: Path to data we want to remove. :type data: dict :type path: tuple - :return: Data from specified path. - :rtype: list, dict or str + :return: Original data without removed part. + :rtype: dict """ - count = 0 - for key in path: - if isinstance(data, dict): - data = data[key] - count += 1 - elif isinstance(data, list): - result = [] - for item in data: - result.append(HoneycombUtil._parse_json_tree(item, - path[count:])) - return result - return data + origin_data = previous_data = data + try: + for path_item in path: + previous_data = data + if isinstance(path_item, str): + data = data[path_item] + elif isinstance(path_item, tuple): + for data_item in data[path_item[0]]: + if data_item[path_item[1]] == path_item[2]: + data = data_item + except KeyError as err: + logger.debug("Data not found: {0}".format(err)) + return origin_data + + if isinstance(path[-1], str): + previous_data.pop(path[-1]) + elif isinstance(path[-1], tuple): + previous_data[path[-1][0]].remove(data) + if not previous_data[path[-1][0]]: + previous_data.pop(path[-1][0]) + + return origin_data + + @staticmethod + def set_item_value(data, path, new_value): + """Set or change the value (single leaf or sub-tree) in data received + from Honeycomb REST API. + + If the item is not present in the data structure, it is created. + + :param data: Data received from Honeycomb REST API. + :param path: Path to data we want to change or create. + :param new_value: The value to be set. + :type data: dict + :type path: tuple + :type new_value: str, dict or list + :return: Original data with the new value. + :rtype: dict + """ + + origin_data = data + for path_item in path[:-1]: + if isinstance(path_item, str): + try: + data = data[path_item] + except KeyError: + data[path_item] = {} + data = data[path_item] + elif isinstance(path_item, tuple): + try: + flag = False + index = 0 + for data_item in data[path_item[0]]: + if data_item[path_item[1]] == path_item[2]: + data = data[path_item[0]][index] + flag = True + break + index += 1 + if not flag: + data[path_item[0]].append({path_item[1]: path_item[2]}) + data = data[path_item[0]][-1] + except KeyError: + data[path_item] = [] + + if not path[-1] in data.keys(): + data[path[-1]] = {} + + if isinstance(new_value, list) and isinstance(data[path[-1]], list): + for value in new_value: + data[path[-1]].append(value) + else: + data[path[-1]] = new_value + + return origin_data @staticmethod def get_honeycomb_data(node, url_file): @@ -196,7 +298,8 @@ class HoneycombUtil(object): """ path = HoneycombUtil.read_path_from_url_file(url_file) - return HTTPRequest.get(node, path) + status_code, resp = HTTPRequest.get(node, path) + return status_code, loads(resp) @staticmethod def put_honeycomb_data(node, url_file, data, @@ -211,7 +314,7 @@ class HoneycombUtil(object): :param data_representation: How the data is represented. :type node: dict :type url_file: str - :type data: str + :type data: dict, str :type data_representation: DataRepresentation :return: Status code and content of response. :rtype: tuple @@ -224,6 +327,8 @@ class HoneycombUtil(object): except AttributeError as err: raise HoneycombError("Wrong data representation: {0}.". format(data_representation), repr(err)) + if data_representation == DataRepresentation.JSON: + data = dumps(data) path = HoneycombUtil.read_path_from_url_file(url_file) return HTTPRequest.put(node=node, path=path, headers=header, @@ -244,7 +349,7 @@ class HoneycombUtil(object): giving up. :type node: dict :type url_file: str - :type data: str + :type data: dict, str :type data_representation: DataRepresentation :type timeout: int :return: Status code and content of response. @@ -258,6 +363,8 @@ class HoneycombUtil(object): except AttributeError as err: raise HoneycombError("Wrong data representation: {0}.". format(data_representation), repr(err)) + if data_representation == DataRepresentation.JSON: + data = dumps(data) path = HoneycombUtil.read_path_from_url_file(url_file) return HTTPRequest.post(node=node, path=path, headers=header, -- cgit 1.2.3-korg