← Back to team overview

cf-charmers team mailing list archive

Re: [Merge] lp:~whitmo/charms/trusty/cloudfoundry/cf-disco-check into lp:~cf-charmers/charms/trusty/cloudfoundry/trunk

 


Diff comments:

> === modified file '.bzrignore'
> --- .bzrignore	2014-11-13 16:22:27 +0000
> +++ .bzrignore	2014-12-12 19:15:16 +0000
> @@ -47,3 +47,7 @@
>  # Generated test charms
>  cloudfoundry-r*
>  output-v*.yaml
> +
> +# Generated when running CATs
> +src/
> +pkg/
> 
> === modified file 'cfdeploy'
> --- cfdeploy	2014-12-03 23:14:23 +0000
> +++ cfdeploy	2014-12-12 19:15:16 +0000
> @@ -35,7 +35,7 @@
>          try:
>              subprocess.check_output(
>                  ['sudo', 'apt-get', 'install', '-y', '-q', 'python-virtualenv'],
> -                stderr=subprocess.STDOUT)
> +                stderr=subprocess.fSTDOUT)

This looks like a typo.

>          except subprocess.CalledProcessError as e:
>              print e.output
>              raise
> @@ -77,6 +77,7 @@
>      parser.add_argument('-l', '--log', action='store_true',
>                          help='Write debug log to cfdeploy.log')
>      parser.add_argument('-c', '--constraints')
> +    parser.add_argument('--to')
>      parser.add_argument('-g', '--generate', action='store_true')
>      parser.add_argument('admin_password')
>  
> @@ -152,6 +153,7 @@
>      bar.next(message='Deploying Orchestrator')
>      wait_for(30, 5, partial(deploy,
>               constraints=options.constraints,
> +             to=options.to,
>               generate_dependents=options.generate,
>               admin_password=options.admin_password))
>      until(lambda: socket_open(reconciler_endpoint(), 8888),
> 
> === modified file 'charmgen/generator.py'
> --- charmgen/generator.py	2014-11-07 21:50:48 +0000
> +++ charmgen/generator.py	2014-12-12 19:15:16 +0000
> @@ -227,6 +227,10 @@
>              if zone:
>                  driver = zone[0]
>                  for follower in zone[1:]:
> +                    if follower not in services:
> +                        # allow dangling references in placement to handle
> +                        # removed services
> +                        continue
>                      with_references.add(driver)
>                      if 'to' not in services[follower]:
>                          services[follower]['to'] = [driver]
> 
> === modified file 'charmgen/placements.yaml'
> --- charmgen/placements.yaml	2014-11-11 03:52:11 +0000
> +++ charmgen/placements.yaml	2014-12-12 19:15:16 +0000
> @@ -5,7 +5,7 @@
>          b: [router, nats, nats-sf]
>          c: [login, uaa, mysql]
>          d: [dea, hm, etcd]
> -        e: [loggregator, loggregator-trafficcontrol]
> +        e: [loggregator-trafficcontrol, loggregator, doppler]
>          f: [haproxy, collector]
>  local:
>      __default__:
> 
> === modified file 'cloudfoundry/contexts.py'
> --- cloudfoundry/contexts.py	2014-11-11 04:02:53 +0000
> +++ cloudfoundry/contexts.py	2014-12-12 19:15:16 +0000
> @@ -226,7 +226,7 @@
>  
>  class LoginRelation(RelationContext):
>      name = 'login'
> -    interface = 'http'
> +    interface = 'login'
>      required_keys = []
>      port = 8080
>  
> @@ -239,6 +239,8 @@
>          data = self[self.name][0]
>          return {
>              'login.port': data['port'],
> +            'login.saml.socket.connectionManagerTimeout': 10000,
> +            'login.saml.socket.soTimeout': 10000,
>          }
>  
>  
> @@ -250,6 +252,11 @@
>      def erb_mapping(self):
>          return {
>              'dea_next.directory_server_protocol': 'http',
> +            # these are the default values, but since they're used in hm and
> +            # cc and those specs don't give the default value (only dea_next),
> +            # we have to manually specify them here
> +            'dea_next.advertise_interval_in_seconds': 5,
> +            'dea_next.heartbeat_interval_in_seconds': 10,
>          }
>  
>  
> @@ -296,8 +303,6 @@
>              'traffic_controller.host': data[0]['host'],
>              'traffic_controller.incoming_port': data[0]['port'],
>              'traffic_controller.outgoing_port': data[0]['outgoing_port'],
> -            'logger_endpoint.use_ssl': False,  # TODO: support SSL option
> -            'logger_endpoint.port': 80,  # default is 443
>          }
>  
>  
> @@ -328,6 +333,48 @@
>          }
>  
>  
> +class DopplerRelation(RelationContext):
> +    name = 'doppler'
> +    interface = 'doppler'
> +    required_keys = ['address', 'incoming_port', 'outgoing_port',
> +                     'dropsonde_port', 'doppler_endpoint_secret']
> +    incoming_port = 3457
> +    outgoing_port = 8083
> +    dropsonde_port = 3460
> +
> +    def get_shared_secrets(self):
> +        secret_context = StoredContext('doppler-secrets.yml', {
> +            'doppler_endpoint_secret': host.pwgen(20),
> +        })
> +        return secret_context
> +
> +    def provide_data(self):
> +        secrets = self.get_shared_secrets()
> +        return {
> +            'address': hookenv.unit_get('private-address').encode('utf-8'),
> +            'incoming_port': self.incoming_port,
> +            'outgoing_port': self.outgoing_port,
> +            'dropsonde_port': self.dropsonde_port,
> +            'doppler_endpoint_secret': secrets['doppler_endpoint_secret']
> +        }
> +
> +    def erb_mapping(self):
> +        data = self[self.name]
> +        return {
> +            'doppler.zone': 'z1',
> +            'doppler.blacklisted_syslog_ranges': [],
> +            'doppler.incoming_port': data[0]['incoming_port'],
> +            'doppler.outgoing_port': data[0]['outgoing_port'],
> +            'doppler.dropsonde_incoming_port': data[0]['dropsonde_port'],
> +            'doppler_endpoint.shared_secret': data[0]['doppler_endpoint_secret'],
> +            'loggregator.doppler_port': data[0]['outgoing_port'],
> +            'loggregator.incoming_port': data[0]['incoming_port'],
> +            'loggregator.dropsonde_incoming_port': data[0]['dropsonde_port'],
> +            'loggregator.dropsonde_outgoing_port': data[0]['outgoing_port'],
> +            'loggregator_endpoint.shared_secret': data[0]['doppler_endpoint_secret'],
> +        }
> +
> +
>  class EtcdRelation(RelationContext):
>      name = 'etcd'
>      interface = 'etcd'
> @@ -343,13 +390,15 @@
>  class CloudControllerRelation(RelationContext):
>      name = 'cc'
>      interface = 'controller'
> -    required_keys = ['hostname', 'port', 'user', 'password', 'db_encryption_key']
> +    required_keys = ['hostname', 'port', 'user', 'password',
> +                     'internal_api_password', 'db_encryption_key']
>  
>      def get_credentials(self):
>          return StoredContext('api_credentials.yml', {
>              'user': host.pwgen(7),
>              'password': host.pwgen(7),
>              'db_encryption_key': host.pwgen(7),
> +            'internal_api_password': host.pwgen(7),
>          })
>  
>      def provide_data(self):
> @@ -357,6 +406,7 @@
>          return {
>              'user': creds['user'],
>              'password': creds['password'],
> +            'internal_api_password': creds['internal_api_password'],
>              'db_encryption_key': creds['db_encryption_key'],
>              'hostname': hookenv.unit_get('private-address').encode('utf-8'),
>              'port': 9022,
> @@ -369,6 +419,7 @@
>              'cc.srv_api_uri': 'http://{}:{}'.format(data['hostname'], data['port']),
>              'cc.bulk_api_user': data['user'],
>              'cc.bulk_api_password': data['password'],
> +            'cc.internal_api_password': data['internal_api_password'],
>              'cc.staging_upload_user': 'ignored',  # FIXME: We need a staging cache set up
>              'cc.staging_upload_password': 'ignored',
>              'cc.db_encryption_key': data['db_encryption_key'],
> @@ -439,7 +490,7 @@
>  
>  class RouterRelation(RelationContext):
>      name = 'router'
> -    interface = 'http'
> +    interface = 'router'
>      required_keys = ['address']
>      port = 8000
>      varz_port = 8084  # not currently used
> @@ -545,6 +596,8 @@
>              'app_domains': [d['domain'] for d in self[self.name]],
>              'system_domain': domain,  # TODO: These should probably be config options
>              'system_domain_organization': 'juju-org',
> +            'logger_endpoint.use_ssl': False,  # TODO: support SSL option
> +            'logger_endpoint.port': 80,  # default is 443
>          }
>  
>  
> 
> === modified file 'cloudfoundry/health_checks.py'
> --- cloudfoundry/health_checks.py	2014-11-20 15:50:36 +0000
> +++ cloudfoundry/health_checks.py	2014-12-12 19:15:16 +0000
> @@ -36,3 +36,14 @@
>          return result
>      else:
>          return dict(result, health='warn', message='Working (%s)' % status['status'])
> +
> +
> +def healthz_check(ctype, service, healthz=tasks.healthz, varz=tasks.varz):
> +    health = healthz(ctype) and 'pass' or 'fail'
> +    result = {
> +        'name': 'healthz',
> +        'health': health,
> +        'message': None,
> +        'data': varz(ctype),
> +    }
> +    return result
> 
> === modified file 'cloudfoundry/jobs.py'
> --- cloudfoundry/jobs.py	2014-11-20 15:50:36 +0000
> +++ cloudfoundry/jobs.py	2014-12-12 19:15:16 +0000
> @@ -2,6 +2,7 @@
>  from functools import partial
>  from .path import path
>  import yaml
> +import itertools
>  
>  from charmhelpers.core import hookenv
>  from charmhelpers.core import services
> @@ -68,7 +69,13 @@
>      service_def = service_data[charm_name]
>      results = []
>      health = 'pass'
> -    checks = service_def.get('health', []) + [health_checks.status]
> +    job_health = [job['health'] for job in service_def['jobs'] \
> +                  if 'health' in job]
> +    base_checks = service_def.get('health', [])
> +    checks = itertools.chain(base_checks,
> +                             itertools.chain(*job_health),
> +                             [health_checks.status])
> +
>      for health_check in checks:
>          result = health_check(service_def)
>          if result['health'] == 'fail':
> 
> === modified file 'cloudfoundry/releases.py'
> --- cloudfoundry/releases.py	2014-11-05 18:22:07 +0000
> +++ cloudfoundry/releases.py	2014-12-12 19:15:16 +0000
> @@ -8,8 +8,6 @@
>      ('uaa-v1', 'uaa'),
>      ('login-v1', 'login'),
>      ('nats-stream-forwarder-v1', 'nats-sf'),
> -    ('loggregator-v1', 'loggregator'),
> -    ('hm9000-v1', 'hm'),
>      ('haproxy-v1', 'haproxy'),
>      ('collector-v1', 'collector'),
>  
> @@ -25,15 +23,49 @@
>      ('mysql:db', 'cc:db'),
>      ('mysql:db', 'uaa:db'),
>      ('etcd:client', 'hm:etcd'),
> -    ('etcd:client', 'loggregator:etcd'),
>      ('etcd:client', 'loggregator-trafficcontrol:etcd'),
>  ]
>  
> +UTILITIES = {
> +    "cf-disco": "cf-disco-v0.0.1-538768711c7545d87e60ad79ec3568d8c77c7db6"
> +}
> +
>  COMMON_UPGRADES = []
>  
> -
>  RELEASES = [
>      {
> +        "releases": (190, 190),
> +        "topology": {
> +            "services": COMMON_SERVICES + [
> +                ('router-v3', 'router'),
> +                ('cloud-controller-v3', 'cc'),
> +                ('cloud-controller-clock-v3', 'cc-clock'),
> +                ('cloud-controller-worker-v3', 'cc-worker'),
> +                ('dea-v3', 'dea'),
> +                ('loggregator-trafficcontroller-v3', 'loggregator-trafficcontrol'),
> +                ('hm9000-v2', 'hm'),
> +                ('doppler-v1', 'doppler'),
> +            ],
> +            "relations": COMMON_RELATIONS + [
> +                ('etcd:client', 'cc:etcd'),
> +                ('etcd:client', 'cc-worker:etcd'),
> +                ('etcd:client', 'cc-clock:etcd'),
> +                ('etcd:client', 'router:etcd'),
> +                ('etcd:client', 'dea:etcd'),
> +                ('etcd:client', 'doppler:etcd'),
> +            ],
> +            "expose": ['haproxy'],
> +            "constraints": {
> +                "__default__": "arch=amd64 instance-type=m3.medium",
> +                "cc": "arch=amd64 root-disk=12G mem=12G",
> +                "cc-worker": "arch=amd64 root-disk=10G",
> +                "cc-clock": "arch=amd64 root-disk=10G",
> +                "dea": "arch=amd64 mem=5G",
> +            },
> +        },
> +        "upgrades": COMMON_UPGRADES
> +    },
> +    {
>          "releases": (177, 180),
>          "topology": {
>              "services": COMMON_SERVICES + [
> @@ -42,7 +74,9 @@
>                  ('cloud-controller-clock-v2', 'cc-clock'),
>                  ('cloud-controller-worker-v2', 'cc-worker'),
>                  ('dea-v2', 'dea'),
> +                ('loggregator-v1', 'loggregator'),
>                  ('loggregator-trafficcontroller-v2', 'loggregator-trafficcontrol'),
> +                ('hm9000-v1', 'hm'),
>              ],
>              "relations": COMMON_RELATIONS + [
>                  ('etcd:client', 'cc:etcd'),
> @@ -50,6 +84,7 @@
>                  ('etcd:client', 'cc-clock:etcd'),
>                  ('etcd:client', 'router:etcd'),
>                  ('etcd:client', 'dea:etcd'),
> +                ('etcd:client', 'loggregator:etcd'),
>              ],
>              "expose": ['haproxy'],
>              "constraints": {
> @@ -71,9 +106,13 @@
>                  ('cloud-controller-clock-v1', 'cc-clock'),
>                  ('cloud-controller-worker-v1', 'cc-worker'),
>                  ('dea-v1', 'dea'),
> +                ('loggregator-v1', 'loggregator'),
>                  ('loggregator-trafficcontroller-v1', 'loggregator-trafficcontrol'),
> -            ],
> -            "relations": COMMON_RELATIONS,
> +                ('hm9000-v1', 'hm'),
> +            ],
> +            "relations": COMMON_RELATIONS + [
> +                ('etcd:client', 'loggregator:etcd'),
> +            ],
>              "expose": ['haproxy'],
>              "constraints": {
>                  "__default__": "arch=amd64",
> 
> === modified file 'cloudfoundry/services.py'
> --- cloudfoundry/services.py	2014-11-07 21:18:42 +0000
> +++ cloudfoundry/services.py	2014-12-12 19:15:16 +0000
> @@ -3,6 +3,10 @@
>  import tasks
>  import utils
>  
> +from functools import partial
> +from .health_checks import healthz_check
> +
> +
>  __all__ = ['SERVICES']
>  
>  
> @@ -49,6 +53,27 @@
>  
>      },
>  
> +    'cloud-controller-clock-v3': {
> +        'summary': "A shared clock",
> +        'description': '',
> +        'jobs': [
> +            {'job_name': 'cloud_controller_clock',
> +             'mapping': {'ccdb': mapper.ccdb},
> +             'provided_data': [],
> +             'required_data': [contexts.NatsRelation,
> +                               contexts.CloudControllerRelation,
> +                               contexts.UAARelation,
> +                               contexts.CloudControllerDBRelation],
> +             },
> +            {'job_name': 'metron_agent',
> +             'required_data': [contexts.LTCRelation,
> +                               contexts.NatsRelation,
> +                               contexts.DopplerRelation,
> +                               contexts.EtcdRelation]},
> +            ],
> +
> +    },
> +
>      'cloud-controller-v1': {
>          'summary': 'CF Cloud Controller, the brains of the operation',
>          'description': '',
> @@ -89,6 +114,29 @@
>          ]
>      },
>  
> +    'cloud-controller-v3': {
> +        'summary': 'CF Cloud Controller, the brains of the operation',
> +        'description': '',
> +        'jobs': [
> +            {'job_name': 'cloud_controller_ng',
> +             'mapping': {'db': mapper.ccdb},
> +             'provided_data': [contexts.CloudControllerRelation,
> +                               contexts.CloudControllerDBRelation],
> +             'required_data': [contexts.NatsRelation,
> +                               contexts.MysqlRelation,
> +                               contexts.UAARelation,
> +                               contexts.DEARelation,
> +                               contexts.CloudControllerRelation.remote_view,
> +                               ],
> +             },
> +            {'job_name': 'metron_agent',
> +             'required_data': [contexts.LTCRelation,
> +                               contexts.NatsRelation,
> +                               contexts.DopplerRelation,
> +                               contexts.EtcdRelation]},
> +        ]
> +    },
> +
>      'cloud-controller-worker-v1': {
>          'summary': "Worker for cc",
>          'description': '',
> @@ -128,6 +176,27 @@
>              ]
>      },
>  
> +    'cloud-controller-worker-v3': {
> +        'summary': "Worker for cc",
> +        'description': '',
> +        'jobs': [
> +            {'job_name': 'cloud_controller_worker',
> +             'mapping': {'ccdb': mapper.ccdb},
> +             'provided_data': [],
> +             'required_data': [contexts.NatsRelation,
> +                               contexts.UAARelation,
> +                               contexts.CloudControllerRelation,
> +                               contexts.CloudControllerDBRelation,
> +                               ],
> +             },
> +            {'job_name': 'metron_agent',
> +             'required_data': [contexts.LTCRelation,
> +                               contexts.NatsRelation,
> +                               contexts.DopplerRelation,
> +                               contexts.EtcdRelation]},
> +            ]
> +    },
> +
>      'dea-v1': {
>          'summary': 'DEA runs CF apps in containers',
>          'description': '',
> @@ -199,6 +268,52 @@
>  
>      },
>  
> +    'dea-v3': {
> +        'summary': 'DEA runs CF apps in containers',
> +        'description': '',
> +        'jobs': [
> +            {
> +                'job_name': 'dea_next',
> +                'mapping': {},
> +                'install': [
> +                    utils.install_linux_image_extra,
> +                    utils.apt_install(['quota']),
> +                    utils.modprobe(['quota_v1', 'quota_v2'])
> +                ],
> +                'provided_data': [contexts.DEARelation],
> +                'required_data': [
> +                    contexts.NatsRelation,
> +                    contexts.LTCRelation,
> +                    contexts.DEARelation.remote_view,
> +                    contexts.RouterRelation,
> +                ],
> +                'data_ready': [
> +                    tasks.install_cfdisco,
> +                    tasks.config_cfdisco,
> +                    # tasks.enable_swapaccounting,
> +                    tasks.patch_dea
> +                ],
> +                'health': [partial(healthz_check, "DEA")]
> +            },
> +            {
> +                'job_name': 'dea_logging_agent',
> +                'mapping': {},
> +                'required_data': [contexts.NatsRelation,
> +                                  contexts.LTCRelation,
> +                                  contexts.EtcdRelation]
> +            },
> +            {'job_name': 'metron_agent',
> +             'data_ready': [tasks.install_cfdisco,
> +                            tasks.config_cfdisco],
> +             'required_data': [contexts.LTCRelation,
> +                               contexts.NatsRelation,
> +                               contexts.DopplerRelation,
> +                               contexts.EtcdRelation],
> +             'health': [partial(healthz_check, "MetronAgent")]},
> +        ]
> +
> +    },
> +
>      'nats-v1': {
>          'service': 'nats',
>          'summary': 'NATS message bus for CF',
> @@ -259,6 +374,25 @@
>  
>      },
>  
> +    'router-v3': {
> +        'service': 'router',
> +        'summary': 'CF Router',
> +        'jobs': [
> +            {'job_name': 'gorouter',
> +             'ports': [contexts.RouterRelation.port],
> +             'mapping': {},
> +             'provided_data': [contexts.RouterRelation],
> +             'required_data': [contexts.NatsRelation,
> +                               contexts.RouterRelation.remote_view]},
> +            {'job_name': 'metron_agent',
> +             'required_data': [contexts.LTCRelation,
> +                               contexts.NatsRelation,
> +                               contexts.DopplerRelation,
> +                               contexts.EtcdRelation]},
> +        ],
> +
> +    },
> +
>      'uaa-v1': {
>          'service': 'uaa',
>          'summary': 'CF Oauth2 for identity management service',
> @@ -303,6 +437,21 @@
>              }]
>          },
>  
> +    'doppler-v1': {
> +        'service': 'doppler',
> +        'summary': 'successor of loggregator',
> +        'description': 'Successor of loggregator.',
> +        'jobs': [{
> +            'job_name': 'doppler',
> +            'mapping': {},
> +            'provided_data': [contexts.DopplerRelation],
> +            'required_data': [contexts.NatsRelation,
> +                              contexts.EtcdRelation,
> +                              contexts.LTCRelation,
> +                              contexts.DopplerRelation.remote_view]
> +            }]
> +        },
> +
>      'loggregator-trafficcontroller-v1': {
>          'service': 'loggregator-trafficcontroller',
>          'summary': 'loggregator-trafficcontroller',
> @@ -337,6 +486,23 @@
>              ]
>          },
>  
> +    'loggregator-trafficcontroller-v3': {
> +        'service': 'loggregator-trafficcontroller',
> +        'summary': 'loggregator-trafficcontroller',
> +        'description': '',
> +        'jobs': [
> +            {'job_name': 'loggregator_trafficcontroller',
> +             'ports': [contexts.LTCRelation.outgoing_port],
> +             'mapping': {},
> +             'provided_data': [contexts.LTCRelation],
> +             'required_data': [contexts.DopplerRelation,
> +                               contexts.LTCRelation.remote_view,
> +                               contexts.NatsRelation,
> +                               contexts.CloudControllerRelation,
> +                               contexts.EtcdRelation]},
> +            ]
> +        },
> +
>      'hm9000-v1': {
>          'service': 'hm9000',
>          'summary': 'health monitor',
> @@ -351,6 +517,21 @@
>              }]
>          },
>  
> +    'hm9000-v2': {
> +        'service': 'hm9000',
> +        'summary': 'health monitor',
> +        'description': '',
> +        'jobs': [{
> +            'job_name': 'hm9000',
> +            'mapping': {},
> +            'provided_data': [],
> +            'required_data': [contexts.NatsRelation,
> +                              contexts.CloudControllerRelation,
> +                              contexts.EtcdRelation,
> +                              contexts.DEARelation]
> +            }]
> +        },
> +
>      'haproxy-v1': {
>          'service': 'haproxy',
>          'summary': 'loadbalance the routers',
> 
> === modified file 'cloudfoundry/tasks.py'
> --- cloudfoundry/tasks.py	2014-10-03 15:47:17 +0000
> +++ cloudfoundry/tasks.py	2014-12-12 19:15:16 +0000
> @@ -1,12 +1,14 @@
> +import json
> +import logging
>  import os
>  import re
> +import shlex
>  import shutil
> +import stat
>  import subprocess
> -import yaml
> -import stat
>  import tempfile
>  import textwrap
> -import logging
> +import yaml
>  from urlparse import urlparse
>  from functools import partial
>  from charmhelpers.core import host
> @@ -16,6 +18,7 @@
>  from cloudfoundry import contexts
>  from cloudfoundry import templating
>  from cloudfoundry import utils
> +from .releases import UTILITIES
>  from .path import path
>  
>  logger = logging.getLogger(__name__)
> @@ -41,7 +44,8 @@
>      enable_monit_http_interface()
>      subprocess.check_call(['gem', 'install', '--no-ri', '--no-rdoc', gem_file])
>      subprocess.check_call([
> -        'pip', 'install', '--use-wheel', '-f', './wheelhouse', '--pre', 'raindance'])
> +        'pip', 'install', '--use-wheel', '-f', './wheelhouse', '--pre',
> +        'raindance'])
>  
>  
>  def install(service_def):
> @@ -99,7 +103,8 @@
>          return
>      from raindance.package import PackageArchive
>      pa = PackageArchive(url)
> -    mirror = pa.build_mirror_section(ARTIFACTS_DIR, SOFTWARE, [(version, ARCH)], [job_name])
> +    mirror = pa.build_mirror_section(ARTIFACTS_DIR,
> +                                     SOFTWARE, [(version, ARCH)], [job_name])
>      for filename in mirror:
>          pass  # just need to iterate to force the (lazy) download
>  
> @@ -203,6 +208,75 @@
>          subprocess.check_call(['patch', '-s', '-F4'], stdin=patch)
>          os.unlink(fn)
>  
> +JUJU_VCAP_BIN = path("/var/vcap/juju/bin")
> +
> +
> +def install_cfdisco(job_name, jujubin=JUJU_VCAP_BIN,
> +                    discov=UTILITIES['cf-disco']):
> +    orch = contexts.OrchestratorRelation()
> +
> +    urlbase = path(orch.get_first('artifacts_url'))
> +    url = urlbase / ('utilities/%s' % discov)
> +    sha1 = discov.rsplit("-", 1)[1]
> +
> +    outfile = jujubin / 'cf-disco'
> +
> +    from raindance.package import PackageArchive
> +    verify = PackageArchive.verify_file
> +
> +    if not (outfile.exists() and verify(outfile, sha1)):
> +        jujubin.makedirs_p()
> +        PackageArchive.wget(url, outfile)
> +        assert PackageArchive.verify_file(outfile, sha1), "Bad sha1 for %s" % outfile
> +        outfile.chmod(755)
> +
> +
> +JUJU_VCAP_ETC = path("/var/vcap/juju/etc")
> +
> +
> +def config_cfdisco(job_name, jujuetc=JUJU_VCAP_ETC):
> +    disco_conf = jujuetc / 'disco.json'
> +    if not disco_conf.exists():
> +        nats = contexts.NatsRelation()
> +        nats_info = nats.erb_mapping()
> +        nats_info = dict((k.replace('nats.', ''), v) for k, v in nats_info.items())
> +        nats_info['addr'] = nats.get_first('address')
> +
> +        # @@ fix to use multiple nats servers
> +        nats_uri_tmpt = "nats://{user}:{password}@{addr}:{port}"
> +        nats_uri = nats_uri_tmpt.format(**nats_info)
> +        privip = hookenv.unit_private_ip()
> +
> +        jujuetc.makedirs_p()
> +        disco_conf.write_text(json.dumps(dict(nats_uri=nats_uri,
> +                                              ip=privip)))
> +
> +
> +def healthz(ctype, exe=JUJU_VCAP_BIN / 'cf-disco',
> +            config=JUJU_VCAP_ETC / 'disco.json'):
> +    conf = json.loads(config.text())
> +    conf['type'] = ctype
> +    tmplt = "{exe} {type} {ip} {nats_uri}"
> +    conf['exe'] = exe
> +    args = shlex.split(tmplt.format(**conf))
> +    try:
> +        return subprocess.check_call(args) == 0
> +    except subprocess.CalledProcessError as e:
> +        if e.returncode == 1:
> +            return False
> +        raise
> +
> +
> +def varz(ctype, exe=JUJU_VCAP_BIN / 'cf-disco',
> +         config=JUJU_VCAP_ETC / 'disco.json'):
> +    conf = json.loads(config.text())
> +    conf['type'] = ctype
> +    tmplt = "{exe} --check='varz' {type} {ip} {nats_uri}"
> +    conf['exe'] = exe
> +    args = shlex.split(tmplt.format(**conf))
> +    out = subprocess.check_output(args)
> +    return json.loads(out)
> +
>  
>  class JobTemplates(services.ManagerCallback):
>      template_base_dir = TEMPLATES_BASE_DIR
> 
> === modified file 'cloudfoundry/utils.py'
> --- cloudfoundry/utils.py	2014-12-03 23:14:23 +0000
> +++ cloudfoundry/utils.py	2014-12-12 19:15:16 +0000
> @@ -23,8 +23,10 @@
>  
>  
>  @contextmanager
> -def cd(directory):
> +def cd(directory, make=False):
>      cwd = os.getcwd()
> +    if not os.path.exists(directory) and make:
> +        os.makedirs(directory)
>      os.chdir(directory)
>      try:
>          yield
> @@ -405,6 +407,7 @@
>  def deploy(**config):
>      status = get_client().status()
>      constraints = config.pop('constraints', None)
> +    to = config.pop('to', None)
>      if 'cloudfoundry' in status['Services']:
>          return True
>      # create an up to date config
> @@ -425,6 +428,10 @@
>              '--repository=%s' % repo_path]
>      if constraints:
>          args.append('--constraints=%s' % constraints)
> +
> +    if to:
> +        args.append('--to=%s' % to)
> +
>      args.append('local:trusty/cloudfoundry')
>      juju = sh.check('juju', throw=False)
>      if juju(*args) != 0:
> 
> === modified file 'reconciler/app.py'
> --- reconciler/app.py	2014-11-20 16:09:27 +0000
> +++ reconciler/app.py	2014-12-12 19:15:16 +0000
> @@ -135,6 +135,8 @@
>          service['health'] = 'warn'
>      else:
>          service['health'] = 'pass'
> +    if service_name == 'cloudfoundry' and db.error:  # XXX don't hard-code
> +        service['health'] = 'fail'
>  
>  
>  def get_current_state():
> 
> === modified file 'reconciler/strategy.py'
> --- reconciler/strategy.py	2014-11-11 18:22:53 +0000
> +++ reconciler/strategy.py	2014-12-12 19:15:16 +0000
> @@ -28,6 +28,7 @@
>          self.strategy = Strategy(self.env)
>          self.history = []
>          self.exec_lock = threading.Lock()
> +        self.error = False
>  
>      def reset(self):
>          self.expected = {}
> @@ -106,8 +107,12 @@
>              return []
>  
>          # Service Deltas
> -        self.strategy.extend(self.build_services())
> -        self.strategy.extend(self.build_relations())
> +        try:
> +            self.strategy.extend(self.build_services())
> +            self.strategy.extend(self.build_relations())
> +        except Exception as e:
> +            self.error = True
> +            logging.error('Error building strategy: %s', e)
>  
>      def _changed(self):
>          previous = hashlib.md5(json.dumps(self.previous or {}, sort_keys=True))
> @@ -205,6 +210,10 @@
>                      self.execute_strategy)
>              else:
>                  self._reset_strategy()
> +            self.error = False
> +        except Exception as e:
> +            self.error = True
> +            logging.error('Error executing strategy: %s', e)
>          finally:
>              self.exec_lock.release()
>  
> 
> === modified file 'tests/02-cats'
> --- tests/02-cats	2014-10-01 18:50:57 +0000
> +++ tests/02-cats	2014-12-12 19:15:16 +0000
> @@ -1,3 +1,3 @@
>  #!/bin/bash
>  
> -python tests/cats.py
> +python tests/cats.py "$@"
> 
> === modified file 'tests/cats.py'
> --- tests/cats.py	2014-10-16 15:01:08 +0000
> +++ tests/cats.py	2014-12-12 19:15:16 +0000
> @@ -6,6 +6,7 @@
>  import subprocess
>  import sys
>  import tempfile
> +from functools import partial
>  
>  from cloudfoundry.releases import RELEASES
>  from cloudfoundry.utils import (api,
> @@ -26,6 +27,7 @@
>      global options
>      parser = argparse.ArgumentParser()
>      parser.add_argument('-v', '--version', default="latest")
> +    parser.add_argument('admin_password', default="password", nargs='?')
>      options = parser.parse_args()
>      if options.version == 'latest':
>          options.version = RELEASES[0]['releases'][1]
> @@ -50,10 +52,11 @@
>  
>  def get_cats(version):
>      logging.info("Getting CATs from github")
> -    if not os.path.exists('cf-acceptance-tests'):
> -        sh.git('clone',
> -               'https://github.com/cloudfoundry/cf-acceptance-tests.git')
> -    with cd('cf-acceptance-tests'):
> +    if not os.path.exists('src/github.com/cloudfoundry/cf-acceptance-tests'):
> +        with cd('src/github.com/cloudfoundry', make=True):
> +            sh.git('clone',
> +                   'https://github.com/cloudfoundry/cf-acceptance-tests.git')
> +    with cd('src/github.com/cloudfoundry/cf-acceptance-tests'):
>          sh.git('fetch')
>          sh.check('./bin/compile')
>          # Switch to the branch of the revision in question
> @@ -66,12 +69,13 @@
>          sha = acceptance[0]['sha']
>          print "Switching CATs to {} to test version {}".format(
>              sha[:6], version)
> -        sh.git('reset', '--hard', sha)
> -
> -
> -def run_cats():
> +        sh.git('checkout', sha)
> +
> +
> +def run_cats(options):
>      ep = endpoint()
> -    with cd('cf-acceptance-tests'):
> +    gopath = os.getcwd()
> +    with cd('src/github.com/cloudfoundry/cf-acceptance-tests'):
>          fd, fn = tempfile.mkstemp(suffix=".json")
>          os.close(fd)
>          with open(fn, 'w') as fp:
> @@ -81,8 +85,8 @@
>                  "api": api(),
>                  "syslog_drain_port": 8082,
>                  "syslog_ip_address": ep,
> -                "password": "admin",
> -                "admin_password": "admin",
> +                "password": options.admin_password,
> +                "admin_password": options.admin_password,
>                  "user": "admin",
>                  "admin_user": "admin",
>                  "org": "juju-org",
> @@ -94,7 +98,10 @@
>              json.dump(defaults, fp, indent=2, sort_keys=True)
>          logging.info("Running CATs %s", defaults)
>          env = os.environ.copy()
> -        env.update({'CONFIG': os.path.abspath(fn)})
> +        env.update({
> +            'CONFIG': os.path.abspath(fn),
> +            'GOPATH': gopath,
> +        })
>          subprocess.call('./bin/test', shell=True, env=env)
>  
>  
> @@ -103,9 +110,11 @@
>      logging.basicConfig(level=logging.INFO)
>      get_cats(options.version)
>      bootstrap()
> -    deploy()
> -    wait_for(60 * 40, 30, cf_service, endpoint, login)
> -    run_cats()
> +    deploy(generate_dependents=True,
> +           admin_password=options.admin_password)
> +    wait_for(60 * 40, 30,
> +             cf_service, endpoint, partial(login, options.admin_password))
> +    run_cats(options)
>      sys.exit(0)
>  
>  
> 
> === modified file 'tests/test_tasks.py'
> --- tests/test_tasks.py	2014-10-03 15:47:17 +0000
> +++ tests/test_tasks.py	2014-12-12 19:15:16 +0000
> @@ -1,11 +1,20 @@
>  import unittest
>  import mock
> +import contextlib
> +import tempfile
> +import json
>  
> +from subprocess import CalledProcessError
>  from charmhelpers.core import services
>  from cloudfoundry.path import path
>  from cloudfoundry import tasks
>  
>  
> +def patch_set(*methods):
> +    patches = [mock.patch(x) for x in methods]
> +    return contextlib.nested(*patches)
> +
> +
>  class TestTasks(unittest.TestCase):
>      def setUp(self):
>          self.charm_dir_patch = mock.patch(
> @@ -161,3 +170,84 @@
>          output = tasks._enable_swapaccounting(sample)
>          self.assertIn("arg1 cgroup_enable=memory swapaccount=1", output)
>          self.assertIn("recovery arg2 cgroup_enable=memory swapaccount=1", output)
> +
> +    def test_cfdisco_install(self):
> +        patches = ['cloudfoundry.contexts.OrchestratorRelation',
> +                   'raindance.package.PackageArchive.wget',
> +                   'raindance.package.PackageArchive.verify_file']
> +
> +        bin_d = path(tempfile.mkdtemp(prefix='cf-tests-'))
> +        with patch_set(*patches) as (om, wm, vm):
> +            om().get_first.return_value = 'http://pkg/url'
> +            vm.return_value = True
> +            tasks.install_cfdisco("some_job",
> +                                  jujubin=bin_d,
> +                                  discov="bin-version")
> +            assert wm.called
> +            expect = mock.call(path(u'http://pkg/url/utilities/bin-version'),
> +                               bin_d / 'cf-disco')
> +            assert wm.call_args == expect
> +            assert vm.called
> +
> +    def test_config_cfdisco(self):
> +        patches = ('cloudfoundry.contexts.NatsRelation',
> +                   'charmhelpers.core.hookenv.unit_private_ip')
> +        with patch_set(*patches) as (cxt, ipm):
> +            cxt().get_first.return_value = "10.0.0.1"
> +            cxt().erb_mapping.return_value = {
> +                'nats.user': 'user',
> +                'nats.password': 'pw',
> +                'nats.port': "1234"
> +                }
> +            local_ip = ipm.return_value = "10.0.0.10"
> +            etc = path(tempfile.mkdtemp(prefix='cf-tests-'))
> +            result = etc / 'disco.json'
> +            tasks.config_cfdisco("JOB", etc)
> +            assert result.exists()
> +            outval = json.loads(result.text())
> +            assert outval['ip'] == local_ip
> +            assert outval['nats_uri'] == 'nats://user:pw@10.0.0.1:1234'
> +
> +    def test_check_healthz_ok(self):
> +        config = path(tempfile.mkstemp()[1])
> +        conf_txt = json.dumps(dict(type='COMP',
> +                                   ip='10.0.0.1',
> +                                   nats_uri='NATS_URI'))
> +        config.write_text(conf_txt)
> +        with mock.patch('subprocess.check_call') as cm:
> +            cm.return_value = 0
> +            assert tasks.healthz('CF-THANG', exe='cd', config=config) is True
> +
> +    def test_check_healthz_not_ok(self):
> +        config = path(tempfile.mkstemp()[1])
> +        conf_txt = json.dumps(dict(type='COMP',
> +                                   ip='10.0.0.1',
> +                                   nats_uri='NATS_URI'))
> +
> +        config.write_text(conf_txt)
> +        with mock.patch('subprocess.check_call') as cm:
> +            cm.side_effect = CalledProcessError(1, "CMD")
> +            assert tasks.healthz('CF-THANG', exe='cd', config=config) is False
> +
> +    def test_healthz_raises(self):
> +        config = path(tempfile.mkstemp()[1])
> +        conf_txt = json.dumps(dict(type='COMP',
> +                                   ip='10.0.0.1',
> +                                   nats_uri='NATS_URI'))
> +        config.write_text(conf_txt)
> +        with mock.patch('subprocess.check_call') as cm:
> +            cm.side_effect = CalledProcessError(2, "CMD")
> +            with self.assertRaises(CalledProcessError):
> +                tasks.healthz('CF-THANG', exe='cd', config=config)
> +
> +    def test_check_varz(self):
> +        config = path(tempfile.mkstemp()[1])
> +        conf_txt = json.dumps(dict(type='COMP',
> +                                   ip='10.0.0.1',
> +                                   nats_uri='NATS_URI'))
> +
> +        config.write_text(conf_txt)
> +        with mock.patch('subprocess.check_output') as co:
> +            co.return_value = "{}"
> +            out = tasks.varz('CF-THANG', "exe", config)
> +            assert out == {}
> 


-- 
https://code.launchpad.net/~whitmo/charms/trusty/cloudfoundry/cf-disco-check/+merge/244644
Your team Cloud Foundry Charmers is subscribed to branch lp:~cf-charmers/charms/trusty/cloudfoundry/trunk.


References