Ejemplo n.º 1
0
class TestHelm(EventTestCase):
    def setUp(self):
        super(TestHelm, self).setUp()
        self.sensors = Mock()
        self.logger = Mock()
        self.steerer = Mock()
        self.helm = Helm(self.exchange, self.sensors, self.steerer,
                         self.logger, HELM_CONFIG)
        self.helm.previous_heading = 180

    def currently_tracking(self,
                           previous_heading,
                           current_track,
                           rudder_angle=0):
        self.sensors.compass_heading_smoothed = current_track
        self.sensors.compass_heading_average = NaN
        self.sensors.rate_of_turn = current_track - previous_heading
        self.sensors.rate_of_turn_average = self.sensors.rate_of_turn

    def averagely_tracking(self, previous_heading, current_track):
        self.sensors.compass_heading_average = current_track
        self.sensors.compass_heading_smoothed = NaN
        self.sensors.rate_of_turn = 0
        self.sensors.rate_of_turn_average = current_track - previous_heading

    def test_should_steer_following_set_course_event(self):
        self.averagely_tracking(204, 200)
        self.steerer.on_course.side_effect = [False, False]
        self.exchange.publish(Event(EventName.set_course, heading=196))
        self.steerer.steer.assert_called_with(196, 200, -4)

        self.averagely_tracking(200, 202)
        self.sensors.rate_of_turn_average = -20
        self.exchange.publish(Event(EventName.check_course))
        self.steerer.steer.assert_called_with(196, 202, -20)

    def test_should_not_steer_if_steerer_says_we_are_on_course(self):
        self.averagely_tracking(300, 290)
        self.steerer.on_course.side_effect = [True]
        self.exchange.publish(Event(EventName.set_course, heading=294))

        self.assertEqual(self.steerer.steer.call_count, 0)

    def test_should_steer_if_off_course_by_more_than_configured_20_degrees(
            self):
        self.helm.requested_heading = 90
        self.averagely_tracking(50, 60)
        self.steerer.on_course.side_effect = [False]

        self.helm.check_course(Event(EventName.check_course))

        self.steerer.steer.assert_called_with(90, 60, 10)

    def test_should_subscribe_check_course_every_10_seconds(self):
        self.listen(EventName.every)
        helm = Helm(self.exchange, self.sensors, self.steerer, self.logger,
                    HELM_CONFIG)

        self.assertEqual(self.events[EventName.every][0].next_event.name,
                         EventName.check_course)
Ejemplo n.º 2
0
    def __init__(self, file):
        """
        Parse course.yml contents into instances.
        """
        self.config = Config()
        self.helm = Helm()
        self._dict = yaml.load(file)
        self._repositories = []
        self._charts = []
        for name, repository in self._dict.get('repositories', {}).iteritems():
            repository['name'] = name
            self._repositories.append(Repository(repository))

        for name, chart in self._dict.get('charts', {}).iteritems():
            self._charts.append(Chart({name: chart}))

        for repo in self._repositories:
            type(repo)
            if not self.config.local_development:
                logging.debug("Installing repository: {}".format(repo))
                repo.install()

        self.helm.repo_update()

        if not self.config.local_development:
            self._compare_required_versions()
Ejemplo n.º 3
0
 def setUp(self):
     super(TestHelm, self).setUp()
     self.sensors = Mock()
     self.logger = Mock()
     self.steerer = Mock()
     self.helm = Helm(self.exchange, self.sensors, self.steerer,
                      self.logger, HELM_CONFIG)
     self.helm.previous_heading = 180
Ejemplo n.º 4
0
    def install(self, namespace=None, context=None):
        """
        Description:
        - Uprade --install the course chart

        Arguments:
        - namespace (string). Passed in but will be overriddne by Chart().namespace if set

        Returns:
        - Bool
        """
        helm = Helm()

        # Set the namespace      
        if self.namespace is None:
            self._namespace = namespace

        # Set the context
        if self.context is None:
            self._context = context

        self.pre_install_hook()
        # TODO: Improve error handling of a repository installation
        self.repository.install(self.name, self.version)
        self.chart_path = self.repository.chart_path
        # Update the helm dependencies

        if self.repository.git is None:
            self.update_dependencies()

        # Build the args for the chart installation
        # And add any extra arguments

        self.args = ['{}'.format(self._release_name), self.chart_path, ]
        self.args.append('--namespace={}'.format(self.namespace))
        if self.context is not None:
            self.args.append('--kube-context={}'.format(self.context))
        self.args.extend(self.debug_args)
        self.args.extend(self.helm_args)
        if self.version:
            self.args.append('--version={}'.format(self.version))
        for file in self.files:
            self.args.append("-f={}".format(file))

        for key, value in self.values.iteritems():
            for k, v in self._format_set(key, value):
                self.args.append("--set={}={}".format(k, v))
        
        for key, value in self.values_strings.iteritems():
            for k, v in self._format_set(key, value):
                self.args.append("--set={}={}".format(k, v))

        self.__check_env_vars()
        try:
            helm.upgrade(self.args)
        except ReckonerCommandException, e:
            logging.error(e.stderr)
            raise e
