Skip to content

Commit

Permalink
Merge branch 'canonical:master' into skip_registration_plus_minor_fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
silverdrake11 authored Jul 25, 2024
2 parents cda9564 + d221e62 commit 00b0bbc
Show file tree
Hide file tree
Showing 22 changed files with 170 additions and 76 deletions.
6 changes: 1 addition & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,7 @@ jobs:
- uses: actions/checkout@v4
with:
submodules: true
- run: |
make depends
# -common seems a catch-22, but this is just a shortcut to
# initialize user and dirs, some used through tests.
sudo apt-get -y install landscape-common
- run: make depends-ci
- run: make check TRIAL=/usr/bin/trial3
lint:
runs-on: ubuntu-latest
Expand Down
6 changes: 1 addition & 5 deletions .github/workflows/codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,7 @@ jobs:
- uses: actions/checkout@v4
with:
submodules: true
- run: |
make depends
# -common seems a catch-22, but this is just a shortcut to
# initialize user and dirs, some used through tests.
sudo apt-get -y install landscape-common
- run: make depends-ci
- run: make coverage TRIAL=/usr/bin/trial3
- name: upload
uses: codecov/codecov-action@v3
25 changes: 25 additions & 0 deletions .github/workflows/static-analysis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: TiCS static analysis

on:
workflow_dispatch:
schedule:
- cron: '0 6 * * *' # Run at 6:00a (arbitrary) to avoid peak activity on runners
jobs:
TICS:
runs-on: ubuntu-latest
steps:
- name: Checkout master branch
uses: actions/checkout@v4
with:
submodules: true
- name: Make coverage report for TiCS
run: make prepare-tics-analysis
- name: TICS GitHub Action
uses: tiobe/tics-github-action@v3
with:
mode: qserver
project: landscape-client
viewerUrl: https://canonical.tiobe.com/tiobeweb/TICS/api/cfg?name=default
ticsAuthToken: ${{ secrets.TICSAUTHTOKEN }}
installTics: true
filelist: . # whole project for nightly/on-demand runs
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
_trial_temp
.coverage
coverage/
docs/api
build
build-stamp
Expand Down Expand Up @@ -33,3 +34,5 @@ landscape-client_ppc64el*.txt
landscape-client_s390x*.txt
landscape_client.egg-info
.pybuild
venv
.venv
21 changes: 20 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,21 @@ help: ## Print help about available targets

.PHONY: depends
depends:
sudo apt-get -y install python3-configobj python3-coverage python3-distutils-extra\
sudo apt update && sudo apt-get -y install python3-configobj python3-coverage python3-distutils-extra\
python3-flake8 python3-mock python3-netifaces python3-pip python3-pycurl python3-twisted\
net-tools

.PHONY: depends-dev
depends-dev: depends
pip install pre-commit
$(PRE_COMMIT) install

# -common seems a catch-22, but this is just a shortcut to
# initialize user and dirs, some used through tests.
.PHONY: depends-ci
depends-ci: depends
sudo apt-get -y install landscape-common

all: build

.PHONY: build
Expand Down Expand Up @@ -53,10 +62,12 @@ pyflakes:
pre-commit:
-pre-commit run -a

.PHONY: clean
clean:
-find landscape -name __pycache__ -exec rm -rf {} \;
-find landscape -name \*.pyc -exec rm -f {} \;
-rm -rf .coverage
-rm -rf coverage
-rm -rf tags
-rm -rf _trial_temp
-rm -rf docs/api
Expand Down Expand Up @@ -136,6 +147,14 @@ snap:
$(SNAPCRAFT)
.PHONY: snap

# TICS expects coverage info to be in ./coverage/.coverage
.PHONY: prepare-tics-analysis
prepare-tics-analysis: depends-ci coverage
sudo apt install pylint
mkdir -p coverage
cp .coverage ./coverage/.coverage
cp coverage.xml ./coverage/coverage.xml

include Makefile.packaging

.DEFAULT_GOAL := help
6 changes: 6 additions & 0 deletions README
Original file line number Diff line number Diff line change
Expand Up @@ -205,3 +205,9 @@ uploading the snap or otherwise manipulating things in your store
account. You will need to repeat the dbus-run-session and
gnome-keyring-daemon steps BEFORE logging in if you do need to be
logged into the store.

### Automatic Builds and Releases

