Skip to content

Commit

Permalink
Add telemetry (#258)
Browse files Browse the repository at this point in the history
* Add iotedgehubdev class

* Build solution before start iotedgehubdev

* Update templates to fix route error

* Fail fast if device connection string is not set before setup

* Allow start iotedgehubdev in verbose mode

* Add modulecred command

* Add gateway_host option to setup command

* Add start command

* Remove references to runtime

* Remove runtime.template.json from template.zip

* Only start local registry when pushing images so config/deployment.json
only generated after build

* Check if the solution is built before starting simulator

* Instruct users to setup for starting

* Add tests

* Enlarge monitor timeout when testing simulator

* Fix a issue with spaces in paths

* Prevent deploy command overwriting configs

* Promote setup, start and stop commands to root

* Update unit of monitor timeout to seconds

* Add telemetry modules

* Update config folder to home dir

* Add with_telemetry decorator to commands

* Add tests

* Defer exception handling to with_telemetry decorator

* Fix test failure

* Fix unfinished conflict resolve

* Added support for windows containers via nanoserver (#158)

* added initial support for windows containers

* initial support for nanoserver

* corrected az cli path

* only az not working

* all dependencies correctly working

* added required pip packages

* removed unneeded file

* removed external dependencies

* completed support for nanoserver

* fixed all issued mentioned in PR comments

* installing both python2 and python3 on nanoserver

* set path to point to python3, since python2 is not working yet

* added missing parenthesis

* set python3 as default python

* mapped folder set automatically

* improved build script

* added setuptools install

* set to use latest tag

* added support for powershell script

* automatically map folder

* added sock mapping

* removed pip from requirements since it may generate errors

* ignored .backup files

* fixed spacings

* install modules locally

* fixed bug the prevented arguments to be handled correctly

* fixed bug the prevented arguments to be handled correctly

* added build script alpha version

* comments cleanup

* build will now also push docker images

* Bump version: 0.86.0 → 0.87.0

* version set back to 0.81.0

* fixed some bugs, added parameter for pipy login

* modularized build script

* fixed syntax

* removed renaming of python3 executables

* switch to docker folder if needed

* exit on error

* correctly handled directory switching

* switched to ubuntu 18.04 for python 3.6 default support

* using double slash to make script work

* improved docker image build performances

* file system cleanup

* added platform parameter

* Container docker compose support (#243)

Adding docker compose to container

* Support for multiple registries  (#193)

* envvar parsing of multiple container registries

* rename value, support for pushing modules based on module.json

* string comparison code clean up

* modify envvars with better values & refactor dockercls and .env.tmp

* modified variable names, minor fixes, added envvar testing specific to container registry

* add tests for additional cr, comments to explain code, fix merge conflict

* add additional testing for mutliple registries, fix logic around given/expected env vars

* fix env load in tests

* Tell travis to use DOTENV_FILE

* Mod process timeout for new monitor-events  (#253)

* Merge upstream changes

* Rename config to telemetryconfig

* Defer telemetry decorator to handle errors

* Fix CI failure
  • Loading branch information
LazarusX authored and jongio committed Aug 17, 2018
1 parent d203829 commit da0d642
Show file tree
Hide file tree
Showing 18 changed files with 496 additions and 73 deletions.
1 change: 1 addition & 0 deletions iotedgedev/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
__author__ = 'Jon Gallant'
__email__ = '[email protected]'
__version__ = '0.81.0'
__AIkey__ = '95b20d64-f54f-4de3-8ad5-165a75a6c6fe'
23 changes: 23 additions & 0 deletions iotedgedev/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from fstrings import f

from .azurecli import AzureCli
from .decorators import with_telemetry
from .dockercls import Docker
from .edge import Edge
from .envvars import EnvVars
Expand All @@ -35,26 +36,31 @@

@click.group(context_settings=CONTEXT_SETTINGS, cls=OrganizedGroup)
@click.version_option()
@with_telemetry
def main():
pass


@main.group(context_settings=CONTEXT_SETTINGS, help="Manage IoT Edge solutions", order=1)
@with_telemetry
def solution():
pass


@main.group(context_settings=CONTEXT_SETTINGS, help="Manage IoT Edge simulator", order=1)
@with_telemetry
def simulator():
pass


@main.group(context_settings=CONTEXT_SETTINGS, help="Manage IoT Hub and IoT Edge devices", order=1)
@with_telemetry
def iothub():
pass


@main.group(context_settings=CONTEXT_SETTINGS, help="Manage Docker", order=1)
@with_telemetry
def docker():
pass

Expand All @@ -78,6 +84,7 @@ def docker():
required=False,
type=click.Choice(["csharp", "nodejs", "python", "csharpfunction"]),
help="Specify the template used to create the default module")
@with_telemetry
def create(name, module, template):
utility = Utility(envvars, output)
sol = Solution(output, utility)
Expand All @@ -91,6 +98,7 @@ def create(name, module, template):
help="Create a new IoT Edge solution and provision Azure resources",
# hack to prevent Click truncating help messages
short_help="Create a new IoT Edge solution and provision Azure resources")
@with_telemetry
def init():
utility = Utility(envvars, output)

Expand All @@ -107,6 +115,7 @@ def init():

@solution.command(context_settings=CONTEXT_SETTINGS, help="Push, deploy, start, monitor")
@click.pass_context
@with_telemetry
def e2e(ctx):
ctx.invoke(init)
envvars.load(force=True)
Expand All @@ -127,6 +136,7 @@ def e2e(ctx):
default="csharp",
show_default=True,
help="Specify the template used to create the new module")
@with_telemetry
def add(name, template):
mod = Modules(envvars, output)
mod.add(name, template)
Expand All @@ -152,6 +162,7 @@ def add(name, template):
is_flag=True,
help="Deploy modules to Edge device using deployment.json in the config folder")
@click.pass_context
@with_telemetry
def build(ctx, push, do_deploy):
mod = Modules(envvars, output)
mod.build_push(no_push=not push)
Expand Down Expand Up @@ -179,6 +190,7 @@ def build(ctx, push, do_deploy):
is_flag=True,
help="Inform the push command to not build modules images before pushing to container registry")
@click.pass_context
@with_telemetry
def push(ctx, do_deploy, no_build):
mod = Modules(envvars, output)
mod.push(no_build=no_build)
Expand All @@ -191,6 +203,7 @@ def push(ctx, do_deploy, no_build):