Ejemplo n.º 5
0
 def __init__(self, chart):
     self.helm = Helm()
     self.config = Config()
     self._release_name = chart.keys()[0]
     self._chart = chart[self._release_name]
     self._repository = Repository(self._chart.get('repository', default_repository))
     self._chart['values'] = self.ordereddict_to_dict(self._chart.get('values', {}))
     value_strings = self._chart.get('values-strings', {})
     self._chart['values_strings'] = self.ordereddict_to_dict(value_strings)
     if value_strings != {}:
         del(self._chart['values-strings'])
Ejemplo n.º 6
0
 def setUp(self):
     super(TestHelm, self).setUp()
     self.sensors = Mock()
     self.logger = Mock()
     self.steerer = Mock()
     self.helm = Helm(self.exchange, self.sensors, self.steerer, self.logger, HELM_CONFIG)
     self.helm.previous_heading = 180
Ejemplo n.º 7
0
    def __init__(self,
                 file=None,
                 dryrun=False,
                 debug=False,
                 helm_args=None,
                 local_development=False):

        self.config = Config()
        self.config.dryrun = dryrun
        self.config.debug = debug
        self.config.helm_args = helm_args
        self.config.local_development = local_development

        if self.config.debug:
            logging.warn(
                "The --debug flag will be deprecated.  Please use --helm-args or --dry-run instead."
            )
        if self.config.helm_args:
            logging.warn(
                "Specifying --helm-args on the cli will override helm_args in the course file."
            )

        try:
            self.helm = Helm()
        except Exception, e:
            logging.error(e)
            sys.exit(1)
Ejemplo n.º 8
0
    def test_should_subscribe_check_course_every_10_seconds(self):
        self.listen(EventName.every)
        helm = Helm(self.exchange, self.sensors, self.steerer, self.logger,
                    HELM_CONFIG)

        self.assertEqual(self.events[EventName.every][0].next_event.name,
                         EventName.check_course)
Ejemplo n.º 9
0
    def test_should_navigate_to_next_waypoint_with_kink_in_route(self):
        destination = Waypoint(Position(10.03, 10.03), 10)
        gps = FakeMovingGPS([
            Position(10, 10),
            Position(10.01, 10.01),
            Position(10.025, 10.015),
            Position(10.03, 10.03)
        ])
        sensors = FakeSensors(gps, 1, 45)
        steerer = Steerer(self.servo, self.logger, CONFIG['steerer'])
        helm = Helm(self.exchange, sensors, steerer, self.logger,
                    CONFIG['helm'])
        navigator = Navigator(sensors, Globe(), self.exchange, self.logger,
                              CONFIG['navigator'])

        self.exchange.publish(Event(EventName.navigate, waypoint=destination))
        self.ticks(number=14, duration=200)

        self.logger.info.assert_has_calls([
            call(
                'Navigator, steering to +10.030000,+10.030000, bearing  44.6, distance 4681.8m, review after 600s'
            ),
            call(
                'Navigator, steering to +10.030000,+10.030000, bearing  44.6, distance 3121.2m, review after 600s'
            ),
            call(
                'Navigator, steering to +10.030000,+10.030000, bearing  71.3, distance 1734.0m, review after 600s'
            ),
            call('Navigator, arrived at +10.030000,+10.030000')
        ])