The latest version of landscape-client snap will be built from the most recent commit and
published to the `latest/edge` channel via [a recipe on launchpad](https://launchpad.net/~landscape/landscape-client/+snap/core-dev).
Other channels are promoted and released manually.
2 changes: 0 additions & 2 deletions debian/landscape-sysinfo.wrapper
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,3 @@ else

printf "%s\n" "$SYSINFO"
fi

exit 0
7 changes: 5 additions & 2 deletions landscape/client/broker/tests/test_transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,9 +196,12 @@ def test_ssl_verification_negative(self):
def got_result(ignored):
self.assertIs(r.request, None)
self.assertIs(r.content, None)
logfile_value = self.logfile.getvalue()
# pycurl error messages vary by version.
# First is for <= noble, second for > noble.
self.assertTrue(
"server certificate verification failed"
in self.logfile.getvalue(),
"server certificate verification failed" in logfile_value
or "SSL certificate problem" in logfile_value,
)

result.addErrback(got_result)
Expand Down
2 changes: 1 addition & 1 deletion landscape/client/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,7 @@ def query_landscape_edition(self):
show_help(
"Provide the fully qualified domain name "
"of your Landscape Server e.g. "
"landscape.yourdomain.com",
"landscape.example.com",
)
self.landscape_domain = self.prompt_get_input(
"Landscape Domain: ",
Expand Down
30 changes: 22 additions & 8 deletions landscape/client/deployment.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
import os.path
import subprocess
import sys
import time
from datetime import datetime
from datetime import timezone
from logging import debug
from optparse import SUPPRESS_HELP

from twisted.logger import globalLogBeginner
Expand Down Expand Up @@ -198,7 +200,7 @@ def juju_filename(self):
backwards-compatibility."""
return os.path.join(self.data_path, "juju-info.json")

def auto_configure(self):
def auto_configure(self, retry=False, delay=120, max_retries=5):
"""Automatically configure the client snap."""
client_conf = snap_http.get_conf("landscape-client").result
auto_enroll_conf = client_conf.get("auto-register", {})
Expand All @@ -208,14 +210,24 @@ def auto_configure(self):
if not enabled or configured:
return

title = generate_computer_title(auto_enroll_conf)
if title:
self.computer_title = title
self.write()
for _ in range(max_retries):
title = generate_computer_title(auto_enroll_conf)
if title:
self.computer_title = title
self.write()

auto_enroll_conf["configured"] = True
client_conf["auto-register"] = auto_enroll_conf
snap_http.set_conf("landscape-client", client_conf)
auto_enroll_conf["configured"] = True
client_conf["auto-register"] = auto_enroll_conf
snap_http.set_conf("landscape-client", client_conf)
break

if not retry:
break

# retry until we get the computer title (exponential backoff)
# number of retries capped by `max_retries`
time.sleep(delay)
delay *= 2


def get_versioned_persist(service):
Expand Down Expand Up @@ -243,11 +255,13 @@ def generate_computer_title(auto_enroll_config):
snap_info = get_snap_info()
wait_for_serial = auto_enroll_config.get("wait-for-serial-as", True)
if "serial" not in snap_info and wait_for_serial:
debug(f"No serial assertion in snap info {snap_info}, waiting...")
return

hostname = get_fqdn()
wait_for_hostname = auto_enroll_config.get("wait-for-hostname", False)
if hostname == "localhost" and wait_for_hostname:
debug("Waiting for hostname...")
return

nics = get_active_device_info(default_only=True)
Expand Down
4 changes: 3 additions & 1 deletion landscape/client/monitor/computertags.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ class ComputerTags(DataWatcher):
run_interval = 3600 # Every hour only when data changed
run_immediately = True

def __init__(self, args=sys.argv):
def __init__(self, args=None):
super().__init__()
if args is None:
args = sys.argv
self.args = args # Defined to specify args in unit tests

def get_data(self):
Expand Down
31 changes: 31 additions & 0 deletions landscape/client/tests/test_deployment.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,29 @@ def test_auto_configuration_no_title_generated(
self.assertIsNone(self.config.get("computer_title"))
mock_snap_http.set_conf.assert_not_called()

@mock.patch("landscape.client.deployment.generate_computer_title")
@mock.patch("landscape.client.deployment.snap_http")
def test_auto_configuration_no_title_generated_retry(
self,
mock_snap_http,
mock_generate_title,
):
"""The client is not configured."""
mock_snap_http.get_conf.return_value = SnapdResponse(
"sync",
200,
"OK",
{"auto-register": {"enabled": True, "configured": False}},
)
mock_generate_title.return_value = None

self.assertIsNone(self.config.get("computer_title"))

self.config.auto_configure(retry=True, delay=0.01, max_retries=2)
assert mock_generate_title.call_count == 2
self.assertIsNone(self.config.get("computer_title"))
mock_snap_http.set_conf.assert_not_called()


class GetVersionedPersistTest(LandscapeTest):
def test_upgrade_service(self):
Expand Down Expand Up @@ -424,10 +447,12 @@ def test_generate_computer_title(
)
self.assertEqual(title, "classic-f315cab5")

@mock.patch("landscape.client.deployment.debug")
@mock.patch("landscape.client.deployment.get_snap_info")
def test_generate_computer_title_wait_for_serial_no_serial_assertion(
self,
mock_snap_info,
mock_debug,
):
"""Returns `None`."""
mock_snap_info.return_value = {}
Expand All @@ -442,13 +467,18 @@ def test_generate_computer_title_wait_for_serial_no_serial_assertion(
},
)
self.assertIsNone(title)
mock_debug.assert_called_once_with(
"No serial assertion in snap info {}, waiting..."
)

@mock.patch("landscape.client.deployment.debug")
@mock.patch("landscape.client.deployment.get_fqdn")
@mock.patch("landscape.client.deployment.get_snap_info")
def test_generate_computer_title_wait_for_hostname(
self,
mock_snap_info,
mock_fqdn,
mock_debug,
):
"""Returns `None`."""
mock_snap_info.return_value = {
Expand All @@ -468,6 +498,7 @@ def test_generate_computer_title_wait_for_hostname(
},
)
self.assertIsNone(title)
mock_debug.assert_called_once_with("Waiting for hostname...")

@mock.patch("landscape.client.deployment.subprocess")
@mock.patch("landscape.client.deployment.get_active_device_info")
Expand Down
2 changes: 1 addition & 1 deletion landscape/client/tests/test_watchdog.py
Original file line number Diff line number Diff line change
Expand Up @@ -1527,5 +1527,5 @@ def test_is_snap(self, mock_auto_configure):
with mock.patch("landscape.client.watchdog.pwd", new=self.fake_pwd):
run(["--log-dir", self.makeDir()], reactor=reactor)

mock_auto_configure.assert_called_once_with()
mock_auto_configure.assert_called_once_with(retry=True)
self.assertTrue(reactor.running)
2 changes: 1 addition & 1 deletion landscape/client/watchdog.py
Original file line number Diff line number Diff line change
Expand Up @@ -721,7 +721,7 @@ def run(args=sys.argv, reactor=None):
init_logging(config, "watchdog")

if IS_SNAP:
config.auto_configure()
config.auto_configure(retry=True)

application = Application("landscape-client")
watchdog_service = WatchDogService(config)
Expand Down
1 change: 1 addition & 0 deletions landscape/lib/compat.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# flake8: noqa
# TiCS: disabled

_PY3 = str != bytes

Expand Down
3 changes: 2 additions & 1 deletion landscape/lib/jiffies.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ def detect_jiffies():
# is called.
pid = os.fork()
if pid == 0:
os._exit(0)
# os._exit is standard practice to end a child process
os._exit(0) # //TICS !W0212: private access is OK

uptime2_data = read_uptime2()

Expand Down
18 changes: 9 additions & 9 deletions landscape/lib/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,20 +152,15 @@ def get_filtered_if_info(filters=(), extended=False):
continue

ifaddresses = netifaces.ifaddresses(interface)
if not is_active(ifaddresses):
if (
not is_active(ifaddresses)
and netifaces.AF_LINK not in ifaddresses
):
continue

ifencoded = interface.encode()
flags = get_flags(sock, ifencoded)
if not is_up(flags):
continue

ip_addresses = get_ip_addresses(ifaddresses)
if not extended and netifaces.AF_INET not in ip_addresses:
# Skip interfaces with no IPv4 addr unless extended to
# keep backwards compatibility with single-IPv4 addr
# support.
continue

ifinfo = {"interface": interface}
ifinfo["flags"] = flags
Expand All @@ -184,6 +179,11 @@ def get_filtered_if_info(filters=(), extended=False):
ifaddresses,
)
ifinfo["netmask"] = get_netmask(ifaddresses)
elif netifaces.AF_LINK in ifaddresses and not extended:
ifinfo["ip_address"] = "0.0.0.0"
ifinfo["mac_address"] = get_mac_address(ifaddresses)
ifinfo["broadcast_address"] = "0.0.0.0"
ifinfo["netmask"] = "0.0.0.0"

results.append(ifinfo)
finally:
Expand Down
Loading

0 comments on commit 00b0bbc

Please sign in to comment.