@solution.command(context_settings=CONTEXT_SETTINGS, help="Deploy solution to IoT Edge device")
@with_telemetry
def deploy():
edge = Edge(envvars, output, azure_cli)
edge.deploy()
Expand All @@ -203,6 +216,7 @@ def deploy():
help="Expand environment variables and placeholders in *.template.json and copy to config folder",
# hack to prevent Click truncating help messages
short_help="Expand environment variables and placeholders in *.template.json and copy to config folder")
@with_telemetry
def genconfig():
mod = Modules(envvars, output)
mod.build_push(no_build=True, no_push=True)
Expand All @@ -221,6 +235,7 @@ def genconfig():
required=False,
default=socket.getfqdn(),
show_default=True)
@with_telemetry
def setup_simulator(gateway_host):
sim = Simulator(envvars, output)
sim.setup(gateway_host)
Expand Down Expand Up @@ -265,6 +280,7 @@ def setup_simulator(gateway_host):
default=53000,
show_default=True,
help="Port of the service for sending message.")
@with_telemetry
def start_simulator(solution, build, verbose, inputs, port):
sim = Simulator(envvars, output)
if solution or not inputs:
Expand All @@ -279,6 +295,7 @@ def start_simulator(solution, build, verbose, inputs, port):
@simulator.command(context_settings=CONTEXT_SETTINGS,
name="stop",
help="Stop IoT Edge simulator")
@with_telemetry
def stop_simulator():
sim = Simulator(envvars, output)
sim.stop()
Expand All @@ -302,6 +319,7 @@ def stop_simulator():
"-o",
help="Specify the output file to save the credentials. If the file exists, its content will be overwritten.",
required=False)
@with_telemetry
def modulecred(local, output_file):
sim = Simulator(envvars, output)
sim.modulecred(local, output_file)
Expand All @@ -315,6 +333,7 @@ def modulecred(local, output_file):
"-t",
required=False,
help="Specify number of seconds to monitor for messages")
@with_telemetry
def monitor(timeout):
utility = Utility(envvars, output)
ih = IoTHub(envvars, utility, output, azure_cli)
Expand Down Expand Up @@ -550,6 +569,7 @@ def header_and_default(header, default, default2=None):
is_flag=True,
prompt='Update the .env file with connection strings?',
help='If True, the current .env will be updated with the IoT Hub and Device connection strings.')
@with_telemetry
def setup_iothub(credentials,
service_principal,
subscription,
Expand All @@ -571,6 +591,7 @@ def setup_iothub(credentials,
"Also, update config files to use CONTAINER_REGISTRY_* instead of the Microsoft Container Registry. See CONTAINER_REGISTRY environment variables.",
short_help="Pull Edge runtime images from MCR and push to your specified container registry",
name="setup")
@with_telemetry
def setup_registry():
utility = Utility(envvars, output)
dock = Docker(envvars, utility, output)
Expand Down Expand Up @@ -600,6 +621,7 @@ def setup_registry():
required=False,
is_flag=True,
help="Remove all the images")
@with_telemetry
def clean(module, container, image):
utility = Utility(envvars, output)
dock = Docker(envvars, utility, output)
Expand Down Expand Up @@ -632,6 +654,7 @@ def clean(module, container, image):
required=False,
is_flag=True,
help="Save EdgeAgent, EdgeHub and each Edge module logs to LOGS_PATH.")
@with_telemetry
def log(show, save):
utility = Utility(envvars, output)
dock = Docker(envvars, utility, output)
Expand Down
75 changes: 75 additions & 0 deletions iotedgedev/decorators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import hashlib
import sys
from functools import wraps


