From 207b6ab9de668864c0ec622fbc68a60c25c2c3f6 Mon Sep 17 00:00:00 2001 From: checktheroads Date: Mon, 1 Feb 2021 01:11:23 -0700 Subject: [PATCH 1/6] fix as path graph layout --- CHANGELOG.md | 69 +++++------ hyperglass/ui/components/path/chart.tsx | 22 ++-- hyperglass/ui/components/path/path.tsx | 2 +- hyperglass/ui/components/path/useElements.ts | 115 +++++++++++++++++++ hyperglass/ui/components/path/util.ts | 68 ----------- hyperglass/ui/package.json | 2 + hyperglass/ui/yarn.lock | 20 ++++ 7 files changed, 184 insertions(+), 114 deletions(-) create mode 100644 hyperglass/ui/components/path/useElements.ts delete mode 100644 hyperglass/ui/components/path/util.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index fe8940d..506ce51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,13 +5,18 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## 1.0.0-beta75 - 2021-01-28 +## 1.0.0-beta.77 - 2021-02-01 + +### Fixed +- AS Path graph view now uses [dagre](https://github.com/dagrejs/dagre) to properly arrange each AS. + +## 1.0.0-beta.75 - 2021-01-28 ### Changed - Default UI build timeout is now 180 seconds. - The hyperglass `build-ui` CLI command now accepts a `--timeout` argument to override the UI build timeout. -## 1.0.0-beta74 - 2021-01-25 +## 1.0.0-beta.74 - 2021-01-25 ### Changed - The Scrapli driver no longer specifically ignores the system's SSH config file. @@ -20,7 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - [#109](https://github.com/checktheroads/hyperglass/issues/109): Remove the custom error page, because it doesn't work and doesn't really add much. -## 1.0.0-beta73 - 2021-01-18 +## 1.0.0-beta.73 - 2021-01-18 ### Added - [#106](https://github.com/checktheroads/hyperglass/issues/106): Add built-in support for Nokia SR OS (thanks @paunadeu!). @@ -32,7 +37,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - [#107](https://github.com/checktheroads/hyperglass/issues/107): Fix footer menu styling so it doesn't overflow the viewport, especially on mobile. -## 1.0.0-beta72 - 2021-01-16 +## 1.0.0-beta.72 - 2021-01-16 ### Fixed - [#104](https://github.com/checktheroads/hyperglass/issues/104): Handle the usage of `juniper_junos` as a NOS. `juniper_junos` will now automatically be mapped to `juniper`. @@ -41,7 +46,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - **BREAKING**: The installer no longer generates a Systemd service file. While this was likely convenient for most, it introduced significant complexity and caused most installations using `~/hyperglass` as the app path to fail, with no clear way to resolve it. Further, while Systemd is arguably the most common, it is not the *only* process manager available. As such, the docs will be updated with a Systemd example, much like the current reverse proxy documentation. -## 1.0.0-beta71 - 2021-01-10 +## 1.0.0-beta.71 - 2021-01-10 ### Added - Added Google Analytics Support. Use the `google_analytics` field for the tracking ID in `hyperglass.yaml`. @@ -49,7 +54,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Minor frontend code improvements. -## 1.0.0-beta70 - 2021-01-05 +## 1.0.0-beta.70 - 2021-01-05 ### Fixed @@ -58,7 +63,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Query results now automatically cancel when each result panel unmounts (e.g. when one clicks the back button). -## 1.0.0-beta69 - 2021-01-03 +## 1.0.0-beta.69 - 2021-01-03 ### Fixed @@ -69,14 +74,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Setup no longer adds example files -## 1.0.0-beta67 - 2021-01-02 +## 1.0.0-beta.67 - 2021-01-02 ### Fixed - Fix handling of `web.theme.default_color_mode`. Starting in 1.0.0-beta.65, it was completely ignored and used the library's default of `light`. Now, it's handled properly. - Fix table output layout issues, particularly on mobile. -## 1.0.0-beta66 - 2021-01-02 +## 1.0.0-beta.66 - 2021-01-02 ### Fixed @@ -87,7 +92,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `web.theme.colors.black` and `web.theme.colors.white` are now `web.theme.colors.dark` and `web.theme.colors.light respectively` -## 1.0.0-beta65 - 2021-01-01 +## 1.0.0-beta.65 - 2021-01-01 ### Added @@ -102,7 +107,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `web.text.title` and `web.text.subtitle` now carry a 32 character limit for simpler styling. - Various UI layout, styling improvements, and stability improvements. -## 1.0.0-beta63 - 2020-10-18 +## 1.0.0-beta.63 - 2020-10-18 ### Added @@ -112,13 +117,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix an issue causing hyperglass custom exceptions to not be properly raised, which caused more generic error messages in the UI/API. -## 1.0.0-beta62 - 2020-10-17 +## 1.0.0-beta.62 - 2020-10-17 ### Fixed - Fix an issue causing exceptions not to be logged to the log file (but logged to stdout). -## 1.0.0-beta61 - 2020-10-11 +## 1.0.0-beta.61 - 2020-10-11 ### POTENTIALLY BREAKING CHANGE @@ -132,7 +137,7 @@ When hyperglass starts up, it will check to see if `~/hyperglass` or `/etc/hyper - [#81](https://github.com/checktheroads/hyperglass/issues/81): Add support for SSH key authentication. See [the docs](https://hyperglass.io/docs/adding-devices#credential) for more details. -## 1.0.0-beta60 - 2020-10-10 +## 1.0.0-beta.60 - 2020-10-10 ### Fixed @@ -154,7 +159,7 @@ This would correspond with the following stanza in the Redis configuration file: requirepass examplepassword ``` -## 1.0.0-beta59 - 2020-10-05 +## 1.0.0-beta.59 - 2020-10-05 ### Added @@ -166,7 +171,7 @@ requirepass examplepassword - Improve output parsing scalability - parsers can now be defined on a per-NOS basis regardless of whether or not structured-data is used. - Restructure model locations & importing to remove some complexities. -## 1.0.0-beta58 - 2020-09-28 +## 1.0.0-beta.58 - 2020-09-28 ### Changed @@ -183,7 +188,7 @@ requirepass examplepassword - [#77](https://github.com/checktheroads/hyperglass/issues/77): Allow dashes in FQDN validation pattern. - [#83](https://github.com/checktheroads/hyperglass/issues/83): Fix lack of support for `protocol-nh` field in Juniper XML BGP table. -## 1.0.0-beta57 - 2020-07-30 +## 1.0.0-beta.57 - 2020-07-30 ### BREAKING CHANGE @@ -201,7 +206,7 @@ $ hyperglass-agent send-certificate - Refactored device, query, proxy models to no longer scrub unsupported characters from the device name for the purposes of Python class attribute accessing. - Updated hyperglass-agent docs. -## 1.0.0-beta56 - 2020-07-28 +## 1.0.0-beta.56 - 2020-07-28 ### Changed @@ -212,19 +217,19 @@ $ hyperglass-agent send-certificate - [#56](https://github.com/checktheroads/hyperglass/issues/56): Fix a silent Redis connection error if the Redis server was anything other than `localhost`, preventing hyperglass from starting. -## 1.0.0-beta55 - 2020-07-27 +## 1.0.0-beta.55 - 2020-07-27 ### Changed - Removed JS favicon build process in favor of native Python implementation ([favicons](https://github/checktheroads/favicons)) -## 1.0.0-beta54 - 2020-07-25 +## 1.0.0-beta.54 - 2020-07-25 ### Fixed - Queries to hyperglass-agent devices failed due to the error `AttributeError: 'AgentConnection' object has no attribute 'collect'` -## 1.0.0-beta53 - 2020-07-23 +## 1.0.0-beta.53 - 2020-07-23 ### Added @@ -240,7 +245,7 @@ $ hyperglass-agent send-certificate - UI: Error messages couldn't be copied with the copy button -## 1.0.0-beta52 - 2020-07-19 +## 1.0.0-beta.52 - 2020-07-19 ### Added @@ -264,7 +269,7 @@ $ hyperglass-agent send-certificate - Improve command customization docs. - [#61](https://github.com/checktheroads/hyperglass/issues/61): Fixed copy output for table data. Output is now a bulleted list of parsed data. -## 1.0.0-beta51 - 2020-07-13 +## 1.0.0-beta.51 - 2020-07-13 ### Changed @@ -276,7 +281,7 @@ $ hyperglass-agent send-certificate - [#54](https://github.com/checktheroads/hyperglass/issues/54): A Junos parsing error caused routes with no communities to raise an error. - Pre-validated config files are no longer logged on startup unless debugging is enabled. -## 1.0.0-beta50 - 2020-07-12 +## 1.0.0-beta.50 - 2020-07-12 ### Added @@ -296,7 +301,7 @@ $ hyperglass-agent send-certificate - [#54](https://github.com/checktheroads/hyperglass/issues/54): A Junos structured/table output parsing error caused routes with multiple next-hops to raise an error. - RPKI validation no longer occurs twice (once on serialization of the output, once on validation of the API response). -## 1.0.0-beta49 - 2020-07-05 +## 1.0.0-beta.49 - 2020-07-05 ### Changed @@ -308,7 +313,7 @@ $ hyperglass-agent send-certificate - Route lookups for private (RFC 1918) addresses failed due to an unnecessary lookup to [bgp.tools](https://bgp.tools) -## 1.0.0-beta48 - 2020-07-04 +## 1.0.0-beta.48 - 2020-07-04 ### Added @@ -320,7 +325,7 @@ $ hyperglass-agent send-certificate - When copying the opengraph image, the copied image was not deleted. - Default traceroute help link now _actually_ points to the new docs site. -## 1.0.0-beta47 - 2020-07-04 +## 1.0.0-beta.47 - 2020-07-04 ### Added @@ -341,13 +346,13 @@ $ hyperglass-agent send-certificate - Generated favicon manifest files now go to the correct directory. - Various docs site fixes -## 1.0.0-beta46 - 2020-06-28 +## 1.0.0-beta.46 - 2020-06-28 ### Added - Support for hyperglass-agent [0.1.5](https://github.com/checktheroads/hyperglass-agent) -## 1.0.0-beta45 - 2020-06-27 +## 1.0.0-beta.45 - 2020-06-27 ### Changed @@ -358,7 +363,7 @@ $ hyperglass-agent send-certificate - Webhook construction bugs that caused webhooks not to send - Empty response handling for table output -## 1.0.0-beta44 - 2020-06-26 +## 1.0.0-beta.44 - 2020-06-26 ### Added @@ -368,13 +373,13 @@ $ hyperglass-agent send-certificate - If webhooks were enabled, a hung test connection to RIPEStat would cause the query to time out -## 1.0.0-beta43 - 2020-06-22 +## 1.0.0-beta.43 - 2020-06-22 ### Fixed - Logo path handling in UI -## 1.0.0-beta42 - 2020-06-21 +## 1.0.0-beta.42 - 2020-06-21 ### Added diff --git a/hyperglass/ui/components/path/chart.tsx b/hyperglass/ui/components/path/chart.tsx index a0b150b..ca20700 100644 --- a/hyperglass/ui/components/path/chart.tsx +++ b/hyperglass/ui/components/path/chart.tsx @@ -1,14 +1,12 @@ -import { useMemo } from 'react'; import { Box, Flex, SkeletonText, Badge, VStack } from '@chakra-ui/react'; import ReactFlow from 'react-flow-renderer'; import { Background, ReactFlowProvider } from 'react-flow-renderer'; import { Handle, Position } from 'react-flow-renderer'; -import { useConfig, useColorValue, useColorToken, useBreakpointValue } from '~/context'; +import { useConfig, useColorValue, useColorToken } from '~/context'; import { useASNDetail } from '~/hooks'; import { Controls } from './controls'; -import { buildElements } from './util'; +import { useElements } from './useElements'; -import type { ReactFlowProps } from 'react-flow-renderer'; import type { TChart, TNode, TNodeData } from './types'; export const Chart: React.FC = (props: TChart) => { @@ -17,19 +15,17 @@ export const Chart: React.FC = (props: TChart) => { const dots = useColorToken('colors', 'blackAlpha.500', 'whiteAlpha.400'); - const flowProps = useBreakpointValue>({ - base: { defaultPosition: [0, 300], defaultZoom: 0 }, - lg: { defaultPosition: [100, 300], defaultZoom: 0.7 }, - }) ?? { defaultPosition: [100, 300], defaultZoom: 0.7 }; - - const elements = useMemo(() => [...buildElements({ asn: primary_asn, name: org_name }, data)], [ - data, - ]); + const elements = useElements({ asn: primary_asn, name: org_name }, data); return ( - + setTimeout(() => inst.fitView(), 0)} + > diff --git a/hyperglass/ui/components/path/path.tsx b/hyperglass/ui/components/path/path.tsx index e4a6ea7..2f301a6 100644 --- a/hyperglass/ui/components/path/path.tsx +++ b/hyperglass/ui/components/path/path.tsx @@ -1,11 +1,11 @@ import { Modal, + Skeleton, ModalBody, ModalHeader, ModalOverlay, ModalContent, useDisclosure, - Skeleton, ModalCloseButton, } from '@chakra-ui/react'; import { useColorValue, useBreakpointValue } from '~/context'; diff --git a/hyperglass/ui/components/path/useElements.ts b/hyperglass/ui/components/path/useElements.ts new file mode 100644 index 0000000..e0916b4 --- /dev/null +++ b/hyperglass/ui/components/path/useElements.ts @@ -0,0 +1,115 @@ +import dagre from 'dagre'; +import { useMemo } from 'react'; +import isEqual from 'react-fast-compare'; + +import type { FlowElement } from 'react-flow-renderer'; +import type { BasePath } from './types'; + +const NODE_WIDTH = 200; +const NODE_HEIGHT = 48; + +export function useElements(base: BasePath, data: TStructuredResponse): FlowElement[] { + return useMemo(() => { + return [...buildElements(base, data)]; + }, [data.routes.length]); +} + +/** + * Calculate the positions for each AS Path. + * @see https://github.com/MrBlenny/react-flow-chart/issues/61 + */ +function* buildElements(base: BasePath, data: TStructuredResponse): Generator { + const { routes } = data; + // Eliminate empty AS paths & deduplicate non-empty AS paths. Length should be same as count minus empty paths. + const asPaths = routes.filter(r => r.as_path.length !== 0).map(r => [...new Set(r.as_path)]); + + const totalPaths = asPaths.length - 1; + + const g = new dagre.graphlib.Graph(); + g.setGraph({ marginx: 20, marginy: 20 }); + g.setDefaultEdgeLabel(() => ({})); + + // Set the origin (i.e., the hyperglass user) at the base. + g.setNode(base.asn, { width: NODE_WIDTH, height: NODE_HEIGHT }); + + for (const [groupIdx, pathGroup] of asPaths.entries()) { + // For each ROUTE's AS Path: + + // Find the route after this one. + const nextGroup = groupIdx < totalPaths ? asPaths[groupIdx + 1] : []; + + // Connect the first hop in the AS Path to the base (for dagre). + g.setEdge(base.asn, `${groupIdx}-${pathGroup[0]}`); + + // Eliminate duplicate AS Paths. + if (!isEqual(pathGroup, nextGroup)) { + for (const [idx, asn] of pathGroup.entries()) { + // For each ASN in the ROUTE: + + const node = `${groupIdx}-${asn}`; + const endIdx = pathGroup.length - 1; + + // Add the AS as a node. + g.setNode(node, { width: NODE_WIDTH, height: NODE_HEIGHT }); + + // Connect the first hop in the AS Path to the base (for react-flow). + if (idx === 0) { + yield { + id: `e${base.asn}-${node}`, + source: base.asn, + target: node, + }; + } + // Connect every intermediate hop to each other. + if (idx !== endIdx) { + const next = `${groupIdx}-${pathGroup[idx + 1]}`; + g.setEdge(node, next); + yield { + id: `e${node}-${next}`, + source: node, + target: next, + }; + } + } + } + } + + // Now that that nodes are added, create the layout. + dagre.layout(g, { rankdir: 'BT', align: 'UR' }); + + // Get the base ASN's positions. + const x = g.node(base.asn).x - NODE_WIDTH / 2; + const y = g.node(base.asn).y + NODE_HEIGHT * 6; + + yield { + id: base.asn, + type: 'ASNode', + position: { x, y }, + data: { asn: base.asn, name: base.name, hasChildren: true, hasParents: false }, + }; + + for (const [groupIdx, pathGroup] of asPaths.entries()) { + const nextGroup = groupIdx < totalPaths ? asPaths[groupIdx + 1] : []; + if (!isEqual(pathGroup, nextGroup)) { + for (const [idx, asn] of pathGroup.entries()) { + const node = `${groupIdx}-${asn}`; + const endIdx = pathGroup.length - 1; + const x = g.node(node).x - NODE_WIDTH / 2; + const y = g.node(node).y - NODE_HEIGHT * (idx * 6); + + // Get each ASN's positions. + yield { + id: node, + type: 'ASNode', + position: { x, y }, + data: { + asn, + name: `AS${asn}`, + hasChildren: idx < endIdx, + hasParents: true, + }, + }; + } + } + } +} diff --git a/hyperglass/ui/components/path/util.ts b/hyperglass/ui/components/path/util.ts deleted file mode 100644 index 67339a2..0000000 --- a/hyperglass/ui/components/path/util.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { arrangeIntoTree } from '~/util'; - -import type { FlowElement, Elements } from 'react-flow-renderer'; -import type { PathPart } from '~/types'; -import type { BasePath } from './types'; - -function treeToElement(part: PathPart, len: number, index: number): FlowElement[] { - const x = index * 250; - const y = -(len * 10); - const elements = [ - { - id: String(part.base), - type: 'ASNode', - position: { x, y }, - data: { - asn: part.base, - name: `AS${part.base}`, - hasChildren: part.children.length !== 0, - hasParents: true, - }, - }, - ] as Elements; - - for (const child of part.children) { - let xc = index; - if (part.children.length !== 0) { - elements.push({ - id: `e${part.base}-${child.base}`, - source: String(part.base), - target: String(child.base), - }); - } else { - xc = x; - } - elements.push(...treeToElement(child, part.children.length * 12 + len, xc)); - } - return elements; -} - -export function* buildElements(base: BasePath, data: TStructuredResponse): Generator { - const { routes } = data; - // Eliminate empty AS paths & deduplicate non-empty AS paths. Length should be same as count minus empty paths. - const asPaths = routes.filter(r => r.as_path.length !== 0).map(r => [...new Set(r.as_path)]); - const asTree = arrangeIntoTree(asPaths); - const numHops = asPaths.flat().length; - const childPaths = asTree.map((a, i) => { - return treeToElement(a, asTree.length, i); - }); - - // Add the first hop at the base. - yield { - id: base.asn, - type: 'ASNode', - position: { x: 150, y: numHops * 10 }, - data: { asn: base.asn, name: base.name, hasChildren: true, hasParents: false }, - }; - - for (const path of childPaths) { - // path = Each unique path from origin - const first = path[0]; - yield { id: `e${base.asn}-${first.id}`, source: base.asn, target: first.id }; - // Add link from base to each first hop. - yield { id: `e${base.asn}-${first.id}`, source: base.asn, target: first.id }; - for (const hop of path) { - yield hop; - } - } -} diff --git a/hyperglass/ui/package.json b/hyperglass/ui/package.json index 82c4f6d..7dea06d 100644 --- a/hyperglass/ui/package.json +++ b/hyperglass/ui/package.json @@ -26,6 +26,7 @@ "@hookstate/persistence": "^3.0.0", "@meronex/icons": "^4.0.0", "color2k": "^1.1.1", + "dagre": "^0.8.5", "dayjs": "^1.8.25", "framer-motion": "^3.2.2-rc.1", "lodash": "^4.17.15", @@ -47,6 +48,7 @@ }, "devDependencies": { "@hookstate/devtools": "^3.0.0", + "@types/dagre": "^0.7.44", "@types/node": "^14.14.17", "@types/react": "^17.0.0", "@types/react-select": "^3.0.28", diff --git a/hyperglass/ui/yarn.lock b/hyperglass/ui/yarn.lock index 344fee9..6f7a449 100644 --- a/hyperglass/ui/yarn.lock +++ b/hyperglass/ui/yarn.lock @@ -1055,6 +1055,11 @@ dependencies: tslib "^2.0.0" +"@types/dagre@^0.7.44": + version "0.7.44" + resolved "https://registry.yarnpkg.com/@types/dagre/-/dagre-0.7.44.tgz#8f4b796b118ca29c132da7068fbc0d0351ee5851" + integrity sha512-N6HD+79w77ZVAaVO7JJDW5yJ9LAxM62FpgNGO9xEde+KVYjDRyhIMzfiErXpr1g0JPon9kwlBzoBK6s4fOww9Q== + "@types/eslint-visitor-keys@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" @@ -2735,6 +2740,14 @@ d@1, d@^1.0.1: es5-ext "^0.10.50" type "^1.0.1" +dagre@^0.8.5: + version "0.8.5" + resolved "https://registry.yarnpkg.com/dagre/-/dagre-0.8.5.tgz#ba30b0055dac12b6c1fcc247817442777d06afee" + integrity sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw== + dependencies: + graphlib "^2.1.8" + lodash "^4.17.15" + damerau-levenshtein@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz#143c1641cb3d85c60c32329e26899adea8701791" @@ -3971,6 +3984,13 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== +graphlib@^2.1.8: + version "2.1.8" + resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-2.1.8.tgz#5761d414737870084c92ec7b5dbcb0592c9d35da" + integrity sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A== + dependencies: + lodash "^4.17.15" + has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" From 7c41c577d0f6d0b8245f71280e3eeb702819f1d6 Mon Sep 17 00:00:00 2001 From: checktheroads Date: Sat, 6 Feb 2021 00:19:04 -0700 Subject: [PATCH 2/6] fix missing build-ui timeout argument --- hyperglass/cli/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hyperglass/cli/commands.py b/hyperglass/cli/commands.py index af7fe9a..b558430 100644 --- a/hyperglass/cli/commands.py +++ b/hyperglass/cli/commands.py @@ -105,7 +105,7 @@ def start(build, direct, workers): # noqa: C901 try: if build: - build_complete = build_ui() + build_complete = build_ui(timeout=180) if build_complete and not direct: start(**kwargs) From 6bef0104462129ac85e025321a707f78e3cab062 Mon Sep 17 00:00:00 2001 From: checktheroads Date: Sat, 6 Feb 2021 00:19:29 -0700 Subject: [PATCH 3/6] add FRR and BIRD SSH support --- hyperglass/constants.py | 2 + hyperglass/execution/drivers/ssh_scrapli.py | 2 + hyperglass/models/commands/__init__.py | 10 +++- hyperglass/models/commands/bird.py | 56 +++++++++++++++++++++ hyperglass/models/commands/frr.py | 56 +++++++++++++++++++++ hyperglass/models/config/devices.py | 2 +- 6 files changed, 125 insertions(+), 3 deletions(-) create mode 100644 hyperglass/models/commands/bird.py create mode 100644 hyperglass/models/commands/frr.py diff --git a/hyperglass/constants.py b/hyperglass/constants.py index 0b4bd60..7087b48 100644 --- a/hyperglass/constants.py +++ b/hyperglass/constants.py @@ -74,12 +74,14 @@ SCRAPE_HELPERS = { DRIVER_MAP = { "arista_eos": "scrapli", + "bird_ssh": "scrapli", "cisco_ios": "scrapli", "cisco_xe": "scrapli", "cisco_xr": "scrapli", "cisco_nxos": "scrapli", "juniper": "scrapli", "tnsr": "scrapli", + "frr_ssh": "scrapli", "frr": "hyperglass_agent", "bird": "hyperglass_agent", } diff --git a/hyperglass/execution/drivers/ssh_scrapli.py b/hyperglass/execution/drivers/ssh_scrapli.py index fafb1d9..7ad2b2c 100644 --- a/hyperglass/execution/drivers/ssh_scrapli.py +++ b/hyperglass/execution/drivers/ssh_scrapli.py @@ -38,9 +38,11 @@ from .ssh import SSHConnection SCRAPLI_DRIVER_MAP = { "arista_eos": AsyncEOSDriver, + "bird_ssh": AsyncGenericDriver, "cisco_ios": AsyncIOSXEDriver, "cisco_nxos": AsyncNXOSDriver, "cisco_xr": AsyncIOSXRDriver, + "frr_ssh": AsyncGenericDriver, "juniper": AsyncJunosDriver, "tnsr": AsyncGenericDriver, } diff --git a/hyperglass/models/commands/__init__.py b/hyperglass/models/commands/__init__.py index 9ca62df..622385d 100644 --- a/hyperglass/models/commands/__init__.py +++ b/hyperglass/models/commands/__init__.py @@ -1,6 +1,7 @@ """Validate command configuration variables.""" # Local +from .frr import FRRCommands from .tnsr import TNSRCommands from .vyos import VyosCommands from ..main import HyperglassModelExtra @@ -14,12 +15,15 @@ from .cisco_nxos import CiscoNXOSCommands from .nokia_sros import NokiaSROSCommands from .mikrotik_routeros import MikrotikRouterOS from .mikrotik_switchos import MikrotikSwitchOS +from .bird import BIRDCommands _NOS_MAP = { "arista": AristaCommands, + "bird_ssh": BIRDCommands, "cisco_ios": CiscoIOSCommands, "cisco_nxos": CiscoNXOSCommands, "cisco_xr": CiscoXRCommands, + "frr_ssh": FRRCommands, "huawei": HuaweiCommands, "juniper": JuniperCommands, "mikrotik_routeros": MikrotikRouterOS, @@ -33,12 +37,14 @@ _NOS_MAP = { class Commands(HyperglassModelExtra): """Base class for command definitions.""" - juniper: CommandGroup = JuniperCommands() arista: CommandGroup = AristaCommands() + bird_ssh: CommandGroup = BIRDCommands() cisco_ios: CommandGroup = CiscoIOSCommands() - cisco_xr: CommandGroup = CiscoXRCommands() cisco_nxos: CommandGroup = CiscoNXOSCommands() + cisco_xr: CommandGroup = CiscoXRCommands() + frr_ssh: CommandGroup = FRRCommands() huawei: CommandGroup = HuaweiCommands() + juniper: CommandGroup = JuniperCommands() mikrotik_routeros: CommandGroup = MikrotikRouterOS() mikrotik_switchos: CommandGroup = MikrotikSwitchOS() nokia_sros: CommandGroup = NokiaSROSCommands() diff --git a/hyperglass/models/commands/bird.py b/hyperglass/models/commands/bird.py new file mode 100644 index 0000000..9a86f0a --- /dev/null +++ b/hyperglass/models/commands/bird.py @@ -0,0 +1,56 @@ +"""BIRD Routing Daemon Command Model.""" + +# Third Party +from pydantic import StrictStr + +# Local +from .common import CommandSet, CommandGroup + + +class _IPv4(CommandSet): + """Default commands for ipv4 commands.""" + + bgp_community: str = 'birdc "show route all where {target} ~ bgp_community"' + bgp_aspath: str = 'birdc "show route all where bgp_path ~ {target}"' + bgp_route: str = 'birdc "show route all where {target} ~ net"' + ping: str = "ping -4 -c 5 -I {source} {target}" + traceroute: str = "traceroute -4 -w 1 -q 1 -s {source} {target}" + + +class _IPv6(CommandSet): + """Default commands for ipv6 commands.""" + + bgp_community: str = 'birdc "show route all where {target} ~ bgp_community"' + bgp_aspath: str = 'birdc "show route all where bgp_path ~ {target}"' + bgp_route: str = 'birdc "show route all where {target} ~ net"' + ping: StrictStr = "ping -6 -c 5 -I {source} {target}" + traceroute: StrictStr = "traceroute -6 -w 1 -q 1 -s {source} {target}" + + +class _VPNIPv4(CommandSet): + """Default commands for dual afi commands.""" + + bgp_community: str = 'birdc "show route all table {vrf} where {target} ~ bgp_community"' + bgp_aspath: str = 'birdc "show route all table {vrf} where bgp_path ~ {target}"' + bgp_route: str = 'birdc "show route all table {vrf} where {target} ~ net"' + ping: StrictStr = "ping -4 -c 5 -I {source} {target}" + traceroute: StrictStr = "traceroute -4 -w 1 -q 1 -s {source} {target}" + + +class _VPNIPv6(CommandSet): + """Default commands for dual afi commands.""" + + bgp_community: str = 'birdc "show route all table {vrf} where {target} ~ bgp_community"' + bgp_aspath: str = 'birdc "show route all table {vrf} where bgp_path ~ {target}"' + bgp_route: str = 'birdc "show route all table {vrf} where {target} ~ net"' + ping: StrictStr = "ping -6 -c 5 -I {source} {target}" + traceroute: StrictStr = "traceroute -6 -w 1 -q 1 -s {source} {target}" + + +class BIRDCommands(CommandGroup): + """Validation model for default BIRD commands.""" + + ipv4_default: _IPv4 = _IPv4() + ipv6_default: _IPv6 = _IPv6() + ipv4_vpn: _VPNIPv4 = _VPNIPv4() + ipv6_vpn: _VPNIPv6 = _VPNIPv6() diff --git a/hyperglass/models/commands/frr.py b/hyperglass/models/commands/frr.py new file mode 100644 index 0000000..2dde708 --- /dev/null +++ b/hyperglass/models/commands/frr.py @@ -0,0 +1,56 @@ +"""FRRouting Command Model.""" + +# Third Party +from pydantic import StrictStr + +# Local +from .common import CommandSet, CommandGroup + + +class _IPv4(CommandSet): + """Default commands for ipv4 commands.""" + + bgp_community: StrictStr = 'vtysh -c "show bgp ipv4 unicast community {target}"' + bgp_aspath: StrictStr = 'vtysh -c "show bgp ipv4 unicast regexp {target}"' + bgp_route: StrictStr = 'vtysh -c "show bgp ipv4 unicast {target}"' + ping: StrictStr = "ping -4 -c 5 -I {source} {target}" + traceroute: StrictStr = "traceroute -4 -w 1 -q 1 -s {source} {target}" + + +class _IPv6(CommandSet): + """Default commands for ipv6 commands.""" + + bgp_community: StrictStr = 'vtysh -c "show bgp ipv6 unicast community {target}"' + bgp_aspath: StrictStr = 'vtysh -c "show bgp ipv6 unicast regexp {target}"' + bgp_route: StrictStr = 'vtysh -c "show bgp ipv6 unicast {target}"' + ping: StrictStr = "ping -6 -c 5 -I {source} {target}" + traceroute: StrictStr = "traceroute -6 -w 1 -q 1 -s {source} {target}" + + +class _VPNIPv4(CommandSet): + """Default commands for dual afi commands.""" + + bgp_community: StrictStr = 'vtysh -c "show bgp vrf {vrf} ipv4 unicast community {target}"' + bgp_aspath: StrictStr = 'vtysh -c "show bgp vrf {vrf} ipv4 unicast regexp {target}"' + bgp_route: StrictStr = 'vtysh -c "show bgp vrf {vrf} ipv4 unicast {target}"' + ping: StrictStr = "ping -4 -c 5 -I {source} {target}" + traceroute: StrictStr = "traceroute -4 -w 1 -q 1 -s {source} {target}" + + +class _VPNIPv6(CommandSet): + """Default commands for dual afi commands.""" + + bgp_community: StrictStr = 'vtysh -c "show bgp vrf {vrf} ipv6 unicast community {target}"' + bgp_aspath: StrictStr = 'vtysh -c "show bgp vrf {vrf} ipv6 unicast regexp {target}"' + bgp_route: StrictStr = 'vtysh -c "show bgp vrf {vrf} ipv6 unicast {target}"' + ping: StrictStr = "ping -6 -c 5 -I {source} {target}" + traceroute: StrictStr = "traceroute -6 -w 1 -q 1 -s {source} {target}" + + +class FRRCommands(CommandGroup): + """Validation model for default FRRouting commands.""" + + ipv4_default: _IPv4 = _IPv4() + ipv6_default: _IPv6 = _IPv6() + ipv4_vpn: _VPNIPv4 = _VPNIPv4() + ipv6_vpn: _VPNIPv6 = _VPNIPv6() diff --git a/hyperglass/models/config/devices.py b/hyperglass/models/config/devices.py index 0379951..ec2b592 100644 --- a/hyperglass/models/config/devices.py +++ b/hyperglass/models/config/devices.py @@ -50,7 +50,7 @@ class Device(HyperglassModel): credential: Credential proxy: Optional[Proxy] display_name: StrictStr - port: StrictInt + port: StrictInt = 22 ssl: Optional[Ssl] nos: StrictStr commands: Optional[StrictStr] From 5fb3fbeb54d08bb77001178b4671def92e97c8c7 Mon Sep 17 00:00:00 2001 From: checktheroads Date: Sat, 6 Feb 2021 00:48:38 -0700 Subject: [PATCH 4/6] update docs with frr/bird information --- docs/docs/adding-devices.mdx | 2 +- docs/docs/commands.mdx | 2 +- docs/docs/platforms.mdx | 24 ++++++++++++++++++++++-- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/docs/docs/adding-devices.mdx b/docs/docs/adding-devices.mdx index 1bf1def..41829b7 100644 --- a/docs/docs/adding-devices.mdx +++ b/docs/docs/adding-devices.mdx @@ -47,7 +47,7 @@ routers: | `address` | String | Device management hostname or IP address. | | `network` | String | [Network Configuration](#network) | | `display_name` | String | Device's user-facing name. | -| `port` | Integer | TCP port used to connect to the device. | +| `port` | Integer | TCP port used to connect to the device. `22` by default. | | `nos` | String | Network Operating System. Must be a supported platform. | | `structured_output` | Boolean | Disabled output parsing to structured data. | | `credential` | | [Device Credential Configuration](#credential) | diff --git a/docs/docs/commands.mdx b/docs/docs/commands.mdx index 96e82fc..e66e287 100644 --- a/docs/docs/commands.mdx +++ b/docs/docs/commands.mdx @@ -30,7 +30,7 @@ hyperglass comes with built in support for the following platforms: - Huawei VRP - VyOS -:::warning VyOS & VRFs +:::caution VyOS & VRFs As of `vyos-1.3-rolling-202007050117` which is the latest release VyOS has been tested with hyperglass, VyOS does not support BGP or other dynamic routing protocols in a VRF. As such, the default BGP commands for VyOS **omit the VRF from the command**. ::: diff --git a/docs/docs/platforms.mdx b/docs/docs/platforms.mdx index 116b871..145f0ac 100644 --- a/docs/docs/platforms.mdx +++ b/docs/docs/platforms.mdx @@ -11,8 +11,12 @@ description: Platforms supported by hyperglass The following platforms use [hyperglass-agent](agent/installation.mdx) for connection handling. When configuring the `nos` property of a device, use the value in the **Key** column. +:::caution hyperglass agent is being deprecated +While hyperglass-agent was a cool idea, maintaining the two codebases and protocol sets has proven cumbersome, with very little benefit. As of **v1.0.0-beta.76**, `frr_ssh` and `bird_ssh` are available for use as replacements. +::: + | Name | Key | -| --------- | ------ | +| :-------- | :----- | | BIRD | `bird` | | FRRouting | `frr` | @@ -21,7 +25,7 @@ The following platforms use [hyperglass-agent](agent/installation.mdx) for conne The following platforms use [Netmiko](https://github.com/ktbyers/netmiko) or [Scrapli](https://github.com/carlmontanari/scrapli) for connection handling. When configuring the `nos` property of a device, use the value in the **Key** column. | Name | Key | -| ------------------------- | --------------------- | +| :------------------------ | :-------------------- | | A10 | `a10` | | Accedian | `accedian` | | Alcatel AOS | `alcatel_aos` | @@ -31,6 +35,7 @@ The following platforms use [Netmiko](https://github.com/ktbyers/netmiko) or [Sc | Aruba | `aruba_os` | | Avaya ERS | `avaya_ers` | | Avaya VSP | `avaya_vsp` | +| BIRD | `bird_ssh` | | Calix B6 | `calix_b6` | | Check Point GAiA | `checkpoint_gaia` | | Ciena SAOS | `ciena_saos` | @@ -65,6 +70,7 @@ The following platforms use [Netmiko](https://github.com/ktbyers/netmiko) or [Sc | F5 Linux | `f5_linux` | | Flex VNF | `flexvnf` | | Fortinet | `fortinet` | +| FRRouting | `frr_ssh` | | Generic Terminal Server | `generic_termserver` | | HPE/3COM Comware | `hp_comware` | | HPE ProCurve | `hp_procurve` | @@ -97,3 +103,17 @@ The following platforms use [Netmiko](https://github.com/ktbyers/netmiko) or [Sc | Ubuiquiti EdgeSwitch | `ubiquiti_edgeswitch` | | Vyatta VyOS | `vyatta_vyos` | | VyOS | `vyos` | + +## Caveats + +### BIRD + +If using BIRD, more specifically [`bird_ssh`](platforms.mdx), you'll more than likely need to provide hyperglass with credentials that have root privileges. This is because, to my knowledge, the socket `birdc` uses is created by root. Therefore, if hyperglass runs the `birdc` commands as an unprivileged user, they will fail with a permissions error. + +### FRRouting + +The user account you provide hyperglass in the `credential` field needs to be a member of the `frrvty` group. See [the FRR docs](http://docs.frrouting.org/en/latest/vtysh.html) for more detail. + +### VyOS + +As of `vyos-1.3-rolling-202007050117` which is the latest release VyOS has been tested with hyperglass, VyOS does not support BGP or other dynamic routing protocols in a VRF. As such, the default BGP commands for VyOS **omit the VRF from the command**. From 5e7e7c122634ed10433b579602e84b49bef710ec Mon Sep 17 00:00:00 2001 From: checktheroads Date: Sat, 6 Feb 2021 00:55:15 -0700 Subject: [PATCH 5/6] bump version to 1.0.0-beta.76 --- CHANGELOG.md | 11 ++++++++++- hyperglass/constants.py | 2 +- pyproject.toml | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 506ce51..87dafbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,10 +5,19 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## 1.0.0-beta.77 - 2021-02-01 +## 1.0.0-beta.76 - 2021-02-06 + +**NOTICE**: *[hyperglass-agent](https://github.com/checktheroads/hyperglass-agent) will be deprecated soon. Use `frr_ssh` or `bird_ssh` for SSH connectivity in the meantime.* + +### Added +- FRR & BIRD may now be accessed via standard SSH using the `frr_ssh` and `bird_ssh` NOS. [See the docs](https://hyperglass.io/docs/platforms#caveats) for important caveats. + +### Changed +- `port` in `devices.yaml` now defaults to 22 if not specified. ### Fixed - AS Path graph view now uses [dagre](https://github.com/dagrejs/dagre) to properly arrange each AS. +- Added timeout argument to `hyperglass start --build` - fixes issue where running a UI build in this way failed due to a missing timeout argument error. ## 1.0.0-beta.75 - 2021-01-28 diff --git a/hyperglass/constants.py b/hyperglass/constants.py index 7087b48..9aefe7a 100644 --- a/hyperglass/constants.py +++ b/hyperglass/constants.py @@ -4,7 +4,7 @@ from datetime import datetime __name__ = "hyperglass" -__version__ = "1.0.0-beta.75" +__version__ = "1.0.0-beta.76" __author__ = "Matt Love" __copyright__ = f"Copyright {datetime.now().year} Matthew Love" __license__ = "BSD 3-Clause Clear License" diff --git a/pyproject.toml b/pyproject.toml index 9190005..37a0520 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ license = "BSD-3-Clause-Clear" name = "hyperglass" readme = "README.md" repository = "https://github.com/checktheroads/hyperglass" -version = "1.0.0-beta.75" +version = "1.0.0-beta.76" [tool.poetry.scripts] hyperglass = "hyperglass.console:CLI" From f15fb4df28fc2defd4c0653e6a0112e51d316c1c Mon Sep 17 00:00:00 2001 From: checktheroads Date: Sat, 6 Feb 2021 00:59:16 -0700 Subject: [PATCH 6/6] fix code style --- hyperglass/models/commands/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hyperglass/models/commands/__init__.py b/hyperglass/models/commands/__init__.py index 622385d..17f408e 100644 --- a/hyperglass/models/commands/__init__.py +++ b/hyperglass/models/commands/__init__.py @@ -2,6 +2,7 @@ # Local from .frr import FRRCommands +from .bird import BIRDCommands from .tnsr import TNSRCommands from .vyos import VyosCommands from ..main import HyperglassModelExtra @@ -15,7 +16,6 @@ from .cisco_nxos import CiscoNXOSCommands from .nokia_sros import NokiaSROSCommands from .mikrotik_routeros import MikrotikRouterOS from .mikrotik_switchos import MikrotikSwitchOS -from .bird import BIRDCommands _NOS_MAP = { "arista": AristaCommands,