async def execute(query: Query) -> Union[str, Sequence[Dict]]: """Initiate query validation and execution.""" output = params.messages.general log.debug("Received query for {}", query.json()) log.debug("Matched device config: {}", query.device) mapped_driver = map_driver(query.device.driver) driver = mapped_driver(query.device, query) timeout_args = { "unformatted_msg": params.messages.connection_error, "device_name": query.device.name, "error": params.messages.request_timeout, } if query.device.proxy: timeout_args["proxy"] = query.device.proxy.name signal.signal(signal.SIGALRM, handle_timeout(**timeout_args)) signal.alarm(params.request_timeout - 1) if query.device.proxy: proxy = driver.setup_proxy() with proxy() as tunnel: response = await driver.collect( tunnel.local_bind_host, tunnel.local_bind_port ) else: response = await driver.collect() output = await driver.parsed_response(response) if isinstance(output, str): # If the output is a string (not structured) and is empty, # produce an error. if output == "" or output == "\n": raise ResponseEmpty( params.messages.no_output, device_name=query.device.name ) elif isinstance(output, Dict): # If the output an empty dict, responses have data, produce an # error. if not output: raise ResponseEmpty( params.messages.no_output, device_name=query.device.name ) log.debug("Output for query: {}:\n{}", query.json(), repr(output)) signal.alarm(0) return output
def parse_juniper(output: Iterable) -> Dict: # noqa: C901 """Parse a Juniper BGP XML response.""" data = {} for i, response in enumerate(output): cleaned = clean_xml_output(response) try: parsed = xmltodict.parse(cleaned, force_list=("rt", "rt-entry", "community")) log.debug("Initially Parsed Response: \n{}", parsed) if "rpc-reply" in parsed.keys(): parsed_base = parsed["rpc-reply"]["route-information"] elif "route-information" in parsed.keys(): parsed_base = parsed["route-information"] if "route-table" not in parsed_base: raise ResponseEmpty(params.messages.no_output) if "rt" not in parsed_base["route-table"]: raise ResponseEmpty(params.messages.no_output) parsed = parsed_base["route-table"] validated = JuniperRoute(**parsed) serialized = validated.serialize().export_dict() if i == 0: data.update(serialized) else: data["routes"].extend(serialized["routes"]) except xmltodict.expat.ExpatError as err: log.critical(str(err)) raise ParsingError("Error parsing response data") from err except KeyError as err: log.critical("{} was not found in the response", str(err)) raise ParsingError("Error parsing response data") except ValidationError as err: log.critical(str(err)) raise ParsingError(err.errors()) return data
async def response(self): """Initiate query validation and execution.""" device = getattr(devices, self.query_location) log.debug(f"Received query for {self.query_data}") log.debug(f"Matched device config: {device}") supported, transport = validate_nos(device.nos) connect = None output = params.messages.general connect = Connect(device, self.query_data, transport) if supported and transport == "rest": output = await connect.rest() elif supported and transport == "scrape": if device.proxy: output = await connect.scrape_proxied() else: output = await connect.scrape_direct() else: raise ConfigError('"{nos}" is not supported.', nos=device.nos) if output == "" or output == "\n": raise ResponseEmpty(params.messages.no_output, device_name=device.display_name) log.debug( f"Output for query: {self.query_data.json()}:\n{repr(output)}") return output
def parse_juniper(output): """Parse a Juniper BGP XML response.""" data = {} for i, response in enumerate(output): try: parsed = xmltodict.parse(response, force_list=("rt", "rt-entry", "community")) if "rpc-reply" in parsed.keys(): parsed_base = parsed["rpc-reply"]["route-information"] elif "route-information" in parsed.keys(): parsed_base = parsed["route-information"] if "route-table" not in parsed_base: raise ResponseEmpty(params.messages.no_output) if "rt" not in parsed_base["route-table"]: raise ResponseEmpty(params.messages.no_output) parsed = parsed_base["route-table"] validated = JuniperRoute(**parsed) serialized = validated.serialize().export_dict() if i == 0: data.update(serialized) else: data["routes"].extend(serialized["routes"]) except xmltodict.expat.ExpatError as err: log.critical(str(err)) raise ParsingError("Error parsing response data") except KeyError as err: log.critical(f"'{str(err)}' was not found in the response") raise ParsingError("Error parsing response data") return data
async def execute(query: Query) -> Union[str, Sequence[Dict]]: """Initiate query validation and execution.""" output = params.messages.general log.debug("Received query for {}", query.json()) log.debug("Matched device config: {}", query.device) supported, driver_name = validate_nos(query.device.nos) mapped_driver = DRIVER_MAP.get(driver_name, NetmikoConnection) driver = mapped_driver(query.device, query) timeout_args = { "unformatted_msg": params.messages.connection_error, "device_name": query.device.name, "error": params.messages.request_timeout, } if query.device.proxy: timeout_args["proxy"] = query.device.proxy.name signal.signal(signal.SIGALRM, handle_timeout(**timeout_args)) signal.alarm(params.request_timeout - 1) if query.device.proxy: proxy = driver.setup_proxy() with proxy() as tunnel: response = await driver.collect( tunnel.local_bind_host, tunnel.local_bind_port ) else: response = await driver.collect() output = await driver.parsed_response(response) if output == "" or output == "\n": raise ResponseEmpty(params.messages.no_output, device_name=query.device.name) log.debug("Output for query: {}:\n{}", query.json(), repr(output)) signal.alarm(0) return output
async def collect(self) -> Iterable: # noqa: C901 """Connect to a device running hyperglass-agent via HTTP.""" log.debug("Query parameters: {}", self.query) client_params = { "headers": { "Content-Type": "application/json" }, "timeout": params.request_timeout, } if self.device.ssl is not None and self.device.ssl.enable: with self.device.ssl.cert.open("r") as file: cert = file.read() if not cert: raise RestError( "SSL Certificate for device {d} has not been imported", level="danger", d=self.device.display_name, ) http_protocol = "https" client_params.update({"verify": str(self.device.ssl.cert)}) log.debug( (f"Using {str(self.device.ssl.cert)} to validate connection " f"to {self.device.name}")) else: http_protocol = "http" endpoint = "{protocol}://{address}:{port}/query/".format( protocol=http_protocol, address=self.device._target, port=self.device.port) log.debug("URL endpoint: {}", endpoint) try: async with httpx.AsyncClient(**client_params) as http_client: responses = () for query in self.query: encoded_query = await jwt_encode( payload=query, secret=self.device.credential.password. get_secret_value(), duration=params.request_timeout, ) log.debug("Encoded JWT: {}", encoded_query) raw_response = await http_client.post( endpoint, json={"encoded": encoded_query}) log.debug("HTTP status code: {}", raw_response.status_code) raw = raw_response.text log.debug("Raw Response:\n{}", raw) if raw_response.status_code == 200: decoded = await jwt_decode( payload=raw_response.json()["encoded"], secret=self.device.credential.password. get_secret_value(), ) log.debug("Decoded Response:\n{}", decoded) responses += (decoded, ) elif raw_response.status_code == 204: raise ResponseEmpty( params.messages.no_output, device_name=self.device.display_name, ) else: log.error(raw_response.text) except httpx.exceptions.HTTPError as rest_error: msg = parse_exception(rest_error) log.error("Error connecting to device {}: {}", self.device.name, msg) raise RestError( params.messages.connection_error, device_name=self.device.display_name, error=msg, ) except OSError as ose: log.critical(str(ose)) raise RestError( params.messages.connection_error, device_name=self.device.display_name, error="System error", ) except CertificateError as cert_error: log.critical(str(cert_error)) msg = parse_exception(cert_error) raise RestError( params.messages.connection_error, device_name=self.device.display_name, error=f"{msg}: {cert_error}", ) if raw_response.status_code != 200: log.error("Response code is {}", raw_response.status_code) raise RestError( params.messages.connection_error, device_name=self.device.display_name, error=params.messages.general, ) if not responses: log.error("No response from device {}", self.device.name) raise RestError( params.messages.connection_error, device_name=self.device.display_name, error=params.messages.no_response, ) return responses