AETHER-2788 Use parameter from configuration to configure eNB
AETHER-2725 CWMP fault returns by eNodeB
AETHER-2692 Check the XML sent by eNodeB and ACS are valid
AETHER-2691 Research on eNodeB TR-069 service issue
AETHER-2788 Use parameter from configuration to configure eNB
AETHER-2789 Load enodeb configuration by the serial number
AETHER-2821 Configure the PLMN which is current not supported by enodebd
AETHER-2839 Create acs_common to own the common attribute of eNodeb configuration
AETHER-2831 Writing documentation of configuring enodebd
This patch contains above jira tickets.
It can work and configure the eNodeB with single configuration now.
Change-Id: I4875d099246a1995de420c4947e7a99823055161
diff --git a/README.md b/README.md
index 61e4a0f..60b0c16 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,20 @@
-# Magma enodebd
+# ENODEBD
-## Generate protos
+eNodeB daemon is an Automatic Configuration Server (ACS) which forks from [Facebook Magma project](https://github.com/magma/magma). It currently tested with Sercomm eNodeB P27-SCE4255W small cell.
-```bash
-python tools/gen_protos.py proto_files/orc8r/protos proto_files,proto_files/orc8r/protos/prometheus proto_files .
-python tools/gen_protos.py proto_files/lte/protos proto_files,proto_files/orc8r/protos/prometheus proto_files .
-python tools/gen_prometheus_proto.py . .
-```
+# Configuration
+
+We have these configuration files for configuring eNodeBD service.
+
+1. override_configs/gateway.mconfig
+
+The enodebd will generate the empty configuration to config service, and it will load
+`override_configs/gateway.mconfig` to fill in the empty configuration.
+
+2. magma_configs/acs_common.yml
+
+The `acs_common.yml` will hold the default value for all eNodeBs, like as the PLMN may be a shared value among all eNodeBs.
+
+3. magma_configs/serial_numbers/_2009CW5000019_.yml
+
+The `serial_number.yml` will have the customized value for each configurable parameters. The value in `serial_number.yml` will override the value defines in `gateway.mconfig` and `acs_common.yml` when corresponding eNodeB connects (base on the serial number provided by eNodeB).
\ No newline at end of file
diff --git a/common/service.py b/common/service.py
index 2bcce61..5a3ca98 100644
--- a/common/service.py
+++ b/common/service.py
@@ -326,6 +326,7 @@
'or mconfig, defaulting to INFO',
)
log_level = LogLevel.Value('INFO')
+
self._set_log_level(log_level)
@staticmethod
diff --git a/configuration/mconfig_managers.py b/configuration/mconfig_managers.py
index bd39c4c..3b76418 100644
--- a/configuration/mconfig_managers.py
+++ b/configuration/mconfig_managers.py
@@ -157,7 +157,7 @@
mconfig_str = cfg_file.read()
return self.deserialize_mconfig(mconfig_str)
except (OSError, json.JSONDecodeError, json_format.ParseError) as e:
- raise LoadConfigError('Error loading mconfig') from e
+ raise LoadConfigError('Error loading mconfig, mconfig may have format issue') from e
def load_service_mconfig(
self, service_name: str,
diff --git a/configuration/service_configs.py b/configuration/service_configs.py
index c60c340..f2d0f24 100644
--- a/configuration/service_configs.py
+++ b/configuration/service_configs.py
@@ -22,7 +22,8 @@
# Location of configs (both service config and mconfig)
CONFIG_DIR = './magma_configs'
CONFIG_OVERRIDE_DIR = './override_configs'
-
+ENB_COMMON_FILE = './magma_configs/acs_common.yml'
+ENB_CONFIG_DIR = './magma_configs/serial_number'
def load_override_config(service_name: str) -> Optional[Any]:
"""
@@ -73,7 +74,6 @@
LoadConfigError:
Unable to load config due to missing file or missing key
"""
- print(CONFIG_DIR, service_name)
cfg_file_name = os.path.join(CONFIG_DIR, '%s.yml' % service_name)
cfg = _load_yaml_file(cfg_file_name)
@@ -83,6 +83,36 @@
cfg.update(overrides)
return cfg
+def load_enb_config() -> Any:
+ """
+ Load enb configurations from directory.
+
+ Args:
+ None
+
+ Returns: json-decoded value of the service config
+ """
+
+ ret = dict()
+ for fname in os.listdir(ENB_CONFIG_DIR):
+ sn = fname.replace(".yml", "")
+ cfg_file_name = os.path.join(ENB_CONFIG_DIR, fname)
+ ret[sn] = _load_yaml_file(cfg_file_name)
+
+ return ret
+
+def load_common_config() -> Any:
+ """
+ Load enb common configuration.
+
+ Args:
+ None
+
+ Returns: json-decoded value of the service config
+ """
+
+ return _load_yaml_file(ENB_COMMON_FILE)
+
cached_service_configs = {} # type: Dict[str, Any]
diff --git a/device_config/configuration_init.py b/device_config/configuration_init.py
index cf9505b..4b156df 100644
--- a/device_config/configuration_init.py
+++ b/device_config/configuration_init.py
@@ -18,6 +18,7 @@
from lte.protos.mconfig import mconfigs_pb2
from common.misc_utils import get_ip_from_if
from configuration.exceptions import LoadConfigError
+from configuration.service_configs import load_enb_config, load_common_config
from configuration.mconfig_managers import load_service_mconfig_as_json
from data_models.data_model import DataModel
from data_models.data_model_parameters import ParameterName
@@ -78,6 +79,9 @@
Returns:
Desired data model configuration for the device
"""
+
+ print("DEVICE CFG: ", device_config)
+
cfg_desired = EnodebConfiguration(data_model)
# Determine configuration parameters
@@ -150,6 +154,7 @@
config = json.loads(
load_service_mconfig_as_json('yang').get('value', '{}'),
)
+
enb.extend(
filter(
lambda entry: entry['serial'] == enb_serial,
@@ -185,68 +190,43 @@
mconfig: mconfigs_pb2.EnodebD,
device_config: EnodebConfiguration,
) -> SingleEnodebConfig:
- # For fields that are specified per eNB
- if mconfig.enb_configs_by_serial is not None and \
- len(mconfig.enb_configs_by_serial) > 0:
- enb_serial = \
- device_config.get_parameter(ParameterName.SERIAL_NUMBER)
- if enb_serial in mconfig.enb_configs_by_serial:
- enb_config = mconfig.enb_configs_by_serial[enb_serial]
- earfcndl = enb_config.earfcndl
- pci = enb_config.pci
- allow_enodeb_transmit = enb_config.transmit_enabled
- tac = enb_config.tac
- bandwidth_mhz = enb_config.bandwidth_mhz
- cell_id = enb_config.cell_id
- duplex_mode = map_earfcndl_to_duplex_mode(earfcndl)
- subframe_assignment = None
- special_subframe_pattern = None
- if duplex_mode == DuplexMode.TDD:
- subframe_assignment = enb_config.subframe_assignment
- special_subframe_pattern = \
- enb_config.special_subframe_pattern
- else:
- raise ConfigurationError(
- 'Could not construct desired config '
- 'for eNB',
- )
- else:
- pci = mconfig.pci
- allow_enodeb_transmit = mconfig.allow_enodeb_transmit
- tac = mconfig.tac
- bandwidth_mhz = mconfig.bandwidth_mhz
- cell_id = DEFAULT_CELL_IDENTITY
- if mconfig.tdd_config is not None and str(mconfig.tdd_config) != '':
- earfcndl = mconfig.tdd_config.earfcndl
- subframe_assignment = mconfig.tdd_config.subframe_assignment
- special_subframe_pattern = \
- mconfig.tdd_config.special_subframe_pattern
- elif mconfig.fdd_config is not None and str(mconfig.fdd_config) != '':
- earfcndl = mconfig.fdd_config.earfcndl
- subframe_assignment = None
- special_subframe_pattern = None
- else:
- earfcndl = mconfig.earfcndl
- subframe_assignment = mconfig.subframe_assignment
- special_subframe_pattern = mconfig.special_subframe_pattern
- # And now the rest of the fields
- plmnid_list = mconfig.plmnid_list
+ # The eNodeB parameters to be generated with default value,
+ # It will load from eNB configs based on serial number or default value
+ # The params is a nested list which contains 2 format of parameter names.
+ # The first parameter is the name of eNB / ACS configuration in
+ # magma_configs/serial_number/ and magma_configs/acs_common.yml
+ # The second parameter is the name of gateway configuration in
+ # override_configs/gateway.mconfig
+ params = [
+ ["earfcndl", "earfcndl"],
+ ["subframeAssignment", "subframe_assignment"],
+ ["special_subframe_pattern", "special_subframe_pattern"],
+ ["pci", "pci"],
+ ["plmnidList", "plmnid_list"],
+ ["tac", "tac"],
+ ["bandwidthMhz", "bandwidth_mhz"],
+ ["allowEnodebTransmit", "allow_enodeb_transmit"]
+ ]
+
+ extend_params = ["cell_id", "mme_address", "mme_port"]
- single_enodeb_config = SingleEnodebConfig(
- earfcndl=earfcndl,
- subframe_assignment=subframe_assignment,
- special_subframe_pattern=special_subframe_pattern,
- pci=pci,
- plmnid_list=plmnid_list,
- tac=tac,
- bandwidth_mhz=bandwidth_mhz,
- cell_id=cell_id,
- allow_enodeb_transmit=allow_enodeb_transmit,
- mme_address=None,
- mme_port=None,
- )
- return single_enodeb_config
+ params_dict = dict()
+
+ common_config = load_common_config()
+ enb_configs = load_enb_config()
+ enb_serial = device_config.get_parameter(ParameterName.SERIAL_NUMBER)
+ enb_config = enb_configs.get(enb_serial, dict())
+
+ for param in params:
+ params_dict[param[1]] = enb_config.get(param[0],
+ common_config.get(param[0], mconfig.__getattribute__(param[1]))
+ )
+
+ for param in extend_params:
+ params_dict[param] = enb_config.get(param, common_config.get(param, None))
+
+ return SingleEnodebConfig(**params_dict)
def _set_pci(
diff --git a/device_config/enodeb_configuration.py b/device_config/enodeb_configuration.py
index 5d28d50..6e0b951 100644
--- a/device_config/enodeb_configuration.py
+++ b/device_config/enodeb_configuration.py
@@ -158,4 +158,4 @@
tr_param = trparam_model.get_parameter(param_name)
if tr_param is None:
logger.warning('Parameter <%s> not defined in model', param_name)
- raise ConfigurationError("Parameter not defined in model.")
+ raise ConfigurationError("Parameter %s not defined in model." % param_name)
diff --git a/devices/freedomfi_one.py b/devices/freedomfi_one.py
index e9bf9b0..bfa66eb 100644
--- a/devices/freedomfi_one.py
+++ b/devices/freedomfi_one.py
@@ -24,6 +24,7 @@
ParameterName,
TrParameterType,
)
+from configuration.service_configs import load_enb_config
from device_config.configuration_init import build_desired_config
from device_config.enodeb_config_postprocessor import EnodebConfigurationPostProcessor
from device_config.enodeb_configuration import EnodebConfiguration
@@ -176,7 +177,9 @@
and converting it to Magma understood fields.
"""
- STATUS_PATH = "Device.X_000E8F_DeviceFeature.X_000E8F_NEStatus."
+ DEFGW_STATUS_PATH = "Device.X_SCM_DeviceFeature.X_SCM_NEStatus.X_SCM_DEFGW_Status"
+ SAS_STATUS_PATH = "Device.Services.FAPService.1.FAPControl.LTE.X_SCM_SAS.State"
+ ENB_STATUS_PATH = "Device.X_SCM_DeviceFeature.X_SCM_NEStatus.X_SCM_eNB_Status"
# Status parameters
DEFAULT_GW = "defaultGW"
@@ -187,30 +190,29 @@
STATUS_PARAMETERS = {
# Status nodes
+ # This works
DEFAULT_GW: TrParam(
- STATUS_PATH + "X_000E8F_DEFGW_Status",
+ DEFGW_STATUS_PATH,
is_invasive=False,
type=TrParameterType.STRING,
is_optional=False,
),
- SYNC_STATUS: TrParam(
- STATUS_PATH + "X_000E8F_Sync_Status",
- is_invasive=False,
- type=TrParameterType.STRING,
- is_optional=False,
- ),
+ # SYNC_STATUS: TrParam(
+ # STATUS_PATH + 'X_000E8F_Sync_Status', is_invasive=False,
+ # type=TrParameterType.STRING, is_optional=False,
+ # ),
+ # This works
SAS_STATUS: TrParam(
- STATUS_PATH + "X_000E8F_SAS_Status",
+ SAS_STATUS_PATH,
is_invasive=False,
type=TrParameterType.STRING,
is_optional=False,
),
- ENB_STATUS: TrParam(
- STATUS_PATH + "X_000E8F_eNB_Status",
- is_invasive=False,
- type=TrParameterType.STRING,
- is_optional=False,
- ),
+ # This doesn't work
+ # ENB_STATUS: TrParam(
+ # ENB_STATUS_PATH, is_invasive=False,
+ # type=TrParameterType.STRING, is_optional=False,
+ # ),
# GPS status, lat, long
GPS_SCAN_STATUS: TrParam(
"Device.FAP.GPS.ScanStatus",
@@ -397,12 +399,11 @@
WEB_UI_ENABLE = "web_ui_enable" # Enable or disable local enb UI
MISC_PARAMETERS = {
- WEB_UI_ENABLE: TrParam(
- "Device.X_000E8F_DeviceFeature.X_000E8F_WebServerEnable",
- is_invasive=False,
- type=TrParameterType.BOOLEAN,
- is_optional=False,
- ),
+ # WEB_UI_ENABLE: TrParam(
+ # 'Device.X_000E8F_DeviceFeature.X_000E8F_WebServerEnable',
+ # is_invasive=False,
+ # type=TrParameterType.BOOLEAN, is_optional=False,
+ # ),
CARRIER_AGG_ENABLE: TrParam(
FAP_CONTROL + "LTE.X_000E8F_RRMConfig.X_000E8F_CA_Enable",
is_invasive=False,
@@ -440,12 +441,11 @@
# Use IPV4 only
TUNNEL_REF: "Device.IP.Interface.1.IPv4Address.1.",
# Only synchronize with GPS
- PRIM_SOURCE: "GNSS",
+ PRIM_SOURCE: "FREE_RUNNING",
# Always enable carrier aggregation for the CBRS bands
- CARRIER_AGG_ENABLE: True,
- CARRIER_NUMBER: 2, # CBRS has two carriers
+ CARRIER_AGG_ENABLE: False,
+ CARRIER_NUMBER: 1, # CBRS has two carriers
CONTIGUOUS_CC: 0, # Its not contiguous carrier
- WEB_UI_ENABLE: False, # Disable WebUI by default
}
@@ -657,12 +657,12 @@
type=TrParameterType.INT,
is_optional=False,
),
- ParameterName.NUM_PLMNS: TrParam(
- FAPSERVICE_PATH + "CellConfig.LTE.EPC.PLMNListNumberOfEntries",
- is_invasive=False,
- type=TrParameterType.INT,
- is_optional=False,
- ),
+ # It may not work, comment out first
+ # ParameterName.NUM_PLMNS: TrParam(
+ # FAPSERVICE_PATH + 'CellConfig.LTE.EPC.PLMNListNumberOfEntries',
+ # is_invasive=False,
+ # type=TrParameterType.INT, is_optional=False,
+ # ),
ParameterName.TAC: TrParam(
FAPSERVICE_PATH + "CellConfig.LTE.EPC.TAC",
is_invasive=False,
@@ -821,10 +821,7 @@
def postprocess(
self, mconfig: Any, service_cfg: Any, desired_cfg: EnodebConfiguration,
) -> None:
- # TODO: Get this config from the domain proxy
- # TODO @amarpad, set these when DProxy integration is done.
- # For now the radio will directly talk to the SAS and get these
- # attributes.
+
desired_cfg.delete_parameter(ParameterName.EARFCNDL)
desired_cfg.delete_parameter(ParameterName.DL_BANDWIDTH)
desired_cfg.delete_parameter(ParameterName.UL_BANDWIDTH)
@@ -847,16 +844,19 @@
# This should not happen
EnodebdLogger.error("Serial number unknown for device")
- if self.SAS_KEY not in service_cfg:
- return
+ # Load eNB customized configuration from "./magma_config/serial_number/"
+ # and configure each connected eNB based on serial number
+ enbcfg = load_enb_config()
+ sn = self.acs.get_parameter(ParameterName.SERIAL_NUMBER)
- sas_cfg = service_cfg[self.SAS_KEY]
- sas_param_names = self.acs.data_model.get_sas_param_names()
- for name, val in sas_cfg.items():
- if name not in sas_param_names:
- EnodebdLogger.warning("Ignoring attribute %s", name)
- continue
- desired_cfg.set_parameter(name, val)
+ for name, val in enbcfg.get(sn, {}).items():
+ # The SAS configuration for eNodeB
+ if name in ["sas", "cell"]:
+ for subname, subval in val.items():
+ print("Config %s updated to: %s" % (subname, subval))
+ desired_cfg.set_parameter(subname, subval)
+
+ print(desired_cfg)
class FreedomFiOneSendGetTransientParametersState(EnodebAcsState):
@@ -872,12 +872,20 @@
self.done_transition = when_done
def get_msg(self, message: Any) -> AcsMsgAndTransition:
+
request = models.GetParameterValues()
request.ParameterNames = models.ParameterNames()
request.ParameterNames.string = []
+
+ # request = models.GetParameterNames()
+ # request.ParameterPath = "Device."
+ # request.NextLevel = False
+
+ # Get the status parameters which was defined in Line 171
for _, tr_param in StatusParameters.STATUS_PARAMETERS.items():
path = tr_param.path
request.ParameterNames.string.append(path)
+
request.ParameterNames.arrayType = "xsd:string[%d]" % len(
request.ParameterNames.string
)
@@ -1031,6 +1039,7 @@
# Parse simple params
param_name_list = self.acs.data_model.get_parameter_names()
+
for name in param_name_list:
path = self.acs.data_model.get_parameter(name).path
if path in path_to_val:
@@ -1056,6 +1065,7 @@
self.acs.device_cfg.set_parameter_for_object(
name, magma_value, obj_name,
)
+
# Now we have enough information to build the desired configuration
if self.acs.desired_cfg is None:
self.acs.desired_cfg = build_desired_config(
diff --git a/magma_configs/acs_common.yml b/magma_configs/acs_common.yml
new file mode 100644
index 0000000..28f630b
--- /dev/null
+++ b/magma_configs/acs_common.yml
@@ -0,0 +1,11 @@
+bandwidthMhz: 100
+specialSubframePattern: 7
+earfcndl: 44490
+plmnidList: "00101"
+pci: 1
+allowEnodebTransmit: False
+subframeAssignment: 2
+tac: 1
+cell_id: 1
+mme_address: ""
+mme_port: 36412
diff --git a/magma_configs/enodebd.yml b/magma_configs/enodebd.yml
index 84e636f..ff5fe6e 100644
--- a/magma_configs/enodebd.yml
+++ b/magma_configs/enodebd.yml
@@ -14,26 +14,26 @@
# log_level is set in mconfig. It can be overridden here
tr069:
- interface: en0 # NOTE: this value must be consistent with dnsmasq.conf
+ interface: eth0 # NOTE: this value must be consistent with dnsmasq.conf
port: 48080
perf_mgmt_port: 8081
# NOTE: this is the IP which enodeb will communicate with enodebd
# if this is ever changed in dnsd.yml, this needs to be updated too
- public_ip: 192.88.99.142
+ public_ip: 18.116.99.179
# TODO: @amar: This is a temp workaround to allow for testing until we
# connect enodebd with the domain proxy which is responsible for talking to
# SAS.
sas:
sas_enabled: True
- sas_server_url: "https://spectrum-connect.federatedwireless.com/v1.2/"
- sas_uid: "INVALID_ID"
+ sas_server_url: "https://sas.goog/v1.2/"
+ sas_uid: "aether"
sas_category: "A"
sas_channel_type: "GAA"
- sas_cert_subject: "INVALID_CERT_SUBJECT"
+ sas_cert_subject: "/C=TW/O=Sercomm/OU=WInnForum CBSD Certificate/CN=P27-SCE4255W:2009CW5000019"
sas_icg_group_id: ""
sas_location: "indoor"
- sas_height_type: "AMSL"
+ sas_height_type: "AGL"
# Reboot eNodeB if eNodeB should be connected to MME but isn't
# This is a workaround for a bug with BaiCells eNodeB where the S1 connection
diff --git a/magma_configs/serial_number/2009CW5000019.yml b/magma_configs/serial_number/2009CW5000019.yml
new file mode 100644
index 0000000..0b0e52f
--- /dev/null
+++ b/magma_configs/serial_number/2009CW5000019.yml
@@ -0,0 +1,29 @@
+bandwidthMhz: 100
+specialSubframePattern: 7
+earfcndl: 44490
+plmnidList: "305010"
+pci: 501
+allowEnodebTransmit: False
+subframeAssignment: 2
+tac: 2
+cell_id: 1
+mme_address: 172.21.143.206
+mme_port: 36412
+
+cell:
+ DL bandwidth: 100
+ UL bandwidth: 100
+ Admin state: 0
+ Periodic inform interval: 180
+ Perf mgmt enable: 0
+ Perf mgmt upload interval: 900
+
+sas:
+ sas_enabled: True
+ sas_server_url: "https://sas.goog/v1.2/"
+ sas_uid: "aether"
+ sas_category: "A"
+ sas_channel_type: "GAA"
+ sas_cert_subject: "/C=TW/O=Sercomm/OU=WInnForum CBSD Certificate/CN=P27-SCE4255W:2009CW5000019"
+ sas_location: "indoor"
+ sas_height_type: "AGL"
\ No newline at end of file
diff --git a/override_configs/gateway.mconfig b/override_configs/gateway.mconfig
index 4fbc7dd..c330502 100644
--- a/override_configs/gateway.mconfig
+++ b/override_configs/gateway.mconfig
@@ -6,12 +6,11 @@
"specialSubframePattern": 7,
"earfcndl": 44490,
"logLevel": "INFO",
- "plmnidList": "00101",
+ "plmnidList": "305011",
"pci": 260,
"allowEnodebTransmit": false,
"subframeAssignment": 2,
- "tac": 1,
- "enb_configs_by_serial": {"2009CW5000019": {}}
+ "tac": 1
}
}
-}
\ No newline at end of file
+}
diff --git a/state_machines/enb_acs_impl.py b/state_machines/enb_acs_impl.py
index c29690e..2a7367f 100644
--- a/state_machines/enb_acs_impl.py
+++ b/state_machines/enb_acs_impl.py
@@ -65,6 +65,7 @@
self.mme_timeout_handler = None
self.mme_timer = None
self._start_state_machine(service)
+
def get_state(self) -> str:
if self.state is None:
diff --git a/tr069/rpc_methods.py b/tr069/rpc_methods.py
index aff0f30..c6203d9 100644
--- a/tr069/rpc_methods.py
+++ b/tr069/rpc_methods.py
@@ -148,6 +148,7 @@
# Allow un-set parameters. If CPE can't handle this, it will
# respond with an error message
pass
+
return request_out
# CPE->ACS RPC calls
@@ -327,6 +328,7 @@
# would pick up all tags that start with the tag of interest (e.g
# cwmp:SetParameterAttributes would also match
# cwmp:SetParameterAttributesStruct)
+
XML_FORMAT_STRS = [
["cwmp:%s>", "!!!TEMP_MOD!!!:%s>"],
["cwmp:%s/>", "!!!TEMP_MOD!!!:%s/>"],
@@ -364,7 +366,6 @@
if(ctx.descriptor.out_message.Attributes.sub_name == 'EmptyHttp'):
ctx.out_string = [b'']
-
AutoConfigServer.event_manager.add_listener(
'method_return_string',
on_method_return_string,
diff --git a/tr069/spyne_mods.py b/tr069/spyne_mods.py
index 0f0e918..ad0b54b 100644
--- a/tr069/spyne_mods.py
+++ b/tr069/spyne_mods.py
@@ -91,11 +91,11 @@
ctx.in_string = [in_string.encode(charset, 'ignore')]
if ctx.in_string == [b'']:
ctx.in_string = [
- b'<soap11env:Envelope xmlns:cwmp="urn:dslforum-org:cwmp-1-0" xmlns:soap11env="http://schemas.xmlsoap.org/soap/envelope/">/n'
- b' <soap11env:Body>/n'
+ b'<soapenv:Envelope xmlns:cwmp="urn:dslforum-org:cwmp-1-0" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">/n'
+ b' <soapenv:Body>/n'
b' <cwmp:EmptyHttp/>/n'
- b' </soap11env:Body>/n'
- b'</soap11env:Envelope>',
+ b' </soapenv:Body>/n'
+ b'</soapenv:Envelope>',
]
super(Tr069Soap11, self).create_in_document(ctx, charset)