Ejemplo n.º 10
0
    def test_should_steer_repeatedly_during_navigation(self):
        logger = Mock()
        destination = Waypoint(Position(10.0003, 10.0003), 10)
        gps = FakeMovingGPS([
            Position(10, 10),
            Position(10.0001, 10.00015),
            Position(10.00025, 10.0002),
            Position(10.0003, 10.0003)
        ])
        sensors = FakeSensors(gps, 1, 45)
        steerer = Steerer(self.servo, logger, CONFIG['steerer'])
        helm = Helm(self.exchange, sensors, steerer, logger, CONFIG['helm'])
        navigator = Navigator(sensors, Globe(), self.exchange, logger,
                              CONFIG['navigator'])

        self.exchange.publish(Event(EventName.navigate, waypoint=destination))
        self.ticks(number=7, duration=20)

        logger.debug.assert_has_calls([
            call(
                'Navigator, distance from waypoint +46.819018, combined tolerance +10.000000'
            ),
            call(
                'Navigator, distance from waypoint +27.647432, combined tolerance +10.000000'
            ),
            call(
                'Steerer, steering 36.4, heading 45.0, rate of turn +1.0, rudder +0.0, new rudder +4.6'
            ),
            call(
                'Steerer, steering 36.4, heading 45.0, rate of turn +1.0, rudder +4.6, new rudder +9.2'
            ),
            call(
                'Navigator, distance from waypoint +12.281099, combined tolerance +10.000000'
            ),
            call(
                'Steerer, steering 63.1, heading 45.0, rate of turn +1.0, rudder +9.2, new rudder +0.4'
            ),
            call(
                'Navigator, distance from waypoint +0.000000, combined tolerance +10.000000'
            ),
            call(
                'Steerer, steering 63.1, heading 45.0, rate of turn +1.0, rudder +0.4, new rudder -8.3'
            ),
            call(
                'Steerer, steering 63.1, heading 45.0, rate of turn +1.0, rudder -8.3, new rudder -17.1'
            )
        ])

        logger.info.assert_has_calls([
            call(
                'Navigator, steering to +10.000300,+10.000300, bearing  44.6, distance 46.8m, review after 23s'
            ),
            call(
                'Navigator, steering to +10.000300,+10.000300, bearing  36.4, distance 27.6m, review after 13s'
            ),
            call(
                'Navigator, steering to +10.000300,+10.000300, bearing  63.1, distance 12.3m, review after 6s'
            ),
            call('Navigator, arrived at +10.000300,+10.000300')
        ])
Ejemplo n.º 11
0
    def install(self, chart_name=None, version=None):
        """ Install Helm repository """

        from helm import Helm  # currently cheating to get around a circular import issue

        helm = Helm()
        if self.git is None:
            self._chart_path = "{}/{}".format(self.name, chart_name)
            if self not in helm.repositories:
                try:
                    return helm.repo_add(str(self.name), str(self.url))
                except AutoHelmCommandException, e:
                    logging.warn("Unable to install repository {}: {}".format(self.name, e.stderr))
                    return False
            else:
                logging.debug("Chart repository {} already installed".format(self.name))
                return True
Ejemplo n.º 12
0
    def __init__(self, gps=False, servo_port=SERVO_PORT):
        # devices
        self._gps = gps
        self.windsensor = WindSensor(I2C(WINDSENSOR_I2C_ADDRESS))
        self.compass = Compass(I2C(COMPASS_I2C_ADDRESS),
                               I2C(ACCELEROMETER_I2C_ADDRESS))
        self.red_led = GpioWriter(17, os)
        self.green_led = GpioWriter(18, os)

        # Navigation
        self.globe = Globe()
        self.timer = Timer()
        self.application_logger = self._rotating_logger(APPLICATION_NAME)
        self.position_logger = self._rotating_logger("position")
        self.exchange = Exchange(self.application_logger)
        self.timeshift = TimeShift(self.exchange, self.timer.time)
        self.event_source = EventSource(self.exchange, self.timer,
                                        self.application_logger,
                                        CONFIG['event source'])

        self.sensors = Sensors(self.gps, self.windsensor, self.compass,
                               self.timer.time, self.exchange,
                               self.position_logger, CONFIG['sensors'])
        self.gps_console_writer = GpsConsoleWriter(self.gps)
        self.rudder_servo = Servo(serial.Serial(servo_port),
                                  RUDDER_SERVO_CHANNEL, RUDDER_MIN_PULSE,
                                  RUDDER_MIN_ANGLE, RUDDER_MAX_PULSE,
                                  RUDDER_MAX_ANGLE)
        self.steerer = Steerer(self.rudder_servo, self.application_logger,
                               CONFIG['steerer'])
        self.helm = Helm(self.exchange, self.sensors, self.steerer,
                         self.application_logger, CONFIG['helm'])
        self.course_steerer = CourseSteerer(self.sensors, self.helm,
                                            self.timer,
                                            CONFIG['course steerer'])
        self.navigator = Navigator(self.sensors, self.globe, self.exchange,
                                   self.application_logger,
                                   CONFIG['navigator'])
        self.self_test = SelfTest(self.red_led, self.green_led, self.timer,
                                  self.rudder_servo, RUDDER_MIN_ANGLE,
                                  RUDDER_MAX_ANGLE)

        # Tracking
        self.tracking_logger = self._rotating_logger("track")
        self.tracking_sensors = Sensors(self.gps, self.windsensor,
                                        self.compass, self.timer.time,
                                        self.exchange, self.tracking_logger,
                                        CONFIG['sensors'])
        self.tracker = Tracker(self.tracking_logger, self.tracking_sensors,
                               self.timer)
