def test_get_kernel_params_filters_out_unnecessary_arguments(self): params_okay = { name.decode("ascii"): factory.make_name("value") for name, _ in GetBootConfig.arguments } params_other = { factory.make_name("name"): factory.make_name("value") for _ in range(3) } params_all = params_okay.copy() params_all.update(params_other) client = Mock() client.localIdent = params_okay["system_id"] client_service = Mock() client_service.getClientNow.return_value = succeed(client) backend = TFTPBackend(self.make_dir(), client_service) backend.fetcher = Mock() backend.get_kernel_params(params_all) self.assertThat( backend.fetcher, MockCalledOnceWith(client, GetBootConfig, **params_okay), )
def test_get_reader_converts_BootConfigNoResponse_to_FileNotFound(self): client = Mock() client.localIdent = factory.make_name("system_id") client.return_value = fail(BootConfigNoResponse()) client_service = Mock() client_service.getClientNow.return_value = succeed(client) backend = TFTPBackend(self.make_dir(), client_service) with ExpectedException(FileNotFound): yield backend.get_reader(b"pxelinux.cfg/default")
def test_get_boot_method_reader_returns_rendered_params(self): # Fake configuration parameters, as discovered from the file path. fake_params = {"mac": factory.make_mac_address("-")} # Fake kernel configuration parameters, as returned from the RPC call. fake_kernel_params = make_kernel_parameters() fake_params = fake_kernel_params._asdict() # Stub the output of list_boot_images so the label is set in the # kernel parameters. boot_image = { "osystem": fake_params["osystem"], "release": fake_params["release"], "architecture": fake_params["arch"], "subarchitecture": fake_params["subarch"], "purpose": fake_params["purpose"], "supported_subarches": "", "label": fake_params["label"], } self.patch(tftp_module, "list_boot_images").return_value = [boot_image] del fake_params["label"] # Stub RPC call to return the fake configuration parameters. client = Mock() client.localIdent = factory.make_name("system_id") client.return_value = succeed(fake_params) client_service = Mock() client_service.getClientNow.return_value = succeed(client) # get_boot_method_reader() takes a dict() of parameters and returns an # `IReader` of a PXE configuration, rendered by # `PXEBootMethod.get_reader`. backend = TFTPBackend(self.make_dir(), client_service) # Stub get_reader to return the render parameters. method = PXEBootMethod() fake_render_result = factory.make_name("render").encode("utf-8") render_patch = self.patch(method, "get_reader") render_patch.return_value = BytesReader(fake_render_result) # Get the rendered configuration, which will actually be a JSON dump # of the render-time parameters. params_with_ip = dict(fake_params) params_with_ip['remote_ip'] = factory.make_ipv4_address() reader = yield backend.get_boot_method_reader(method, params_with_ip) self.addCleanup(reader.finish) self.assertIsInstance(reader, BytesReader) output = reader.read(10000) # The result has been rendered by `method.get_reader`. self.assertEqual(fake_render_result, output) self.assertThat( method.get_reader, MockCalledOnceWith(backend, kernel_params=fake_kernel_params, **params_with_ip))
def test_get_boot_method_reader_rendered_parms_for_depoyed_ephemeral(self): # Fake kernel configuration parameters, as returned from the RPC call. fake_kernel_params = make_kernel_parameters( purpose="local", label="local", osystem="caringo" ) fake_params = fake_kernel_params._asdict() # Stub the output of list_boot_images so the label is set in the # kernel parameters. boot_image = { "osystem": fake_params["osystem"], "release": fake_params["release"], "architecture": fake_params["arch"], "subarchitecture": fake_params["subarch"], "purpose": "ephemeral", "supported_subarches": "", "label": fake_params["label"], } self.patch(tftp_module, "list_boot_images").return_value = [boot_image] del fake_params["label"] # Stub RPC call to return the fake configuration parameters. client = Mock() client.localIdent = factory.make_name("system_id") client.return_value = succeed(fake_params) client_service = Mock() client_service.getClient.return_value = client # get_boot_method_reader() takes a dict() of parameters and returns an # `IReader` of a PXE configuration, rendered by # `PXEBootMethod.get_reader`. backend = TFTPBackend(self.make_dir(), client_service) # Stub get_reader to return the render parameters. method = PXEBootMethod() fake_render_result = factory.make_name("render").encode("utf-8") render_patch = self.patch(method, "get_reader") render_patch.return_value = BytesReader(fake_render_result) # Get the rendered configuration, which will actually be a JSON dump # of the render-time parameters. reader = yield backend.get_boot_method_reader(method, fake_params) self.addCleanup(reader.finish) self.assertIsInstance(reader, BytesReader) output = reader.read(10000) # The result has been rendered by `method.get_reader`. self.assertEqual(fake_render_result, output)
def test_get_boot_method_render_substitutes_armhf_in_params(self): # get_config_reader() should substitute "arm" for "armhf" in the # arch field of the parameters (mapping from pxe to maas # namespace). config_path = b"pxelinux.cfg/default-arm" backend = TFTPBackend(self.make_dir(), "http://example.com/") # python-tx-tftp sets up call context so that backends can discover # more about the environment in which they're running. call_context = { "local": (factory.make_ipv4_address(), factory.pick_port()), "remote": (factory.make_ipv4_address(), factory.pick_port()), } @partial(self.patch, backend, "get_boot_method_reader") def get_boot_method_reader(boot_method, params): params_json = json.dumps(params).encode("ascii") params_json_reader = BytesReader(params_json) return succeed(params_json_reader) reader = yield context.call( call_context, backend.get_reader, config_path ) output = reader.read(10000).decode("ascii") observed_params = json.loads(output) # XXX: GavinPanella 2015-11-25 bug=1519804: get_by_pxealias() on # ArchitectureRegistry is not stable, so we permit either here. self.assertIn(observed_params["arch"], ["armhf", "arm64"])
def _test_get_render_file(self, local, remote): # For paths matching PXEBootMethod.match_path, TFTPBackend.get_reader() # returns a Deferred that will yield a BytesReader. mac = factory.make_mac_address("-") config_path = compose_config_path(mac) backend = TFTPBackend(self.make_dir(), Mock()) # python-tx-tftp sets up call context so that backends can discover # more about the environment in which they're running. call_context = {"local": local, "remote": remote} @partial(self.patch, backend, "get_boot_method_reader") def get_boot_method_reader(boot_method, params): params_json = json.dumps(params).encode("ascii") params_json_reader = BytesReader(params_json) return succeed(params_json_reader) reader = yield context.call( call_context, backend.get_reader, config_path ) output = reader.read(10000).decode("ascii") # The addresses provided by python-tx-tftp in the call context are # passed over the wire as address:port strings. expected_params = { "mac": mac, "local_ip": call_context["local"][0], # address only. "remote_ip": call_context["remote"][0], # address only. "bios_boot_method": "pxe", } observed_params = json.loads(output) self.assertEqual(expected_params, observed_params)
def test_get_reader_handles_backslashes_in_path(self): data = factory.make_string().encode("ascii") temp_dir = self.make_dir() subdir = factory.make_name("subdir") filename = factory.make_name("file") os.mkdir(os.path.join(temp_dir, subdir)) factory.make_file(os.path.join(temp_dir, subdir), filename, data) path = ("\\%s\\%s" % (subdir, filename)).encode("ascii") backend = TFTPBackend(temp_dir, "http://nowhere.example.com/") reader = yield backend.get_reader(path) self.addCleanup(reader.finish) self.assertEqual(len(data), reader.size) self.assertEqual(data, reader.read(len(data))) self.assertEqual(b"", reader.read(1))
def test_init(self): temp_dir = self.make_dir() client_service = Mock() backend = TFTPBackend(temp_dir, client_service) self.assertEqual((True, False), (backend.can_read, backend.can_write)) self.assertEqual(temp_dir, backend.base.path) self.assertEqual(client_service, backend.client_service)
def test_get_boot_method_reader_returns_rendered_params_local_device(self): # Fake kernel configuration parameters, as returned from the RPC call. fake_kernel_params = make_kernel_parameters( purpose="local", label="local" ) fake_params = fake_kernel_params._asdict() del fake_params["label"] # Set purpose to `local-device` as this is what will be passed on. fake_params["purpose"] = "local-device" # Stub RPC call to return the fake configuration parameters. client = Mock() client.localIdent = factory.make_name("system_id") client.return_value = succeed(fake_params) client_service = Mock() client_service.getClientNow.return_value = succeed(client) # get_boot_method_reader() takes a dict() of parameters and returns an # `IReader` of a PXE configuration, rendered by # `PXEBootMethod.get_reader`. backend = TFTPBackend(self.make_dir(), client_service) # Stub get_reader to return the render parameters. method = PXEBootMethod() fake_render_result = factory.make_name("render").encode("utf-8") render_patch = self.patch(method, "get_reader") render_patch.return_value = BytesReader(fake_render_result) # Get the rendered configuration, which will actually be a JSON dump # of the render-time parameters. params_with_ip = dict(fake_params) params_with_ip["remote_ip"] = factory.make_ipv4_address() reader = yield backend.get_boot_method_reader(method, params_with_ip) self.addCleanup(reader.finish) self.assertIsInstance(reader, BytesReader) output = reader.read(10000) # The result has been rendered by `method.get_reader`. self.assertEqual(fake_render_result, output) self.assertThat( method.get_reader, MockCalledOnceWith( backend, kernel_params=fake_kernel_params, **params_with_ip ), )
def test_get_reader_converts_other_exceptions_to_tftp_error(self): exception_type = factory.make_exception_type() exception_message = factory.make_string() client = Mock() client.localIdent = factory.make_name("system_id") client.return_value = fail(exception_type(exception_message)) client_service = Mock() client_service.getClientNow.return_value = succeed(client) backend = TFTPBackend(self.make_dir(), client_service) with TwistedLoggerFixture() as logger: with ExpectedException(BackendError, re.escape(exception_message)): yield backend.get_reader(b'pxelinux.cfg/default') # The original exception is logged. self.assertDocTestMatches( """\ TFTP back-end failed. Traceback (most recent call last): ... maastesting.factory.TestException#... """, logger.output)
def test_get_boot_method_reader_grabs_new_client_on_lost_conn(self): # Fake kernel configuration parameters, as returned from the RPC call. fake_kernel_params = make_kernel_parameters() fake_params = fake_kernel_params._asdict() # Stub the output of list_boot_images so the label is set in the # kernel parameters. boot_image = { "osystem": fake_params["osystem"], "release": fake_params["release"], "architecture": fake_params["arch"], "subarchitecture": fake_params["subarch"], "purpose": fake_params["purpose"], "supported_subarches": "", "label": fake_params["label"], } self.patch(tftp_module, "list_boot_images").return_value = [boot_image] del fake_params["label"] # Stub RPC call to return the fake configuration parameters. clients = [] for _ in range(10): client = Mock() client.localIdent = factory.make_name("system_id") client.side_effect = lambda *args, **kwargs: ( succeed(dict(fake_params)) ) clients.append(client) client_service = Mock() client_service.getClientNow.side_effect = [ succeed(client) for client in clients ] client_service.getAllClients.side_effect = [clients[1:], clients[2:]] # get_boot_method_reader() takes a dict() of parameters and returns an # `IReader` of a PXE configuration, rendered by # `PXEBootMethod.get_reader`. backend = TFTPBackend(self.make_dir(), client_service) # Stub get_reader to return the render parameters. method = PXEBootMethod() fake_render_result = factory.make_name("render").encode("utf-8") render_patch = self.patch(method, "get_reader") render_patch.return_value = BytesReader(fake_render_result) # Get the reader once. remote_ip = factory.make_ipv4_address() params_with_ip = dict(fake_params) params_with_ip["remote_ip"] = remote_ip reader = yield backend.get_boot_method_reader(method, params_with_ip) self.addCleanup(reader.finish) # The first client is now saved. self.assertEqual(clients[0], backend.client_to_remote[remote_ip]) # Get the reader twice. params_with_ip = dict(fake_params) params_with_ip["remote_ip"] = remote_ip reader = yield backend.get_boot_method_reader(method, params_with_ip) self.addCleanup(reader.finish) # The second client is now saved. self.assertEqual(clients[1], backend.client_to_remote[remote_ip]) # Only the first and second client should have been called once, and # all the other clients should not have been called. self.assertEqual(1, clients[0].call_count) self.assertEqual(1, clients[1].call_count) for idx in range(2, 10): self.assertThat(clients[idx], MockNotCalled())
def get_reader(self, data): temp_file = self.make_file(name="example", contents=data) temp_dir = os.path.dirname(temp_file) backend = TFTPBackend(temp_dir, Mock()) return backend.get_reader(b"example")