Ejemplo n.º 1
0
def test_db(request, app):
    def check_service_host(uri):
        """只能在本地或者容器里跑测试"""
        u = urlparse(uri)
        return u.hostname in (
            'localhost', '127.0.0.1') or 'hub.ricebook.net__ci__' in u.hostname

    if not (check_service_host(app.config['SQLALCHEMY_DATABASE_URI'])
            and check_service_host(app.config['REDIS_URL'])):
        raise Exception('Need to run test on localhost or in container')

    db.create_all()
    app = App.get_or_create(default_appname, git=default_git)
    app.add_env_set(default_env_name, default_env)
    Release.create(app, default_sha, make_specs_text())
    Combo.create(default_appname,
                 default_combo_name,
                 'web',
                 default_podname,
                 networks=[default_network_name],
                 cpu_quota=default_cpu_quota,
                 memory=default_memory,
                 count=1,
                 envname=default_env_name)

    def teardown():
        db.session.remove()
        db.drop_all()
        rds.flushdb()

    request.addfinalizer(teardown)
Ejemplo n.º 2
0
    def fake_release(port=None, erection_timeout=120):
        '''
        start new container using current specs might cause port conflict if
        using host network mode, must modify port before renew container
        '''
        sha = fake_sha(40)
        if port:
            entrypoints = {
                'web': {
                    'cmd': 'python -m http.server --bind 0.0.0.0 {}'.format(port),
                    'ports': [str(port)],
                    'healthcheck': {
                        'http_url': '/{}'.format(artifact_filename),
                        'http_port': int(port),
                        'http_code': 200,
                    },
                }
            }
        else:
            entrypoints = default_entrypoints

        app = App.get_or_create(default_appname)
        r = Release.create(app, sha, make_specs_text(entrypoints=entrypoints,
                                                     erection_timeout=erection_timeout))
        r.update_image(test_app_image)
        return sha
Ejemplo n.º 3
0
def trigger_tackle_routine(self):
    """
    gather all apps that has tackle rule defined, and check each rule to
    decide what strategy to apply (async)
    should only run within celery worker
    """
    apps = App.get_apps_with_tackle_rule()
    for app in apps:
        tackle_single_app.delay(app.name)
Ejemplo n.º 4
0
def deal_with_agent_etcd_change(self, key, deploy_info):
    container_id = deploy_info['ID']
    healthy = deploy_info['Healthy']
    appname = deploy_info['Name']
    app = App.get_by_name(appname)
    container = Container.get_by_container_id(container_id)
    if not container:
        return
    previous_deploy_info = container.deploy_info
    container.update_deploy_info(deploy_info)

    # TODO: use new ELB lib
    # 只要是健康, 无论如何也做一次 ELB 更新, 一方面是反正不贵,
    # 另一方面如果之前哪里出错了没更新成功, 下一次更新还有可能修好
    if healthy:
        logger.info('ELB: ADD [%s, %s, %s, %s, %s]', container.appname, container.podname, container.entrypoint_name, container_id, container.publish)
        update_elb_for_containers(container)
    else:
        logger.info('ELB: REMOVE [%s, %s, %s, %s, %s]', container.appname, container.podname, container.entrypoint_name, container_id, container.publish)
        update_elb_for_containers(container, UpdateELBAction.REMOVE)

    # 处理完了 ELB, 再根据前后状态决定要发什么报警信息
    subscribers = app.subscribers
    msg = ''

    # TODO: acquire exit-code
    # TODO: handle cronjob containers
    previous_healthy = previous_deploy_info.get('Healthy')
    if healthy:
        # 健康万岁
        if previous_healthy is None:
            # 容器第一次健康, 说明刚刚初始化好, 就不需要报警了, mark 一下就好
            container.mark_initialized()
        elif previous_healthy is False:
            # 容器病好了, 要汇报好消息, 但是如果是第一次病好,
            # 那说明只是初始化成功, 这种情况就没必要报警了,
            # 每个容器都会经历一次, 只需要 mark 一下就好
            if container.initialized:
                msg = 'Container resurge: {}'.format(container)
            else:
                container.mark_initialized()
        else:
            # 之前也健康, 那就不用管了
            pass
    else:
        # 生病了
        if previous_healthy is None:
            # 说明刚刚初始化好, 这时候不健康也是正常的, 可以忽略
            pass
        elif previous_healthy is False:
            # 之前就不健康, 那说明已经发过报警了, 就不要骚扰用户了
            pass
        else:
            # 之前是健康的, 现在病了, 当然要报警
            msg = 'Container sick: {}'.format(container)

    notbot_sendmsg(subscribers, msg)
Ejemplo n.º 5
0
def _get_app(appname):
    app = App.get_by_name(appname)
    if not app:
        abort(404, 'App not found: {}'.format(appname))

    if not g.user.granted_to_app(app):
        abort(
            403,
            'You\'re not granted to this app, ask administrators for permission'
        )

    return app