Ejemplo n.º 13
0
 def __init__(self, chart):
     self.helm = Helm()
     self.config = Config()
     self._release_name = chart.keys()[0]
     self._chart = chart[self._release_name]
     self._repository = Repository(self._chart.get('repository', default_repository))
     self._chart['values'] = self._chart.get('values', {})
     
     self._namespace = self._chart.get('namespace')
     self._context = self._chart.get('context')
     value_strings = self._chart.get('values-strings', {})
     self._chart['values_strings'] = value_strings
     
     if value_strings != {}:
         del(self._chart['values-strings'])
Ejemplo n.º 14
0
    def __init__(self):
        self.globe = Globe()
        self.console_logger = self._console_logger()
        self.exchange = Exchange(self.console_logger)
        self.gps = SimulatedGPS(CHORLTON.position,0,0.1)
        self.vehicle = SimulatedVehicle(self.gps, self.globe,self.console_logger,single_step=False)
        self.timeshift = TimeShift(self.exchange,self.vehicle.timer.time)
        self.event_source = EventSource(self.exchange,self.vehicle.timer,self.console_logger,CONFIG['event source'])
        self.sensors = Sensors(self.vehicle.gps, self.vehicle.windsensor,self.vehicle.compass,self.vehicle.timer.time,self.exchange,self.console_logger,CONFIG['sensors'])
        self.steerer = Steerer(self.vehicle.rudder,self.console_logger,CONFIG['steerer'])
        self.helm = Helm(self.exchange, self.sensors, self.steerer, self.console_logger, CONFIG['helm'])
        self.course_steerer = CourseSteerer(self.sensors,self.helm,self.vehicle.timer, CONFIG['course steerer'])
        self.navigator_simulator = Navigator(self.sensors,self.globe,self.exchange,self.console_logger,CONFIG['navigator'])

        self.tracking_timer = Timer()
        self.tracker_simulator = Tracker(self.console_logger,self.sensors,self.tracking_timer)
Ejemplo n.º 15
0
    def __init__(self,
                 file=None,
                 dryrun=False,
                 debug=False,
                 helm_args=None,
                 local_development=False):

        self.config = Config()
        self.config.dryrun = dryrun
        self.config.debug = debug
        self.config.helm_args = helm_args
        self.config.local_development = local_development

        try:
            self.helm = Helm()
        except Exception, e:
            logging.error(e)
            sys.exit(1)
Ejemplo n.º 16
0
    def __init__(self,
                 file=None,
                 dryrun=False,
                 debug=False,
                 helm_args=None,
                 local_development=False):

        self.config = Config()
        self.config.dryrun = dryrun
        self.config.debug = debug
        self.config.helm_args = helm_args
        self.config.local_development = local_development
        self.helm = Helm()

        if not self.config.local_development and not self.helm.server_version:
            logging.error(
                "Tiller not present in cluster. Have you run `helm init`?")
            sys.exit(1)

        self.course = Course(file)
Ejemplo n.º 17
0
    def setUp(self):
        super(TestHelmOscillation, self).setUp()

        # Sensors
        mock_angle = PropertyMock(return_value=3.0)
        self.time = 0
        self.gps = StubGPS()
        self.windsensor = Mock()
        self.compass = Mock()
        self.compass.bearing = 0
        type(self.windsensor).angle = mock_angle

        self.logger = Mock()
        self.sensors = Sensors(self.gps, self.windsensor, self.compass,
                               self.mock_time, self.exchange, self.logger,
                               SENSOR_CONFIG)
        self.rudder_servo = RudderSimulator()
        self.steerer = Steerer(self.rudder_servo, self.logger, STEERER_CONFIG)
        self.helm = Helm(self.exchange, self.sensors, self.steerer,
                         self.logger, HELM_CONFIG)
        self.helm.previous_heading = 0