def with_telemetry(func):
@wraps(func)
def _wrapper(*args, **kwargs):
from . import telemetry
from .telemetryconfig import TelemetryConfig

config = TelemetryConfig()
config.check_firsttime()
params = parse_params(*args, **kwargs)
telemetry.start(func.__name__, params)
try:
value = func(*args, **kwargs)
telemetry.success()
telemetry.flush()
return value
except Exception as e:
from .output import Output
Output().error('Error: {0}'.format(str(e)))
telemetry.fail(str(e), 'Command failed')
telemetry.flush()
sys.exit(1)

return _wrapper


def suppress_all_exceptions(fallback_return=None):
"""We need to suppress exceptions for some internal functions such as those related to telemetry.
They should not be visible to users.
"""
def _decorator(func):
@wraps(func)
def _wrapped_func(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception:
if fallback_return:
return fallback_return
else:
pass

return _wrapped_func

return _decorator


@suppress_all_exceptions()
def parse_params(*args, **kwargs):
"""Record the parameter keys and whether the values are None"""
params = []
for key, value in kwargs.items():
is_none = '='
if value is not None:
is_none = '!='
params.append('{0}{1}None'.format(key, is_none))
return params


def hash256_result(func):
"""Secure the return string of the annotated function with SHA256 algorithm. If the annotated
function doesn't return string or return None, raise ValueError."""
@wraps(func)
def _decorator(*args, **kwargs):
val = func(*args, **kwargs)
if not val:
raise ValueError('Return value is None')
elif not isinstance(val, str):
raise ValueError('Return value is not string')
hash_object = hashlib.sha256(val.encode('utf-8'))
return str(hash_object.hexdigest())
return _decorator
23 changes: 9 additions & 14 deletions iotedgedev/deploymentmanifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import json
import os
import shutil
import sys


class DeploymentManifest:
Expand All @@ -18,17 +17,18 @@ def __init__(self, envvars, output, utility, path, is_template):
self.is_template = is_template
self.json = json.loads(self.utility.get_file_contents(path, expandvars=True))
except FileNotFoundError:
self.output.error('Deployment manifest template file "{0}" not found'.format(path))
if is_template:
deployment_manifest_path = envvars.DEPLOYMENT_CONFIG_FILE_PATH
if os.path.exists(deployment_manifest_path):
self.output.error('Deployment manifest template file "{0}" not found'.format(path))
if output.confirm('Would you like to make a copy of the deployment manifest file "{0}" as the deployment template file?'.format(deployment_manifest_path), default=True):
shutil.copyfile(deployment_manifest_path, path)
self.json = json.load(open(envvars.DEPLOYMENT_CONFIG_FILE_PATH))
envvars.save_envvar("DEPLOYMENT_CONFIG_TEMPLATE_FILE", path)
else:
raise FileNotFoundError('Deployment manifest file "{0}" not found'.format(path))
else:
self.output.error('Deployment manifest file "{0}" not found'.format(path))
sys.exit(1)
raise FileNotFoundError('Deployment manifest file "{0}" not found'.format(path))

def add_module_template(self, module_name):
"""Add a module template to the deployment manifest with amd64 as the default platform"""
Expand All @@ -46,8 +46,7 @@ def add_module_template(self, module_name):
try:
self.utility.nested_set(self.get_module_content(), ["$edgeAgent", "properties.desired", "modules", module_name], json.loads(new_module))
except KeyError as err:
self.output.error("Missing key {0} in file {1}".format(err, self.path))
sys.exit(1)
raise KeyError("Missing key {0} in file {1}".format(err, self.path))

self.add_default_route(module_name)

Expand All @@ -59,26 +58,23 @@ def add_default_route(self, module_name):
try:
self.utility.nested_set(self.get_module_content(), ["$edgeHub", "properties.desired", "routes", new_route_name], new_route)
except KeyError as err:
self.output.error("Missing key {0} in file {1}".format(err, self.path))
sys.exit(1)
raise KeyError("Missing key {0} in file {1}".format(err, self.path))

def get_user_modules(self):
"""Get user modules from deployment manifest"""
try:
modules = self.get_desired_property("$edgeAgent", "modules")
return list(modules.keys())
except KeyError as err:
self.output.error("Missing key {0} in file {1}".format(err, self.path))
sys.exit(1)
raise KeyError("Missing key {0} in file {1}".format(err, self.path))

def get_system_modules(self):
"""Get system modules from deployment manifest"""
try:
modules = self.get_desired_property("$edgeAgent", "systemModules")
return list(modules.keys())
except KeyError as err:
self.output.error("Missing key {0} in file {1}".format(err, self.path))
sys.exit(1)
raise KeyError("Missing key {0} in file {1}".format(err, self.path))

def get_modules_to_process(self):
"""Get modules to process from deployment manifest template"""
Expand All @@ -96,8 +92,7 @@ def get_modules_to_process(self):
modules_to_process.append((module_dir, module_platform))
return modules_to_process
except KeyError as err:
self.output.error("Missing key {0} in file {1}".format(err, self.path))
sys.exit(1)
raise KeyError("Missing key {0} in file {1}".format(err, self.path))

def get_desired_property(self, module, prop):
return self.get_module_content()[module]["properties.desired"][prop]
Expand Down
4 changes: 1 addition & 3 deletions iotedgedev/dockercls.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,7 @@ def init_local_registry(self, local_server):
parts = local_server.split(":")

if len(parts) < 2:
self.output.error("You must specific a port for your local registry server. Expected: 'localhost:5000'. Found: " +
local_server)
sys.exit()
raise ValueError("You must specific a port for your local registry server. Expected: 'localhost:5000'. Found: " + local_server)

port = parts[1]
ports = {'{0}/tcp'.format(port): int(port)}
Expand Down
Loading

0 comments on commit da0d642

Please sign in to comment.