Ejemplo n.º 6
0
def tackle_single_app(appname):
    app = App.get_by_name(appname)
    rule = app.tackle_rule
    app_status_assembler = app.app_status_assembler
    # check container status
    for rule in rule.get('container_tackle_rule', []):
        for c in app_status_assembler.container_status:
            dangers = c.eval_expressions(rule['situations'])
            if dangers:
                method = container_tackle_strategy_lib[rule['strategy']]
                logger.warn('%s container %s in DANGER: %s, tackle strategy %s', appname, c, dangers, method)
                method(c, dangers, **rule.get('kwargs', {}))
Ejemplo n.º 7
0
def renew_container(self, old_container_id, sha=None, user_id=None):
    """renew one container to a certain version, if no version provided, just
    renew this container"""
    old_container = Container.get_by_container_id(old_container_id)
    appname = old_container.appname
    app = App.get_by_name(appname)
    if sha:
        release = app.get_release(sha)
    else:
        sha = old_container.sha
        release = old_container.release

    task_id = self.request.id

    # if erection_timeout == 0, there'll be no smooth renewal, but remove the
    # old container first, and then start new container
    if not release.specs.erection_timeout:
        remove_container_message = remove_container(old_container_id, task_id=task_id)[0]
        if not remove_container_message.success:
            raise ActionError('Remove old container {} failed'.format(old_container))
        create_container_message = create_container(old_container.zone,
                                                    user_id,
                                                    appname,
                                                    sha,
                                                    old_container.combo_name,
                                                    task_id=task_id)[0]
        if not create_container_message.success:
            raise ActionError('Create new container failed: {}'.format(create_container_message.error))
    else:
        create_container_message = create_container(old_container.zone,
                                                    user_id,
                                                    appname,
                                                    sha,
                                                    old_container.combo_name,
                                                    task_id=task_id)[0]
        if not create_container_message.success:
            raise ActionError('Create new container failed: {}'.format(create_container_message.error))
        container_id = create_container_message.id
        container = Container.get_by_container_id(container_id)
        if not container.wait_for_erection():
            remove_container_message = remove_container(container_id, task_id=task_id)[0]
            raise ActionError('New container {} did\'t became healthy, remove result: {}'.format(container, remove_container_message))
        remove_container_message = remove_container(old_container_id, task_id=task_id)[0]
        if not remove_container_message.success:
            raise ActionError('New container {}, but remove old container {} failed'.format(container, old_container_id))

    # reason see the end of create_container definition
    if not task_id:
        return [remove_container_message, create_container_message]
Ejemplo n.º 8
0
def test_permissions(test_db, client):
    FAKE_USER['privileged'] = 0
    user = User.create(**FAKE_USER)

    res = client.get(url_for('app.list_app'))
    assert res.json == []
    res = client.get(url_for('app.get_app', appname=default_appname))
    assert res.status_code == 403

    app = App.get_by_name(default_appname)
    app.grant_user(user)

    res = client.get(url_for('app.list_app'))
    assert len(res.json) == 1
    res = client.get(url_for('app.get_app', appname=default_appname))
    assert res.status_code == 200
Ejemplo n.º 9
0
def register_release(args):
    """Register a release of the specified app

    :<json string appname: required
    :<json string sha: required, must be length 40
    :<json string git: required, the repo address using git protocol, e.g. :code:`[email protected]:projecteru2/citadel.git`
    :<json string specs_text: required, the yaml specs for this app
    :<json string branch: optional git branch
    :<json string git_tag: optional git tag
    :<json string commit_message: optional commit message
    :<json string author: optional author
    """
    appname = args['appname']
    git = args['git']
    sha = args['sha']
    specs_text = args['specs_text']
    branch = args.get('branch')
    git_tag = args.get('git_tag')
    commit_message = args.get('commit_message')
    author = args.get('author')

    app = App.get_or_create(appname, git)
    if not app:
        abort(400,
              'Error during create an app (%s, %s, %s)' % (appname, git, sha))

    try:
        release = Release.create(app,
                                 sha,
                                 specs_text,
                                 branch=branch,
                                 git_tag=git_tag,
                                 author=author,
                                 commit_message=commit_message)
    except (IntegrityError, ValidationError) as e:
        abort(400, str(e))

    if release.raw:
        release.update_image(release.specs.base)

    return release
Ejemplo n.º 10
0
 def list_app(self):
     from citadel.models.app import AppUserRelation, App
     if self.privileged:
         return App.get_all()
     rs = AppUserRelation.query.filter_by(user_id=self.id)
     return [App.get_by_name(r.appname) for r in rs]