Ejemplo n.º 18
0
class TestCluster:
    def __init__(self, kube_config, storage_provisioner, registry):
        self.kube_config = kube_config
        self.registry = registry
        self.storage_provisioner = storage_provisioner

        self.current_context = None
        self.helm = None
        self.namespaces = []

    def _load_kube_config(self):
        config.load_kube_config(config_file=self.kube_config)
        _, context = config.list_kube_config_contexts(config_file=self.kube_config)
        self.current_context = context["name"]

    def create_image_pull_secret(self, namespace="default"):
        secret_metadata = client.V1ObjectMeta(name="image-pull-secret")
        auth_string = str.encode(f"{self.registry['user']}:{self.registry['pwd']}")
        secret_data = {
            "auths": {
                self.registry["url"]: {
                    "auth": base64.b64encode(auth_string).decode("utf-8")
                }
            }
        }
        secret_data = json.dumps(secret_data).encode()
        secret_body = client.V1Secret(
            api_version="v1",
            kind="Secret",
            metadata=secret_metadata,
            type="kubernetes.io/dockerconfigjson",
            data={".dockerconfigjson": base64.b64encode(secret_data).decode("utf-8")},
        )
        core_v1 = client.CoreV1Api()
        try:
            core_v1.create_namespaced_secret(namespace, secret_body)
        except client.rest.ApiException as exc:
            if exc.status == 409 and exc.reason == "Conflict":
                warnings.warn(
                    "Kubernetes Cluster not empty. Image pull secret already exists."
                )
            else:
                raise exc

    def create_namespace(self, name):
        namespace_metadata = client.V1ObjectMeta(name=name)
        namespace_body = client.V1Namespace(
            kind="Namespace", api_version="v1", metadata=namespace_metadata
        )
        core_v1 = client.CoreV1Api()
        core_v1.create_namespace(body=namespace_body)
        self.namespaces.append(name)
        self.create_image_pull_secret(name)

    def delete_namespace(self, name):
        core_v1 = client.CoreV1Api()
        core_v1.delete_namespace(name, body=client.V1DeleteOptions())
        self.namespaces.remove(name)

    def install_storage_provisioner(self):
        self.storage_provisioner.set_helm_connector(self.helm)
        self.storage_provisioner.deploy()

    def setup(self):
        self._load_kube_config()
        self.create_image_pull_secret()
        self.helm = Helm(self.kube_config, self.current_context)
        self.install_storage_provisioner()

    def cleanup(self):
        while self.namespaces:
            self.helm.delete_all(
                namespace=self.namespaces[0], exceptions=[self.storage_provisioner.name]
            )
            self.delete_namespace(self.namespaces[0])
        self.storage_provisioner.delete()
        core_v1 = client.CoreV1Api()
        core_v1.delete_namespaced_secret(
            "image-pull-secret", "default", body=client.V1DeleteOptions()
        )
Ejemplo n.º 19
0
class Course(object):
    """
    Description:
    - Top level class for the attribues of the course.yml file
    - Parses yaml file into verious Reckoner classes

    Arguments:
    - file (File)

    Attributes:
    - config: Instance of Config()
    - helm: Instance of Helm()
    - charts: List of Chart() instances
    - repositories: List of Repository() instances

    """
    def __init__(self, file):
        """
        Parse course.yml contents into instances.
        """
        self.config = Config()
        self.helm = Helm()
        self._dict = yaml.load(file)
        self._repositories = []
        self._charts = []
        for name, repository in self._dict.get('repositories', {}).iteritems():
            repository['name'] = name
            self._repositories.append(Repository(repository))

        for name, chart in self._dict.get('charts', {}).iteritems():
            self._charts.append(Chart({name: chart}))

        for repo in self._repositories:
            type(repo)
            if not self.config.local_development:
                logging.debug("Installing repository: {}".format(repo))
                repo.install()

        self.helm.repo_update()

        if not self.config.local_development:
            self._compare_required_versions()

    def __str__(self):
        return str(self._dict)

    @property
    def repositories(self):
        """ Course repositories """
        return self._repositories

    def __getattr__(self, key):
        return self._dict.get(key)

    @property
    def charts(self):
        """ List of Chart() instances """
        return self._charts

    def plot(self, charts_to_install):
        """
        Accepts charts_to_install, an interable of the names of the charts
        to install. This method compares the charts in the argument to the 
        charts in the course and calls Chart.install()

        """
        _charts = []
        _failed_charts = []
        self._charts_to_install = []

        try:
            iter(charts_to_install)
        except TypeError:
            charts_to_install = (charts_to_install)

        for chart in self.charts:
            if chart.release_name in charts_to_install:
                self._charts_to_install.append(chart)

        for chart in self._charts_to_install:
            logging.info("Installing {}".format(chart.release_name))
            try:
                chart.install(namespace=self.namespace, context=self.context)
            except (Exception, ReckonerCommandException), e:
                if type(e) == ReckonerCommandException:
                    logging.error(e.stderr)
                logging.error('Helm upgrade failed. Rolling back {}'.format(
                    chart.release_name))
                logging.debug(traceback.format_exc())
                chart.rollback
                _failed_charts.append(chart)

        if _failed_charts:
            logging.error(
                "ERROR: Some charts failed to install and were rolled back")
            for chart in _failed_charts:
                logging.error(" - {}".format(chart.release_name))
        return True
