From 1725a26e2950a3d98e2af8c98759e11a47b2f90e Mon Sep 17 00:00:00 2001 From: Naud Ghebre <30516046+naudzghebre@users.noreply.github.com> Date: Tue, 8 Nov 2022 12:54:18 -0500 Subject: [PATCH] Switch the way we retrieve the vm_service_port from /hub to iquery, on device. (#114834) --- .../lib/src/fuchsia_remote_connection.dart | 60 +++-- .../test/fuchsia_remote_connection_test.dart | 214 +++++++++++------- 2 files changed, 174 insertions(+), 100 deletions(-) diff --git a/packages/fuchsia_remote_debug_protocol/lib/src/fuchsia_remote_connection.dart b/packages/fuchsia_remote_debug_protocol/lib/src/fuchsia_remote_connection.dart index 200e0fb829..d71af21468 100644 --- a/packages/fuchsia_remote_debug_protocol/lib/src/fuchsia_remote_connection.dart +++ b/packages/fuchsia_remote_debug_protocol/lib/src/fuchsia_remote_connection.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:convert'; import 'dart:io'; import 'package:process/process.dart'; @@ -506,6 +507,39 @@ class FuchsiaRemoteConnection { _pollDartVms = true; } + /// Helper for getDeviceServicePorts() to extract the vm_service_port from + /// json response. + List getVmServicePortFromInspectSnapshot(dynamic inspectSnapshot) { + final List> snapshot = + List>.from(inspectSnapshot as List); + final List ports = []; + + for (final Map item in snapshot) { + if (!item.containsKey('payload') || item['payload'] == null) { + continue; + } + final Map payload = + Map.from(item['payload'] as Map); + + if (!payload.containsKey('root') || payload['root'] == null) { + continue; + } + final Map root = + Map.from(payload['root'] as Map); + + if (!root.containsKey('vm_service_port') || + root['vm_service_port'] == null) { + continue; + } + + final int? port = int.tryParse(root['vm_service_port'] as String); + if (port != null) { + ports.add(port); + } + } + return ports; + } + /// Gets the open Dart VM service ports on a remote Fuchsia device. /// /// The method attempts to get service ports through an SSH connection. Upon @@ -514,24 +548,14 @@ class FuchsiaRemoteConnection { /// found. An exception is thrown in the event of an actual error when /// attempting to acquire the ports. Future> getDeviceServicePorts() async { - final List portPaths = await _sshCommandRunner - .run('/bin/find /hub -name vmservice-port'); - final List ports = []; - for (final String path in portPaths) { - if (path == '') { - continue; - } - final List lsOutput = - await _sshCommandRunner.run('/bin/ls $path'); - for (final String line in lsOutput) { - if (line == '') { - continue; - } - final int? port = int.tryParse(line); - if (port != null) { - ports.add(port); - } - } + final List inspectResult = await _sshCommandRunner + .run("iquery --format json show '**:root:vm_service_port'"); + final dynamic inspectOutputJson = jsonDecode(inspectResult.join('\n')); + final List ports = + getVmServicePortFromInspectSnapshot(inspectOutputJson); + + if (ports.length > 1) { + throw StateError('More than one Flutter observatory port found'); } return ports; } diff --git a/packages/fuchsia_remote_debug_protocol/test/fuchsia_remote_connection_test.dart b/packages/fuchsia_remote_debug_protocol/test/fuchsia_remote_connection_test.dart index f67013e93d..d9c9ea2aa5 100644 --- a/packages/fuchsia_remote_debug_protocol/test/fuchsia_remote_connection_test.dart +++ b/packages/fuchsia_remote_debug_protocol/test/fuchsia_remote_connection_test.dart @@ -82,7 +82,7 @@ void main() { restoreVmServiceConnectionFunction(); }); - test('end-to-end with three vm connections and flutter view query', () async { + test('end-to-end with one vm connection and flutter view query', () async { int port = 0; Future fakePortForwardingFunction( String address, @@ -102,54 +102,72 @@ void main() { fuchsiaPortForwardingFunction = fakePortForwardingFunction; final FakeSshCommandRunner fakeRunner = FakeSshCommandRunner(); // Adds some extra junk to make sure the strings will be cleaned up. - fakeRunner.findResponse = ['/hub/blah/blah/blah/vmservice-port\n']; - fakeRunner.lsResponse = ['123\n\n\n', '456 ', '789']; + fakeRunner.iqueryResponse = [ + '[', + ' {', + ' "data_source": "Inspect",', + ' "metadata": {', + ' "filename": "fuchsia.inspect.Tree",', + ' "component_url": "fuchsia-pkg://fuchsia.com/flutter_runner#meta/flutter_runner.cm",', + ' "timestamp": 12345678901234', + ' },', + ' "moniker": "core/session-manager/session/flutter_runner",', + ' "payload": {', + ' "root": {', + ' "vm_service_port": "12345",', + ' "16859221": {', + ' "empty_tree": "this semantic tree is empty"', + ' },', + ' "build_info": {', + ' "dart_sdk_git_revision": "77e83fcc14fa94049f363d554579f48fbd6bb7a1",', + ' "dart_sdk_semantic_version": "2.19.0-317.0.dev",', + ' "flutter_engine_git_revision": "563b8e830c697a543bf0a8a9f4ae3edfad86ea86",', + ' "fuchsia_sdk_version": "10.20221018.0.1"', + ' },', + ' "vm": {', + ' "dst_status": 1,', + ' "get_profile_status": 0,', + ' "num_get_profile_calls": 1,', + ' "num_intl_provider_errors": 0,', + ' "num_on_change_calls": 0,', + ' "timezone_content_status": 0,', + ' "tz_data_close_status": -1,', + ' "tz_data_status": -1', + ' }', + ' }', + ' },', + ' "version": 1', + ' }', + ' ]' + ]; fakeRunner.address = 'fe80::8eae:4cff:fef4:9247'; fakeRunner.interface = 'eno1'; final FuchsiaRemoteConnection connection = await FuchsiaRemoteConnection.connectWithSshCommandRunner(fakeRunner); - // [fakePortForwardingFunction] will have returned three different - // forwarded ports, incrementing the port each time by one. (Just a sanity - // check that the forwarding port was called). - expect(forwardedPorts.length, 3); - expect(forwardedPorts[0].remotePort, 123); - expect(forwardedPorts[1].remotePort, 456); - expect(forwardedPorts[2].remotePort, 789); - expect(forwardedPorts[0].port, 0); - expect(forwardedPorts[1].port, 1); - expect(forwardedPorts[2].port, 2); + expect(forwardedPorts.length, 1); + expect(forwardedPorts[0].remotePort, 12345); // VMs should be accessed via localhost ports given by // [fakePortForwardingFunction]. expect(uriConnections[0], - Uri(scheme:'ws', host:'[::1]', port:0, path:'/ws')); - expect(uriConnections[1], - Uri(scheme:'ws', host:'[::1]', port:1, path:'/ws')); - expect(uriConnections[2], - Uri(scheme:'ws', host:'[::1]', port:2, path:'/ws')); + Uri(scheme: 'ws', host: '[::1]', port: 0, path: '/ws')); final List views = await connection.getFlutterViews(); expect(views, isNot(null)); - expect(views.length, 3); + expect(views.length, 1); // Since name can be null, check for the ID on all of them. expect(views[0].id, 'flutterView0'); - expect(views[1].id, 'flutterView1'); - expect(views[2].id, 'flutterView2'); expect(views[0].name, equals(null)); - expect(views[1].name, 'file://flutterBinary1'); - expect(views[2].name, 'file://flutterBinary2'); // Ensure the ports are all closed after stop was called. await connection.stop(); expect(forwardedPorts[0].stopped, true); - expect(forwardedPorts[1].stopped, true); - expect(forwardedPorts[2].stopped, true); }); - test('end-to-end with three vms and remote open port', () async { + test('end-to-end with one vm and remote open port', () async { int port = 0; Future fakePortForwardingFunction( String address, @@ -170,53 +188,71 @@ void main() { fuchsiaPortForwardingFunction = fakePortForwardingFunction; final FakeSshCommandRunner fakeRunner = FakeSshCommandRunner(); // Adds some extra junk to make sure the strings will be cleaned up. - fakeRunner.findResponse = ['/hub/blah/blah/blah/vmservice-port\n']; - fakeRunner.lsResponse = ['123\n\n\n', '456 ', '789']; + fakeRunner.iqueryResponse = [ + '[', + ' {', + ' "data_source": "Inspect",', + ' "metadata": {', + ' "filename": "fuchsia.inspect.Tree",', + ' "component_url": "fuchsia-pkg://fuchsia.com/flutter_runner#meta/flutter_runner.cm",', + ' "timestamp": 12345678901234', + ' },', + ' "moniker": "core/session-manager/session/flutter_runner",', + ' "payload": {', + ' "root": {', + ' "vm_service_port": "12345",', + ' "16859221": {', + ' "empty_tree": "this semantic tree is empty"', + ' },', + ' "build_info": {', + ' "dart_sdk_git_revision": "77e83fcc14fa94049f363d554579f48fbd6bb7a1",', + ' "dart_sdk_semantic_version": "2.19.0-317.0.dev",', + ' "flutter_engine_git_revision": "563b8e830c697a543bf0a8a9f4ae3edfad86ea86",', + ' "fuchsia_sdk_version": "10.20221018.0.1"', + ' },', + ' "vm": {', + ' "dst_status": 1,', + ' "get_profile_status": 0,', + ' "num_get_profile_calls": 1,', + ' "num_intl_provider_errors": 0,', + ' "num_on_change_calls": 0,', + ' "timezone_content_status": 0,', + ' "tz_data_close_status": -1,', + ' "tz_data_status": -1', + ' }', + ' }', + ' },', + ' "version": 1', + ' }', + ' ]' + ]; fakeRunner.address = 'fe80::8eae:4cff:fef4:9247'; fakeRunner.interface = 'eno1'; final FuchsiaRemoteConnection connection = await FuchsiaRemoteConnection.connectWithSshCommandRunner(fakeRunner); - // [fakePortForwardingFunction] will have returned three different - // forwarded ports, incrementing the port each time by one. (Just a sanity - // check that the forwarding port was called). - expect(forwardedPorts.length, 3); - expect(forwardedPorts[0].remotePort, 123); - expect(forwardedPorts[1].remotePort, 456); - expect(forwardedPorts[2].remotePort, 789); - expect(forwardedPorts[0].port, 0); - expect(forwardedPorts[1].port, 1); - expect(forwardedPorts[2].port, 2); + expect(forwardedPorts.length, 1); + expect(forwardedPorts[0].remotePort, 12345); // VMs should be accessed via the alternate address given by // [fakePortForwardingFunction]. expect(uriConnections[0], - Uri(scheme:'ws', host:'[fe80::1:2%25eno2]', port:0, path:'/ws')); - expect(uriConnections[1], - Uri(scheme:'ws', host:'[fe80::1:2%25eno2]', port:1, path:'/ws')); - expect(uriConnections[2], - Uri(scheme:'ws', host:'[fe80::1:2%25eno2]', port:2, path:'/ws')); + Uri(scheme: 'ws', host: '[fe80::1:2%25eno2]', port: 0, path: '/ws')); final List views = await connection.getFlutterViews(); expect(views, isNot(null)); - expect(views.length, 3); + expect(views.length, 1); // Since name can be null, check for the ID on all of them. expect(views[0].id, 'flutterView0'); - expect(views[1].id, 'flutterView1'); - expect(views[2].id, 'flutterView2'); expect(views[0].name, equals(null)); - expect(views[1].name, 'file://flutterBinary1'); - expect(views[2].name, 'file://flutterBinary2'); // Ensure the ports are all closed after stop was called. await connection.stop(); expect(forwardedPorts[0].stopped, true); - expect(forwardedPorts[1].stopped, true); - expect(forwardedPorts[2].stopped, true); }); - test('end-to-end with three vms and ipv4', () async { + test('end-to-end with one vm and ipv4', () async { int port = 0; Future fakePortForwardingFunction( String address, @@ -236,49 +272,67 @@ void main() { fuchsiaPortForwardingFunction = fakePortForwardingFunction; final FakeSshCommandRunner fakeRunner = FakeSshCommandRunner(); // Adds some extra junk to make sure the strings will be cleaned up. - fakeRunner.findResponse = ['/hub/blah/blah/blah/vmservice-port\n']; - fakeRunner.lsResponse = ['123\n\n\n', '456 ', '789']; + fakeRunner.iqueryResponse = [ + '[', + ' {', + ' "data_source": "Inspect",', + ' "metadata": {', + ' "filename": "fuchsia.inspect.Tree",', + ' "component_url": "fuchsia-pkg://fuchsia.com/flutter_runner#meta/flutter_runner.cm",', + ' "timestamp": 12345678901234', + ' },', + ' "moniker": "core/session-manager/session/flutter_runner",', + ' "payload": {', + ' "root": {', + ' "vm_service_port": "12345",', + ' "16859221": {', + ' "empty_tree": "this semantic tree is empty"', + ' },', + ' "build_info": {', + ' "dart_sdk_git_revision": "77e83fcc14fa94049f363d554579f48fbd6bb7a1",', + ' "dart_sdk_semantic_version": "2.19.0-317.0.dev",', + ' "flutter_engine_git_revision": "563b8e830c697a543bf0a8a9f4ae3edfad86ea86",', + ' "fuchsia_sdk_version": "10.20221018.0.1"', + ' },', + ' "vm": {', + ' "dst_status": 1,', + ' "get_profile_status": 0,', + ' "num_get_profile_calls": 1,', + ' "num_intl_provider_errors": 0,', + ' "num_on_change_calls": 0,', + ' "timezone_content_status": 0,', + ' "tz_data_close_status": -1,', + ' "tz_data_status": -1', + ' }', + ' }', + ' },', + ' "version": 1', + ' }', + ' ]' + ]; fakeRunner.address = '196.168.1.4'; final FuchsiaRemoteConnection connection = await FuchsiaRemoteConnection.connectWithSshCommandRunner(fakeRunner); - // [fakePortForwardingFunction] will have returned three different - // forwarded ports, incrementing the port each time by one. (Just a sanity - // check that the forwarding port was called). - expect(forwardedPorts.length, 3); - expect(forwardedPorts[0].remotePort, 123); - expect(forwardedPorts[1].remotePort, 456); - expect(forwardedPorts[2].remotePort, 789); - expect(forwardedPorts[0].port, 0); - expect(forwardedPorts[1].port, 1); - expect(forwardedPorts[2].port, 2); + expect(forwardedPorts.length, 1); + expect(forwardedPorts[0].remotePort, 12345); // VMs should be accessed via the ipv4 loopback. expect(uriConnections[0], - Uri(scheme:'ws', host:'127.0.0.1', port:0, path:'/ws')); - expect(uriConnections[1], - Uri(scheme:'ws', host:'127.0.0.1', port:1, path:'/ws')); - expect(uriConnections[2], - Uri(scheme:'ws', host:'127.0.0.1', port:2, path:'/ws')); + Uri(scheme: 'ws', host: '127.0.0.1', port: 0, path: '/ws')); final List views = await connection.getFlutterViews(); expect(views, isNot(null)); - expect(views.length, 3); + expect(views.length, 1); // Since name can be null, check for the ID on all of them. expect(views[0].id, 'flutterView0'); - expect(views[1].id, 'flutterView1'); - expect(views[2].id, 'flutterView2'); expect(views[0].name, equals(null)); - expect(views[1].name, 'file://flutterBinary1'); - expect(views[2].name, 'file://flutterBinary2'); // Ensure the ports are all closed after stop was called. await connection.stop(); expect(forwardedPorts[0].stopped, true); - expect(forwardedPorts[1].stopped, true); - expect(forwardedPorts[2].stopped, true); }); test('env variable test without remote addr', () async { @@ -294,15 +348,11 @@ void main() { } class FakeSshCommandRunner extends Fake implements SshCommandRunner { - List? findResponse; - List? lsResponse; + List? iqueryResponse; @override Future> run(String command) async { - if (command.startsWith('/bin/find')) { - return findResponse!; - } - if (command.startsWith('/bin/ls')) { - return lsResponse!; + if (command.startsWith('iquery --format json show')) { + return iqueryResponse!; } throw UnimplementedError(command); }