Ejemplo n.º 11
0
def renew(socket):
    """Create a new container to substitute the old one, this API can be used
    to upgrade a app to a specified version, or simply re-create a container
    using the same combo.

    Things can go wrong at any step, the example response
    was the output generated by ``renew("1aa8a638a153b393ee423c0a8c158757b13ab74591ade036b6e73ac33a4bdeac", "3641aca")``
    which failed because the newly created container didn't become healthy
    within the given time limit.

    :<json list container_ids: required, a list of container_id
    :<json string sha: required, minimum length is 7

    **Example response**:

    .. sourcecode:: http

        HTTP/1.1 200 OK
        Content-Type: application/json

        {
            "podname": "eru",
            "nodename": "c1-eru-2.ricebook.link",
            "id": "2f123f1abcdfc8208b298c89e10bcd8f48f9fdb25c9eb7874ea5cc7199825e6e",
            "name": "test-app_web_rvrhPg",
            "error": "",
            "success": true,
            "cpu": {"0": 20},
            "quota": 0.2,
            "memory": 134217728,
            "publish": {"bridge": "172.17.0.5:6789"},
            "hook": "hook output",
            "__class__": "CreateContainerMessage"
        }

        {
            "id": "2f123f1abcdfc8208b298c89e10bcd8f48f9fdb25c9eb7874ea5cc7199825e6e",
            "success": true,
            "message": "hook output",
            "__class__": "RemoveContainerMessage"
        }

        {
            "error": "New container <Container test-zone:test-app:3641aca:web:2f123f1 did't became healthy, remove result: id: 2f123f1abcdfc8208b298c89e10bcd8f48f9fdb25c9eb7874ea5cc7199825e6e success: true",
            "args": ["1aa8a638a153b393ee423c0a8c158757b13ab74591ade036b6e73ac33a4bdeac", "3641aca"],
            "kwargs": {"user_id": null}
        }

    """
    payload = None
    while True:
        message = socket.receive()
        try:
            payload = renew_schema.loads(message)
            break
        except ValidationError as e:
            socket.send(json.dumps(e.messages))
        except JSONDecodeError as e:
            socket.send(json.dumps({'error': str(e)}))

    args = payload.data
    containers = [
        Container.get_by_container_id(id_) for id_ in args['container_ids']
    ]
    appnames = {c.appname for c in containers}
    sha = args['sha']
    if len(appnames) > 1 and sha:
        socket.send(
            json.dumps({
                'error':
                'cannot provide sha when renewing containers of multiple apps: {}'
                .format(appnames)
            }))
        socket.close()

    appname = appnames.pop()
    app = App.get_by_name(appname)
    if not app:
        socket.send(json.dumps({'error': 'app {} not found'.format(appname)}))
        socket.close()

    task_ids = []
    for c in containers:
        async_result = renew_container.delay(c.container_id,
                                             sha,
                                             user_id=session.get('user_id'))
        task_ids.append(async_result.task_id)

    for m in celery_task_stream_response(task_ids):
        logger.debug(m)
        socket.send(m)
Ejemplo n.º 12
0
def deploy(socket):
    """Create containers for the specified release

    :<json string appname: required
    :<json string zone: required
    :<json string sha: required, minimum length is 7
    :<json string combo_name: required, specify the combo to use, you can
    update combo value using this API, so all parameters in the
    :http:post:`/api/app/(appname)/combo` are supported

    **Example response**:

    .. sourcecode:: http

        HTTP/1.1 200 OK
        Content-Type: application/json

        {
            "podname": "eru",
            "nodename": "c1-eru-2.ricebook.link",
            "id": "9c91d06cb165e829e8e0ad5d5b5484c47d4596af04122489e4ead677113cccb4",
            "name": "test-app_web_kMqYFQ",
            "error": "",
            "success": true,
            "cpu": {"0": 20},
            "quota": 0.2,
            "memory": 134217728,
            "publish": {"bridge": "172.17.0.5:6789"},
            "hook": "I am the hook output",
            "__class__": "CreateContainerMessage"
        }

    """
    payload = None
    while True:
        message = socket.receive()
        try:
            payload = deploy_schema.loads(message)
            break
        except ValidationError as e:
            socket.send(json.dumps(e.messages))
        except JSONDecodeError as e:
            socket.send(json.dumps({'error': str(e)}))

    args = payload.data
    appname = args['appname']
    app = App.get_by_name(appname)
    if not app:
        socket.send(json.dumps({'error': 'app {} not found'.format(appname)}))
        socket.close()

    combo_name = args['combo_name']
    combo = app.get_combo(combo_name)
    if not combo:
        socket.send(
            json.dumps({
                'error':
                'combo {} for app {} not found'.format(combo_name, app)
            }))
        socket.close()

    combo.update(**{k: v for k, v in args.items() if hasattr(combo, k)})

    async_result = create_container.delay(user_id=session.get('user_id'),
                                          **args)
    for m in celery_task_stream_response(async_result.task_id):
        logger.debug(m)
        socket.send(m)