Ejemplo n.º 20
0
 def setup(self):
     self._load_kube_config()
     self.create_image_pull_secret()
     self.helm = Helm(self.kube_config, self.current_context)
     self.install_storage_provisioner()
Ejemplo n.º 21
0
class TestHelm(EventTestCase):
    def setUp(self):
        super(TestHelm, self).setUp()
        self.sensors = Mock()
        self.logger = Mock()
        self.steerer = Mock()
        self.helm = Helm(self.exchange, self.sensors, self.steerer, self.logger, HELM_CONFIG)
        self.helm.previous_heading = 180

    def reduction_factor(self):
        factor = float(HELM_CONFIG["turn steer interval"]) / (HELM_CONFIG["on course check interval"])
        return factor

    def currently_tracking(self, previous_heading, current_track, rudder_angle=0):
        self.sensors.compass_heading_instant = current_track
        self.sensors.compass_heading_average = NaN
        self.sensors.rate_of_turn = current_track - previous_heading
        self.sensors.rate_of_turn_average = self.sensors.rate_of_turn

    def averagely_tracking(self, previous_heading, current_track):
        self.sensors.compass_heading_average = current_track
        self.sensors.compass_heading_instant = NaN
        self.sensors.rate_of_turn = 0
        self.sensors.rate_of_turn_average = current_track - previous_heading

    def test_should_steer_following_set_course_event(self):
        self.currently_tracking(204, 200)
        self.exchange.publish(Event(EventName.set_course, heading=196))
        self.steerer.steer.assert_called_with(196, 200, -4)

        self.sensors.track = 202
        self.sensors.rate_of_turn = -20
        self.exchange.publish(Event(EventName.steer))
        self.steerer.steer.assert_called_with(196, 200, -20)

    def test_should_use_instant_heading_when_turning(self):
        self.helm.requested_heading = 5
        self.currently_tracking(340, 350)
        self.helm.turn(Event(EventName.steer))

        self.steerer.steer.assert_called_with(5, 350, 10)

    def test_should_use_average_heading_and_reduction_factor_when_checking_course(self):
        self.helm.requested_heading = 5
        self.averagely_tracking(335, 350)
        self.helm.check_course(Event(EventName.steer))

        self.steerer.steer.assert_called_with(5, 350, 15, self.reduction_factor())

    def test_should_trigger_turning_if_off_course_by_more_than_configured_20_degrees(self):
        self.exchange.unsubscribe(EventName.steer, self.helm.turn)
        self.helm.requested_heading = 90
        self.averagely_tracking(50, 60)
        self.helm.check_course(Event(EventName.steer))

        self.steerer.steer.assert_called_with(90, 60, 10, self.reduction_factor())
        self.assertIn(self.helm.turn, self.exchange.register[EventName.steer])
        self.assertTrue(self.helm.turning)

    def test_should_immediately_change_to_turning_when_course_is_set(self):
        self.exchange.unsubscribe(EventName.steer, self.helm.turn)
        self.currently_tracking(50, 60)

        self.exchange.publish(Event(EventName.set_course, heading=90))

        self.steerer.steer.assert_called_with(90, 60, 10)
        self.assertIn(self.helm.turn, self.exchange.register[EventName.steer])
        self.assertTrue(self.helm.turning)

    def test_should_unsubscribe_turn_to_steer_event_when_on_course_after_configured_three_checks(self):
        self.currently_tracking(120, 125)
        self.exchange.publish(Event(EventName.set_course, heading=90))

        self.currently_tracking(95, 85)
        self.exchange.publish(Event(EventName.steer))
        self.currently_tracking(85, 87)
        self.exchange.publish(Event(EventName.steer))
        self.currently_tracking(85, 87)
        self.exchange.publish(Event(EventName.steer))
        self.currently_tracking(85, 87)
        self.exchange.publish(Event(EventName.steer))

        self.assertNotIn(self.helm.turn, self.exchange.register[EventName.steer])
        self.assertFalse(self.helm.turning)

    def test_should_continue_turning_if_on_course_with_high_rate_of_turn(self):
        self.currently_tracking(95, 85)
        self.exchange.publish(Event(EventName.set_course, heading=90))
        self.steerer.on_course.side_effect = [False, False, False, False]

        self.currently_tracking(85, 95)
        self.exchange.publish(Event(EventName.steer))
        self.currently_tracking(95, 85)
        self.exchange.publish(Event(EventName.steer))
        self.currently_tracking(85, 95)
        self.exchange.publish(Event(EventName.steer))
        self.currently_tracking(95, 85)
        self.exchange.publish(Event(EventName.steer))

        self.assertIn(self.helm.turn, self.exchange.register[EventName.steer])
        self.assertTrue(self.helm.turning)

    def test_should_subscribe_check_course_every_10_seconds(self):
        self.listen(EventName.every)
        helm = Helm(self.exchange, self.sensors, self.steerer, self.logger, HELM_CONFIG)

        self.assertEqual(self.events[EventName.every][0].next_event.name, EventName.check_course)

    def test_should_generate_repeating_steer_event_according_to_config(self):
        self.listen(EventName.every)
        helm = Helm(self.exchange, self.sensors, self.steerer, self.logger, HELM_CONFIG)

        repeating_steer_event = self.events[EventName.every][1]
        self.assertEqual(repeating_steer_event.next_event.name, EventName.steer)
        self.assertEqual(repeating_steer_event.seconds, 3)

    def test_check_course_should_not_steer_if_we_are_turning(self):
        self.helm.turning = True

        self.helm.check_course(Event(EventName.steer))

        self.assertEqual(self.steerer.steer.call_count, 0)
