diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | mcp/config/scenario/os-odl-nofeature-ha.yaml.j2 | 2 | ||||
-rw-r--r-- | mcp/config/scenario/os-odl-nofeature-noha.yaml | 2 | ||||
-rwxr-xr-x | mcp/config/states/opendaylight | 12 | ||||
-rwxr-xr-x | mcp/config/states/virtual_control_plane | 2 | ||||
-rw-r--r-- | mcp/patches/0009-controller-Use-keystoneclient-to-check-project-ID.patch | 168 | ||||
-rw-r--r-- | mcp/patches/0015-Set-ovs-bridges-as-L3-interfaces.patch | 16 | ||||
-rw-r--r-- | mcp/patches/patches.list | 1 | ||||
-rw-r--r-- | mcp/reclass/classes/cluster/mcp-pike-odl-ha/infra/maas.yml | 3 | ||||
-rw-r--r-- | mcp/reclass/classes/cluster/mcp-pike-odl-ha/openstack/control.yml | 7 | ||||
-rw-r--r-- | mcp/reclass/classes/cluster/mcp-pike-odl-noha/openstack/compute.yml | 22 | ||||
-rw-r--r-- | mcp/reclass/classes/cluster/mcp-pike-odl-noha/openstack/control.yml | 11 | ||||
-rw-r--r-- | mcp/reclass/classes/cluster/mcp-pike-ovs-dpdk-ha/openstack/compute_pdf.yml.j2 | 8 | ||||
-rw-r--r-- | mcp/reclass/classes/cluster/mcp-pike-ovs-dpdk-ha/openstack/init.yml | 2 | ||||
-rw-r--r-- | mcp/scripts/.gitignore | 1 | ||||
-rw-r--r-- | mcp/scripts/lib.sh | 4 | ||||
-rwxr-xr-x | mcp/scripts/salt.sh | 3 |
17 files changed, 58 insertions, 207 deletions
diff --git a/.gitignore b/.gitignore index 49939c146..fe7a86422 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,6 @@ **/docs_output/ **/releng/ **/mcp/deploy/images/ -**/mcp/scripts/mcp.rsa* **/mcp/scripts/user-data.sh **/mcp/scripts/virsh_net/*.xml **/mcp/scripts/*.img diff --git a/mcp/config/scenario/os-odl-nofeature-ha.yaml.j2 b/mcp/config/scenario/os-odl-nofeature-ha.yaml.j2 index 0bd3a1307..76b893eae 100644 --- a/mcp/config/scenario/os-odl-nofeature-ha.yaml.j2 +++ b/mcp/config/scenario/os-odl-nofeature-ha.yaml.j2 @@ -23,8 +23,8 @@ cluster: {%- if conf.MCP_VCP %} - virtual_control_plane {%- endif %} - - opendaylight - openstack_ha + - opendaylight - networks virtual: nodes: diff --git a/mcp/config/scenario/os-odl-nofeature-noha.yaml b/mcp/config/scenario/os-odl-nofeature-noha.yaml index 35b290428..96d4bcc27 100644 --- a/mcp/config/scenario/os-odl-nofeature-noha.yaml +++ b/mcp/config/scenario/os-odl-nofeature-noha.yaml @@ -9,9 +9,9 @@ cluster: domain: mcp-pike-odl-noha.local states: - - opendaylight - openstack_noha - neutron_gateway + - opendaylight - networks virtual: nodes: diff --git a/mcp/config/states/opendaylight b/mcp/config/states/opendaylight index a698b8cac..515420a7e 100755 --- a/mcp/config/states/opendaylight +++ b/mcp/config/states/opendaylight @@ -1,6 +1,6 @@ #!/bin/bash -e ############################################################################## -# Copyright (c) 2017 Mirantis Inc., Enea AB and others. +# Copyright (c) 2018 Mirantis Inc., Enea AB and others. # All rights reserved. This program and the accompanying materials # are made available under the terms of the Apache License, Version 2.0 # which accompanies this distribution, and is available at @@ -9,6 +9,16 @@ CI_DEBUG=${CI_DEBUG:-0}; [[ "${CI_DEBUG}" =~ (false|0) ]] || set -x +# shellcheck disable=SC1090 +source "$(dirname "${BASH_SOURCE[0]}")/../../scripts/lib.sh" + +# Get OpenDaylight server options with prefix odl_ +function odl() { + salt --out txt -I 'opendaylight:server' pillar.get "opendaylight:server:odl_$1" | cut -d ' ' -f2 +} + # TODO: use service.masked state instead once salt get updated to 2017.7.0+ salt -I 'opendaylight:server' service.mask opendaylight salt -I 'opendaylight:server' state.sls opendaylight + +wait_for 20 "salt --out yaml -C 'I@neutron:server and *01*' network.connect $(odl bind_ip) $(odl rest_port) | fgrep 'result: true'" diff --git a/mcp/config/states/virtual_control_plane b/mcp/config/states/virtual_control_plane index 80c0c87e6..c391cfe6f 100755 --- a/mcp/config/states/virtual_control_plane +++ b/mcp/config/states/virtual_control_plane @@ -48,7 +48,7 @@ cd /srv/salt/env/prd/maas/files && ln -sf \ salt -C 'E@^(?!cfg01|mas01|kvm|cmp00).*' cp.get_file \ "salt://maas/files/$(basename "${APT_CONF_D_CURTIN}")" "${APT_CONF_D_CURTIN}" -wait_for 10 "salt -C 'E@^(?!cfg01|mas01|kvm|cmp00).*' state.apply salt" +wait_for 10.0 "salt -C 'E@^(?!cfg01|mas01|kvm|cmp00).*' state.apply salt" wait_for 10.0 "salt -C 'E@^(?!cfg01|mas01|kvm|cmp00).*' state.apply linux,ntp" wait_for 10.0 "salt -C 'E@^(?!cfg01|mas01|kvm|cmp00).*' ssh.set_auth_key ${SUDO_USER} \ diff --git a/mcp/patches/0009-controller-Use-keystoneclient-to-check-project-ID.patch b/mcp/patches/0009-controller-Use-keystoneclient-to-check-project-ID.patch deleted file mode 100644 index b79eee860..000000000 --- a/mcp/patches/0009-controller-Use-keystoneclient-to-check-project-ID.patch +++ /dev/null @@ -1,168 +0,0 @@ -:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: -: Copyright (c) 2018 Mirantis Inc., Enea AB and others. -: -: All rights reserved. This program and the accompanying materials -: are made available under the terms of the Apache License, Version 2.0 -: which accompanies this distribution, and is available at -: http://www.apache.org/licenses/LICENSE-2.0 -:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: -From: Alexandru Avadanii <Alexandru.Avadanii@enea.com> -Date: Wed, 3 Jan 2018 00:50:50 +0100 -Subject: [PATCH] controller: Use keystoneclient to check project ID - -Port fix from [1] for using the internal network when connecting -to keystone during project ID validation in nova, instead of -going through public endpoint (and using SSL). - -[1] https://bugs.launchpad.net/nova/+bug/1716344 - -Signed-off-by: Alexandru Avadanii <Alexandru.Avadanii@enea.com> ---- - nova/controller.sls | 10 ++ - ...keystoneclient-to-check-project-ID-exists.patch | 116 +++++++++++++++++++++ - 2 files changed, 126 insertions(+) - create mode 100644 nova/files/0001-Use-keystoneclient-to-check-project-ID-exists.patch - -diff --git a/nova/controller.sls b/nova/controller.sls -index a55d037..59af945 100644 ---- a/nova/controller.sls -+++ b/nova/controller.sls -@@ -71,6 +71,16 @@ contrail_nova_packages: - - {%- endif %} - -+nova-api-openstack-identity-patch: -+ file.patch: -+ - name: /usr/lib/python2.7/dist-packages -+ - source: salt://nova/files/0001-Use-keystoneclient-to-check-project-ID-exists.patch -+ - hash: False -+ - options: '-p1' -+ - unless: 'test -f /var/cache/salt/minion/files/base/nova/files/0001-Use-keystoneclient-to-check-project-ID-exists.patch && cd /usr/lib/python2.7/dist-packages && patch -p1 -R --dry-run /var/cache/salt/minion/files/base/nova/files/0001-Use-keystoneclient-to-check-project-ID-exists.patch' -+ - require: -+ - pkg: nova_controller_packages -+ - /etc/nova/nova.conf: - file.managed: - - source: salt://nova/files/{{ controller.version }}/nova-controller.conf.{{ grains.os_family }} -diff --git a/nova/files/0001-Use-keystoneclient-to-check-project-ID-exists.patch b/nova/files/0001-Use-keystoneclient-to-check-project-ID-exists.patch -new file mode 100644 -index 0000000..58d027e ---- /dev/null -+++ b/nova/files/0001-Use-keystoneclient-to-check-project-ID-exists.patch -@@ -0,0 +1,116 @@ -+From: Christoph Fiehe <fiehe@gmx.de> -+Date: Wed, 3 Jan 2018 00:11:20 +0100 -+Subject: [PATCH] Use keystoneclient to check project ID exists -+ -+Based on Christoph's implementation proposed in [1]. -+ -+[1] https://bugs.launchpad.net/nova/+bug/1716344 -+ -+Signed-off-by: Alexandru Avadanii <Alexandru.Avadanii@enea.com> -+--- -+ nova/api/openstack/identity.py | 81 ++++++++++++++++-------------------------- -+ 1 file changed, 30 insertions(+), 51 deletions(-) -+ -+diff --git a/nova/api/openstack/identity.py b/nova/api/openstack/identity.py -+index 833d3b5..3269cec 100644 -+--- a/nova/api/openstack/identity.py -++++ b/nova/api/openstack/identity.py -+@@ -12,16 +12,15 @@ -+ # License for the specific language governing permissions and limitations -+ # under the License. -+ -+-from keystoneauth1 import exceptions as kse -+-from keystoneauth1 import loading as ks_loading -++from keystoneauth1 import session -++from keystoneclient import exceptions as kse -++from keystoneclient.v3 import client -+ from oslo_log import log as logging -+ import webob -+ -+-import nova.conf -+ from nova.i18n import _ -+ -+ -+-CONF = nova.conf.CONF -+ LOG = logging.getLogger(__name__) -+ -+ -+@@ -32,51 +31,31 @@ def verify_project_id(context, project_id): -+ an HTTPBadRequest is emitted. -+ -+ """ -+- sess = ks_loading.load_session_from_conf_options( -+- CONF, 'keystone', auth=context.get_auth_plugin()) -+- -+- failure = webob.exc.HTTPBadRequest( -+- explanation=_("Project ID %s is not a valid project.") % -+- project_id) -++ auth = context.get_auth_plugin() -++ sess = session.Session(auth=auth) -++ keystone = client.Client(session=sess) -+ try: -+- resp = sess.get('/projects/%s' % project_id, -+- endpoint_filter={ -+- 'service_type': 'identity', -+- 'version': (3, 0) -+- }, -+- raise_exc=False) -+- except kse.EndpointNotFound: -+- LOG.error( -+- "Keystone identity service version 3.0 was not found. This might " -+- "be because your endpoint points to the v2.0 versioned endpoint " -+- "which is not supported. Please fix this.") -+- raise failure -+- except kse.ClientException: -+- # something is wrong, like there isn't a keystone v3 endpoint, -+- # we'll take the pass and default to everything being ok. -+- LOG.exception("Unable to contact keystone to verify project_id") -+- return True -+- -+- if resp: -+- # All is good with this 20x status -+- return True -+- elif resp.status_code == 404: -+- # we got access, and we know this project is not there -+- raise failure -+- elif resp.status_code == 403: -+- # we don't have enough permission to verify this, so default -+- # to "it's ok". -+- LOG.info( -+- "Insufficient permissions for user %(user)s to verify " -+- "existence of project_id %(pid)s", -+- {"user": context.user_id, "pid": project_id}) -+- return True -+- else: -+- LOG.warning( -+- "Unexpected response from keystone trying to " -+- "verify project_id %(pid)s - resp: %(code)s %(content)s", -+- {"pid": project_id, -+- "code": resp.status_code, -+- "content": resp.content}) -+- # realize we did something wrong, but move on with a warning -+- return True -++ project = keystone.projects.get(project_id) -++ except kse.ClientException as e: -++ if e.http_status == 404: -++ # we got access, and we know this project is not there -++ raise webob.exc.HTTPBadRequest( -++ explanation=_("Project ID %s is not a valid project.") % -++ project_id) -++ elif e.http_status == 403: -++ # we don't have enough permission to verify this, so default -++ # to "it's ok". -++ LOG.info( -++ "Insufficient permissions for user %(user)s to verify " -++ "existence of project_id %(pid)s", -++ {"user": context.user_id, "pid": project_id}) -++ return True -++ else: -++ LOG.warning( -++ "Unexpected response from keystone trying to " -++ "verify project_id %(pid)s - resp: %(code)s %(content)s", -++ {"pid": project_id, -++ "code": resp.status_code, -++ "content": resp.content}) -++ # realize we did something wrong, but move on with a warning -++ return True diff --git a/mcp/patches/0015-Set-ovs-bridges-as-L3-interfaces.patch b/mcp/patches/0015-Set-ovs-bridges-as-L3-interfaces.patch index e2396de2b..86c1d510d 100644 --- a/mcp/patches/0015-Set-ovs-bridges-as-L3-interfaces.patch +++ b/mcp/patches/0015-Set-ovs-bridges-as-L3-interfaces.patch @@ -14,21 +14,19 @@ Change-Id: I1e83129cc184cf481bea21d7aa452bf60d9e0499 diff --git a/linux/files/ovs_bridge b/linux/files/ovs_bridge new file mode 100644 -index 0000000..575d38f +index 0000000..216581f --- /dev/null +++ b/linux/files/ovs_bridge -@@ -0,0 +1,12 @@ +@@ -0,0 +1,10 @@ +auto {{ bridge_name }} +allow-ovs {{ bridge_name }} +iface {{ bridge_name }} inet static + ovs_type OVSBridge + address {{ bridge.address }} + netmask {{ bridge.netmask }} -+ {%- if bridge.gateway is defined %} -+ gateway {{ bridge.gateway }} -+ {%- endif %} -+ {%- if bridge.ovs_options is defined %} -+ ovs_options {{ bridge.ovs_options }} ++ mtu {{ bridge.get('mtu', '1500') }} ++ {%- if bridge.datapath_type is defined %} ++ ovs_extra set Bridge ${IFACE} datapath_type={{ bridge.datapath_type }} + {%- endif %} diff --git a/linux/files/ovs_port b/linux/files/ovs_port index 222ca8e..efb0307 100644 @@ -45,10 +43,10 @@ index 222ca8e..efb0307 100644 mtu {{ port.get('mtu', '1500') }} ovs_bridge {{ port.bridge }} diff --git a/linux/network/interface.sls b/linux/network/interface.sls -index 3e79847..dc7180a 100644 +index fa37e5e..b5ed1ae 100644 --- a/linux/network/interface.sls +++ b/linux/network/interface.sls -@@ -72,6 +72,34 @@ remove_cloud_init_file: +@@ -91,6 +91,34 @@ add_int_{{ int_name }}_to_ovs_dpdk_bridge_{{ interface_name }}: ovs_bridge_{{ interface_name }}: openvswitch_bridge.present: - name: {{ interface_name }} diff --git a/mcp/patches/patches.list b/mcp/patches/patches.list index 9c460f8e8..711b0f3a5 100644 --- a/mcp/patches/patches.list +++ b/mcp/patches/patches.list @@ -13,7 +13,6 @@ /usr/share/salt-formulas/env: 0006-maas-module-Add-VLAN-DHCP-enable-support.patch /usr/share/salt-formulas/env: 0007-network.interface-Fix-ifup-OVS-port-with-route.patch /usr/share/salt-formulas/env: 0008-Handle-extra-environment-variables.patch -/usr/share/salt-formulas/env: 0009-controller-Use-keystoneclient-to-check-project-ID.patch /usr/share/salt-formulas/env: 0010-maas-region-allow-timeout-override.patch /usr/share/salt-formulas/env: 0011-system.repo-Debian-Add-keyserver-proxy-support.patch /usr/share/salt-formulas/env: 0012-routes-Skip-network-restart-on-noifupdown.patch diff --git a/mcp/reclass/classes/cluster/mcp-pike-odl-ha/infra/maas.yml b/mcp/reclass/classes/cluster/mcp-pike-odl-ha/infra/maas.yml index 87f73a089..6662f1fad 100644 --- a/mcp/reclass/classes/cluster/mcp-pike-odl-ha/infra/maas.yml +++ b/mcp/reclass/classes/cluster/mcp-pike-odl-ha/infra/maas.yml @@ -9,3 +9,6 @@ classes: - cluster.mcp-pike-common-ha.infra.maas - cluster.mcp-pike-odl-ha.infra +parameters: + _param: + hwe_kernel: 'ga-16.04' diff --git a/mcp/reclass/classes/cluster/mcp-pike-odl-ha/openstack/control.yml b/mcp/reclass/classes/cluster/mcp-pike-odl-ha/openstack/control.yml index c50f5b2cc..0820d8bf5 100644 --- a/mcp/reclass/classes/cluster/mcp-pike-odl-ha/openstack/control.yml +++ b/mcp/reclass/classes/cluster/mcp-pike-odl-ha/openstack/control.yml @@ -16,3 +16,10 @@ parameters: server: backend: enable_websocket: false + keystone: + server: + openrc_extra: + # For HA, all public services are available through nginx on prx + sdn_controller_ip: ${_param:cluster_public_host} + sdn_username: admin # Hardcoded to default ODL values for now + sdn_password: admin diff --git a/mcp/reclass/classes/cluster/mcp-pike-odl-noha/openstack/compute.yml b/mcp/reclass/classes/cluster/mcp-pike-odl-noha/openstack/compute.yml index 35940d556..e7e06a61d 100644 --- a/mcp/reclass/classes/cluster/mcp-pike-odl-noha/openstack/compute.yml +++ b/mcp/reclass/classes/cluster/mcp-pike-odl-noha/openstack/compute.yml @@ -21,32 +21,22 @@ parameters: name: ${_param:external_interface} mtu: ${_param:interface_mtu} proto: manual - type: eth + ovs_port_type: OVSPort + type: ovs_port + ovs_bridge: br-floating + bridge: br-floating br-mesh: enabled: true type: bridge proto: static address: ${_param:tenant_address} netmask: 255.255.255.0 - gateway: ${_param:opnfv_openstack_gateway_node01_tenant_address} use_interfaces: - ${_param:tenant_interface} br-floating: enabled: true type: ovs_bridge mtu: ${_param:interface_mtu} - float-to-ex: - enabled: true - type: ovs_port - mtu: ${_param:interface_mtu} - bridge: br-floating - br-ex: - enabled: true - type: bridge - mtu: ${_param:interface_mtu} + proto: static address: ${_param:external_address} - netmask: 255.255.255.0 - use_interfaces: - - ${_param:external_interface} - use_ovs_ports: - - float-to-ex + netmask: ${_param:opnfv_net_public_mask} diff --git a/mcp/reclass/classes/cluster/mcp-pike-odl-noha/openstack/control.yml b/mcp/reclass/classes/cluster/mcp-pike-odl-noha/openstack/control.yml index 1856c3eb9..8798589c3 100644 --- a/mcp/reclass/classes/cluster/mcp-pike-odl-noha/openstack/control.yml +++ b/mcp/reclass/classes/cluster/mcp-pike-odl-noha/openstack/control.yml @@ -36,3 +36,14 @@ parameters: host: ${_param:opendaylight_service_host} port: 8282 params: ${_param:haproxy_check} + neutron: + server: + backend: + enable_websocket: false + keystone: + server: + openrc_extra: + # For noHA, all public services are available through haproxy on ctl + sdn_controller_ip: ${_param:cluster_vip_address} + sdn_username: admin # Hardcoded to default ODL values for now + sdn_password: admin diff --git a/mcp/reclass/classes/cluster/mcp-pike-ovs-dpdk-ha/openstack/compute_pdf.yml.j2 b/mcp/reclass/classes/cluster/mcp-pike-ovs-dpdk-ha/openstack/compute_pdf.yml.j2 index de779bb78..cf9a0b302 100644 --- a/mcp/reclass/classes/cluster/mcp-pike-ovs-dpdk-ha/openstack/compute_pdf.yml.j2 +++ b/mcp/reclass/classes/cluster/mcp-pike-ovs-dpdk-ha/openstack/compute_pdf.yml.j2 @@ -10,6 +10,7 @@ parameters: linux: network: + ovs_nowait: true interface: dpdk0: name: ${_param:dpdk0_name} @@ -22,8 +23,5 @@ parameters: br-prv: enabled: true type: dpdk_ovs_bridge - address: ${_param:tenant_address} - netmask: 255.255.255.0 - {%- if nm.vlan_private | int > 0 %} - tag: {{ nm.vlan_private }} - {%- endif %} + br-floating: + datapath_type: netdev diff --git a/mcp/reclass/classes/cluster/mcp-pike-ovs-dpdk-ha/openstack/init.yml b/mcp/reclass/classes/cluster/mcp-pike-ovs-dpdk-ha/openstack/init.yml index 1800a58af..55fc6ab5c 100644 --- a/mcp/reclass/classes/cluster/mcp-pike-ovs-dpdk-ha/openstack/init.yml +++ b/mcp/reclass/classes/cluster/mcp-pike-ovs-dpdk-ha/openstack/init.yml @@ -10,5 +10,5 @@ classes: - cluster.mcp-pike-common-ha.openstack_init parameters: _param: - neutron_tenant_network_types: "flat,vxlan" + neutron_tenant_network_types: "flat,vlan" neutron_tenant_vlan_range: "1000:1030" diff --git a/mcp/scripts/.gitignore b/mcp/scripts/.gitignore index 89af7c473..a7f658e4e 100644 --- a/mcp/scripts/.gitignore +++ b/mcp/scripts/.gitignore @@ -1,2 +1,3 @@ +mcp.rsa* user-data.*.sh xdf_data.sh diff --git a/mcp/scripts/lib.sh b/mcp/scripts/lib.sh index 7d57fcf5f..ce5db251f 100644 --- a/mcp/scripts/lib.sh +++ b/mcp/scripts/lib.sh @@ -38,10 +38,11 @@ function get_base_image { function __kernel_modules { # Load mandatory kernel modules: loop, nbd local image_dir=$1 - sudo modprobe loop + test -e /dev/loop-control || sudo modprobe loop if sudo modprobe nbd max_part=8 || sudo modprobe -f nbd max_part=8; then return 0 fi + if [ -e /dev/nbd0 ]; then return 0; fi # nbd might be inbuilt # CentOS (or RHEL family in general) do not provide 'nbd' out of the box echo "[WARN] 'nbd' kernel module cannot be loaded!" if [ ! -e /etc/redhat-release ]; then @@ -225,6 +226,7 @@ function cleanup_mounts { function cleanup_uefi { # Clean up Ubuntu boot entry if cfg01, kvm nodes online from previous deploy local cmd_str="ssh ${SSH_OPTS} ${SSH_SALT}" + ping -c 1 -w 1 "${SALT_MASTER}" || return 0 [ ! "$(hostname)" = 'cfg01' ] || cmd_str='eval' ${cmd_str} "sudo salt -C 'kvm* or cmp*' cmd.run \ \"which efibootmgr > /dev/null 2>&1 && \ diff --git a/mcp/scripts/salt.sh b/mcp/scripts/salt.sh index ad4707369..aecfecea4 100755 --- a/mcp/scripts/salt.sh +++ b/mcp/scripts/salt.sh @@ -63,6 +63,7 @@ ssh ${SSH_OPTS} "${SSH_SALT}" bash -s -e << SALT_INSTALL_END ln -sf ${OPNFV_GIT_DIR}${F_GIT_SUBD} ${OPNFV_FUEL_DIR} ln -sf ${OPNFV_FUEL_DIR}/mcp/reclass /srv/salt ln -sf ${OPNFV_FUEL_DIR}/mcp/deploy/scripts /srv/salt + ln -sf ${OPNFV_FUEL_DIR}/mcp/scripts/mcp.rsa $(dirname "${OPNFV_FUEL_DIR}") cd /srv/salt/${OPNFV_RDIR} && rm -f arch && ln -sf "\$(uname -i)" arch cp -r ${OPNFV_FUEL_DIR}/mcp/metadata/service /usr/share/salt-formulas/reclass @@ -70,7 +71,7 @@ ssh ${SSH_OPTS} "${SSH_SALT}" bash -s -e << SALT_INSTALL_END ln -sf /usr/share/salt-formulas/reclass/service/opendaylight # Armband APT-MK nightly/extra repo for forked & extended reclass - apt-key adv --keyserver keys.gnupg.net --recv 798AB1D1 + wget -qO - https://linux.enea.com/apt-mk/public.gpg | apt-key add - echo 'deb http://linux.enea.com/apt-mk/xenial nightly extra' > \ '/etc/apt/sources.list.d/armband_mcp_extra.list' apt-get update |