Ejemplo n.º 22
0
class Course(object):
    """
    Description:
    - Top level class for the attribues of the course.yml file
    - Parses yaml file into verious AutoHelm classes

    Arguments:
    - file (File)

    Attributes:
    - config: Instance of Config()
    - helm: Instance of Helm()
    - charts: List of Chart() instances
    - repositories: List of Repository() instances

    """
    def __init__(self, file):
        """
        Parse course.yml contents into instances.
        """
        self.config = Config()
        self.helm = Helm()
        self._dict = yaml.load(file)
        self._repositories = []
        self._charts = []
        for name, repository in self._dict.get('repositories', {}).iteritems():
            repository['name'] = name
            self._repositories.append(Repository(repository))

        for name, chart in self._dict.get('charts', {}).iteritems():
            self._charts.append(Chart({name: chart}))

        for repo in self._repositories:
            type(repo)
            if not self.config.local_development:
                logging.debug("Installing repository: {}".format(repo))
                repo.install()

        self.helm.repo_update()

        if not self.config.local_development:
            self._compare_required_versions()

    def __str__(self):
        return str(self._dict)

    @property
    def repositories(self):
        """ Course repositories """
        return self._repositories

    def __getattr__(self, key):
        return self._dict.get(key)

    @property
    def charts(self):
        """ List of Chart() instances """
        return self._charts

    def plot(self, charts_to_install):
        """
        Accepts charts_to_install, an interable of the names of the charts
        to install. This method compares the charts in the argument to the 
        charts in the course and calls Chart.install()

        """
        _charts = []
        _failed_charts = []
        self._charts_to_install = []

        try:
            iter(charts_to_install)
        except TypeError:
            charts_to_install = (charts_to_install)

        for chart in self.charts:
            if chart.release_name in charts_to_install:
                self._charts_to_install.append(chart)

        for chart in self._charts_to_install:
            logging.debug("Installing {}".format(chart.name))
            if not chart.install(self.namespace):
                logging.error(
                    'Helm upgrade failed on {}. Rolling back...'.format(chart))
                chart.rollback
                _failed_charts.append(chart)

        if _failed_charts:
            logging.error(
                "ERROR: Some charts failed to install and were rolled back")
            for chart in _failed_charts:
                logging.error(" - {}".format(chart.release_name))
        return True

    def _compare_required_versions(self):
        """
        Compare installed versions of helm and autohelm to the minimum versions
        required by the course.yml
        Accepts no arguments
        """
        if self.minimum_versions is None:
            return True
        helm_minimum_version = self.minimum_versions.get('helm', '0.0.0')
        autohelm_minimum_version = self.minimum_versions.get(
            'autohelm', '0.0.0')

        logging.debug(
            "Helm Minimum Version is: {}".format(helm_minimum_version))
        logging.debug("Helm Installed Version is {}".format(
            self.helm.client_version))

        logging.debug(
            "Autohelm Minimum Version is {}".format(autohelm_minimum_version))
        logging.debug(
            "Autohelm Installed Version is {}".format(autohelm_version))

        r1 = semver.compare(autohelm_version, autohelm_minimum_version)
        if r1 < 0:
            raise MinimumVersionException(
                "autohelm Minimum Version {} not met.".format(
                    autohelm_minimum_version))

        if not self.config.local_development:
            r2 = semver.compare(self.helm.client_version, helm_minimum_version)
            if r2 < 0:
                raise MinimumVersionException(
                    "helm Minimum Version {} not met.".format(
                        helm_minimum_version))

        return True
Ejemplo n.º 23
0
class Chart(object):
    """
    Description:
    - Chart class for each release in the course.yml

    Arguments:
    - chart (dict):

    Attributes:
    - config: Instance of Config()
    - helm: Instance of Helm()
    - release_name : String. Name of the release
    - name: String. Name of chart
    - files: List. Values files
    - namespace

    Returns:
    - Instance of Response() is truthy where Reponse.exitcode == 0
    - Instance of Response() is falsey where Reponse.exitcode != 0
    """

    def __init__(self, chart):
        self.helm = Helm()
        self.config = Config()
        self._release_name = chart.keys()[0]
        self._chart = chart[self._release_name]
        self._repository = Repository(self._chart.get('repository', default_repository))
        self._chart['values'] = self.ordereddict_to_dict(self._chart.get('values', {}))
        value_strings = self._chart.get('values-strings', {})
        self._chart['values_strings'] = self.ordereddict_to_dict(value_strings)
        if value_strings != {}:
            del(self._chart['values-strings'])

    @property
    def release_name(self):
        """
        Returns release name of course chart
        """
        return self._release_name

    @property
    def name(self):
        """
        Retturns chart name of course chart
        """
        return self._chart.get('chart', self._release_name)

    def ordereddict_to_dict(self, value):
        """
        Converts an OrderedDict to a standard dict
        """
        for k, v in value.items():
            if type(v) == OrderedDict:
                value[k] = self.ordereddict_to_dict(v)
            if type(v) == list:
                for item in v:
                    if type(item) == OrderedDict:
                        v.remove(item)
                        v.append(self.ordereddict_to_dict(item))
        return dict(value)

    @property
    def files(self):
        """ List of values files from the course chart """
        return dict(self._chart.get('files', []))

    @property
    def namespace(self):
        """ Namespace to install the course chart """
        return self._namespace

    @property
    def repository(self):
        """ Repository object parsed from course chart """
        return self._repository

    def __getattr__(self, key):
        return self._chart.get(key)

    def __str__(self):
        return str(dict(self._chart))

    def run_hook(self, coms):
        """ Expects a list of shell commands. Runs the commands defined by the hook """
        if type(coms) == str:
            coms = [coms]

        for com in coms:
            logging.debug("Running Hook {}".format(com))
            ret = subprocess.call(com, shell=True, executable="/bin/bash")
            if ret != 0:
                logging.error("Hook command `{}` returned non-zero exit code".format(com))
                sys.exit(1)

    def rollback(self):
        """ Rollsback most recent release of the course chart """

        release = [release for release in self.helm.releases.deployed if release.name == self._release_name][0]
        if release:
            release.rollback()

    def update_dependencies(self):
        """ Update the course chart dependencies """
        if self.config.local_development or self.config.dryrun:
            return True
        logging.debug("Updating chart dependencies: {}".format(self.chart_path))
        if os.path.exists(self.chart_path):
            try:
                r = self.helm.dependency_update(self.chart_path)
            except AutoHelmCommandException, e:
                logging.warn("Unable to update chart dependancies: {}".format(e.stderr))