Exemple #1
0
class IP(Base, IPMixin):

    __tablename__ = 'ip'

    ipnum = db.Column(db.Integer, nullable=False, default=0, unique=True)
    vethname = db.Column(db.String(50), nullable=False, default='')
    network_id = db.Column(db.Integer, db.ForeignKey('network.id'))
    container_id = db.Column(db.Integer, db.ForeignKey('container.id'))

    def __init__(self, ipnum, network):
        self.ipnum = ipnum
        self.network_id = network.id

    @classmethod
    def create(cls, ipnum, network):
        ip = cls(ipnum, network)
        db.session.add(ip)
        db.session.commit()
        return ip

    @classmethod
    def get_by_value(cls, value):
        return cls.query.filter_by(ipnum=value).first()

    @classmethod
    def get_by_container(cls, container_id):
        return cls.query.filter_by(container_id=container_id).all()

    @classmethod
    def delete_by_network(cls, network_id):
        cls.query.filter_by(network_id=network_id).delete()
        db.session.commit()

    def set_vethname(self, vethname):
        self.vethname = vethname
        db.session.add(self)
        db.session.commit()

    def assigned_to_container(self, container):
        if not container:
            return False
        container.ips.append(self)
        db.session.add(container)
        db.session.commit()
        return True

    def release(self):
        self.network.release_ip(self)
        db.session.delete(self)
        db.session.commit()

    def to_dict(self):
        d = super(IP, self).to_dict()
        d.update(
            address=self.address,
            vlan_address=self.vlan_address,
        )
        return d
Exemple #2
0
class VLanGateway(Base, IPMixin):

    __tablename__ = 'vlan_gateway'
    __table_args__ = (db.UniqueConstraint('network_id', 'host_id'), )

    ipnum = db.Column(db.Integer, nullable=False, default=0)
    network_id = db.Column(db.Integer, db.ForeignKey('network.id'))
    host_id = db.Column(db.Integer, db.ForeignKey('host.id'))

    def __init__(self, ipnum, network_id, host_id):
        self.ipnum = ipnum
        self.network_id = network_id
        self.host_id = host_id

    @classmethod
    def create(cls, ipnum, network_id, host_id):
        try:
            vg = cls(ipnum, network_id, host_id)
            db.session.add(vg)
            db.session.commit()
            return vg
        except sqlalchemy.exc.IntegrityError:
            db.session.rollback()
            return None

    @classmethod
    def get_by_host_and_network(cls, host_id, network_id):
        return cls.query.filter_by(network_id=network_id,
                                   host_id=host_id).first()

    @property
    def name(self):
        return 'vlan.%02d.br' % self.vlan_seq_id

    def release(self):
        self.network.release_gateway(self)
        db.session.delete(self)
        db.session.commit()

    def to_dict(self):
        d = super(VLanGateway, self).to_dict()
        d.update(
            address=self.address,
            vlan_address=self.vlan_address,
            name=self.name,
        )
        return d
Exemple #3
0
class Version(Base):
    __tablename__ = 'version'
    __table_args__ = (db.UniqueConstraint('app_id', 'sha'), )

    sha = db.Column(db.CHAR(40), index=True, nullable=False)
    app_id = db.Column(db.Integer, db.ForeignKey('app.id'))
    created = db.Column(db.DateTime, default=datetime.now)

    containers = db.relationship('Container',
                                 backref='version',
                                 lazy='dynamic',
                                 cascade='save-update, merge, delete')
    tasks = db.relationship('Task',
                            backref='version',
                            lazy='dynamic',
                            cascade='save-update, merge, delete')

    def __init__(self, sha, app_id):
        self.sha = sha
        self.app_id = app_id

    @classmethod
    def create(cls, sha, app_id):
        try:
            version = cls(sha, app_id)
            db.session.add(version)
            db.session.commit()
            return version
        except sqlalchemy.exc.IntegrityError:
            db.session.rollback()
            return None

    @classmethod
    def get_by_app_and_version(cls, application, sha):
        return cls.query.filter(cls.sha.like('{}%'.format(sha)),
                                cls.app_id == application.id).one()

    @property
    def name(self):
        return self.app.name

    @cached_property
    def appconfig(self):
        return AppConfig.get_by_name_and_version(self.name, self.short_sha)

    @property
    def short_sha(self):
        return self.sha[:7]

    @property
    def user_id(self):
        return self.app.user_id

    def list_containers(self, start=0, limit=20):
        from .container import Container
        q = self.containers.order_by(Container.id.desc()).offset(start)
        if limit is not None:
            q = q.limit(limit)
        return q.all()

    def list_tasks(self, start=0, limit=20):
        from .task import Task
        q = self.tasks.order_by(Task.id.desc()).offset(start)
        if limit is not None:
            q = q.limit(limit)
        return q.all()

    def get_resource_config(self, env='prod'):
        return ResourceConfig.get_by_name_and_env(self.name, env)

    def get_ports(self, entrypoint):
        entry = self.appconfig.entrypoints.get(entrypoint, {})
        ports = entry.get('ports', [])
        return [int(p.split('/')[0]) for p in ports]

    def get_image(self):
        return Image.get_by_app_and_version(self.app_id, self.id)

    def to_dict(self):
        d = super(Version, self).to_dict()
        d['name'] = self.name
        d['appconfig'] = self.appconfig.to_dict()
        d['image'] = self.get_image()
        return d
Exemple #4
0
class Host(Base, PropsMixin):
    __tablename__ = 'host'

    addr = db.Column(db.CHAR(30), nullable=False, unique=True)
    name = db.Column(db.CHAR(30), nullable=False)
    uid = db.Column(db.CHAR(60), nullable=False)
    ncore = db.Column(db.Integer, nullable=False, default=0)
    mem = db.Column(db.BigInteger, nullable=False, default=0)
    # 现在这个count是指free的core数
    count = db.Column(db.Numeric(12, 3), nullable=False, default=0)
    pod_id = db.Column(db.Integer, db.ForeignKey('pod.id'))
    is_alive = db.Column(db.Boolean, default=True)
    is_public = db.Column(db.Boolean, default=False)

    tasks = db.relationship('Task',
                            backref='host',
                            lazy='dynamic',
                            cascade='save-update, merge, delete')
    containers = db.relationship('Container',
                                 backref='host',
                                 lazy='dynamic',
                                 cascade='save-update, merge, delete')
    vlans = db.relationship('VLanGateway',
                            backref='host',
                            lazy='dynamic',
                            cascade='save-update, merge, delete')

    eips = PropsItem('eips', default=list, type=_ip_address_filter)

    def __init__(self,
                 addr,
                 name,
                 uid,
                 ncore,
                 mem,
                 pod_id,
                 count,
                 is_public=False):
        self.addr = addr
        self.name = name
        self.uid = uid
        self.ncore = ncore
        self.mem = mem
        self.pod_id = pod_id
        self.count = count
        self.is_public = is_public

    def get_uuid(self):
        return '/eru/host/%s' % self.id

    @classmethod
    def create(cls, pod, addr, name, uid, ncore, mem, is_public=False):
        """创建必须挂在一个 pod 下面"""
        if not pod:
            return None

        # 如果已经存在, 就覆盖
        host = cls.get_by_addr(addr)
        if host:
            override = host.containers.count() == 0
            host.uid = uid
            if override:
                host.ncore = ncore
                host.mem = mem
                host.count = ncore

            if is_public:
                host.set_public()
            else:
                db.session.add(host)
                db.session.commit()

            if override:
                _create_cores_on_host(host, ncore)

            return host

        # 不存在就创建
        try:
            host = cls(addr,
                       name,
                       uid,
                       ncore,
                       mem,
                       pod.id,
                       ncore,
                       is_public=is_public)
            db.session.add(host)
            db.session.commit()
            _create_cores_on_host(host, ncore)
            return host
        except sqlalchemy.exc.IntegrityError:
            db.session.rollback()
            return None

    @classmethod
    def get_by_addr(cls, addr):
        return cls.query.filter_by(addr=addr).first()

    @classmethod
    def get_by_name(cls, name):
        return cls.query.filter_by(name=name).first()

    @classmethod
    def get_random_public_host(cls):
        return cls.query.filter(cls.is_public == True, cls.is_alive == True,
                                cls.ncore > 0).limit(1).first()

    @property
    def ip(self):
        return self.addr.split(':', 1)[0]

    @property
    def _cores_key(self):
        return 'eru:host:%s:cores' % self.id

    @property
    def cores(self):
        r = rds.zrange(self._cores_key,
                       0,
                       -1,
                       withscores=True,
                       score_cast_func=int)
        return [Core(name, self.id, value) for name, value in r]

    @property
    def max_share_core(self):
        return self.pod.max_share_core

    @property
    def core_share(self):
        return self.pod.core_share

    def list_containers(self, start=0, limit=20):
        q = self.containers.offset(start)
        if limit is not None:
            q = q.limit(limit)
        return q.all()

    def list_vlans(self, start=0, limit=20):
        return self.vlans[start:start + limit]

    def get_free_cores(self):
        """取可用的core, 返回一个完全可用列表, 以及部分可用列表"""
        slice_count = self.pod.core_share
        # 条件查询 O(log(N)+M) 排除已经用完的 Core
        r = rds.zrangebyscore(self._cores_key,
                              '(0',
                              slice_count,
                              withscores=True,
                              score_cast_func=int)
        full = []
        fragment = []
        for name, value in r:
            c = Core(name, self.id, value)
            if value == slice_count:
                full.append(c)
            elif 0 < value < slice_count:
                fragment.append(c)
        return full, fragment

    def get_max_container_count(self, ncore, nshare=0):
        if nshare and not self.max_share_core:
            return 0
        exclusive_cores, shared_cores = self.get_free_cores()
        exclusive_count, shared_count = len(exclusive_cores), len(shared_cores)
        max_share_core = exclusive_count if self.max_share_core == -1 else self.max_share_core
        if nshare:
            shared_total = sum(fragment.remain / nshare
                               for fragment in shared_cores)
            if ncore == 0:
                return shared_total + (max_share_core -
                                       shared_count) * self.core_share / nshare
            else:
                return max(
                    min((exclusive_count - i) / ncore, shared_total +
                        self.core_share / nshare * i)
                    for i in range(max_share_core - shared_count + 1))
        return exclusive_count / ncore

    @redis_lock('host:alloc_cores:{self.id}')
    def get_container_cores(self, ncontainer, ncore, nshare=0):
        """get as much as possible."""
        max_count = self.get_max_container_count(ncore, nshare)
        total = min(ncontainer, max_count)

        exclusive_cores, shared_cores = self.get_free_cores()
        exclusive_result, shared_result = [], []

        if ncore:
            exclusive_result = exclusive_cores[:total * ncore]

        if nshare:
            for fragment in shared_cores:
                shared_result.extend(fragment
                                     for _ in range(fragment.remain / nshare))

            offset = total * ncore if ncore else 0
            still_need = total - len(shared_result)
            while len(shared_result) < total:
                c = self.core_share / nshare
                shared_result.extend(exclusive_cores[offset]
                                     for _ in range(min(c, still_need)))
                offset += 1
                still_need -= c

        return total, {'full': exclusive_result, 'part': shared_result}

    def get_filtered_containers(self,
                                version=None,
                                entrypoint=None,
                                app=None,
                                start=0,
                                limit=20):
        q = self.containers
        if version is not None:
            q = q.filter_by(version_id=version.id)
        if entrypoint is not None:
            q = q.filter_by(entrypoint=entrypoint)
        if app is not None:
            q = q.filter_by(app_id=app.id)
        return q.offset(start).limit(limit).all()

    def get_containers_by_version(self, version):
        return self.containers.filter_by(version_id=version.id).all()

    def get_containers_by_app(self, app):
        return self.containers.filter_by(app_id=app.id).all()

    def set_public(self):
        self.is_public = True
        db.session.add(self)
        db.session.commit()

    def set_private(self):
        self.is_public = False
        db.session.add(self)
        db.session.commit()

    def occupy_cores(self, cores, nshare):
        slice_count = self.pod.core_share
        for core in cores.get('full', []):
            _pipeline.zincrby(self._cores_key, core.label, -slice_count)
        for core in cores.get('part', []):
            _pipeline.zincrby(self._cores_key, core.label, -nshare)
        _pipeline.execute()

    def release_cores(self, cores, nshare):
        slice_count = self.pod.core_share
        for core in cores.get('full', []):
            _pipeline.zincrby(self._cores_key, core.label, slice_count)
        for core in cores.get('part', []):
            _pipeline.zincrby(self._cores_key, core.label, nshare)
        _pipeline.execute()

    def kill(self):
        self.is_alive = False
        appnames = set()
        for c in self.containers.all():
            c.is_alive = 0
            db.session.add(c)

            remove_container_backends(c)
            appnames.add(c.appname)

        db.session.add(self)
        db.session.commit()

        publish_to_service_discovery(*appnames)

    def cure(self):
        self.is_alive = True
        appnames = set()
        for c in self.containers.all():
            c.is_alive = 1
            db.session.add(c)

            add_container_backends(c)
            appnames.add(c.appname)

        db.session.add(self)
        db.session.commit()

        publish_to_service_discovery(*appnames)

    def bind_eip(self, eip=None):
        eip = ipam.get_eip(eip)
        if eip is None:
            return None

        mask = IPAddress(0xFFFF)
        ids = [eip.value & 0xFFFF]
        broadcasts = [str(eip | mask)]
        ip_list = ['%s/16' % eip]

        agent = get_agent(self)
        if not agent.bind_eip(zip(ip_list, ids, broadcasts)):
            ipam.release_eip(eip)
            return None

        eips = [ip.value for ip in self.eips] + [eip.value]
        self.eips = eips
        return eip

    def release_eip(self, eip=None):
        if eip is not None:
            to_release = [IPAddress(eip)]
        else:
            to_release = self.eips

        eips = [ip for ip in self.eips if ip not in to_release]

        ip_list = [('%s/16' % e, e.value & 0xFFFF) for e in to_release]
        agent = get_agent(self)
        if not agent.unbind_eip(ip_list):
            return

        for eip in to_release:
            ipam.release_eip(eip)

        self.eips = [e.value for e in eips]
Exemple #5
0
class Task(Base, PropsMixin):
    __tablename__ = 'task'

    host_id = db.Column(db.Integer, db.ForeignKey('host.id'))
    app_id = db.Column(db.Integer, db.ForeignKey('app.id'), index=True)
    version_id = db.Column(db.Integer, db.ForeignKey('version.id'), index=True)
    type = db.Column(db.Integer, nullable=False)
    result = db.Column(db.Integer, nullable=True)
    finished = db.Column(db.DateTime, nullable=True)
    created = db.Column(db.DateTime, default=datetime.now)

    reason = PropsItem('reason', default='')
    container_ids = PropsItem('container_ids', default=list)

    def __init__(self, host_id, app_id, version_id, type_):
        self.host_id = host_id
        self.app_id = app_id
        self.version_id = version_id
        self.type = type_

    def get_uuid(self):
        return '/eru/task/%s' % self.id

    @classmethod
    def create(cls, type_, version, host, props={}):
        try:
            task = cls(host.id, version.app_id, version.id, type_)
            db.session.add(task)
            db.session.commit()
            task.set_props(props)
            return task
        except sqlalchemy.exc.IntegrityError:
            db.session.rollback()
            return None

    def finish(self, result):
        self.finished = datetime.now()
        self.result = result
        db.session.add(self)
        db.session.commit()

    @property
    def publish_key(self):
        return ERU_TASK_PUBKEY % self.id

    @property
    def log_key(self):
        return ERU_TASK_LOGKEY % self.id

    @property
    def result_key(self):
        return ERU_TASK_RESULTKEY % self.id 

    def to_dict(self):
        d = super(Task, self).to_dict()
        d.update(
            props=self.props,
            action=TASK_ACTIONS.get(self.type, 'unknown'),
            name=self.app.name,
            version=self.version.short_sha,
            host=self.host.ip,
            reason=self.reason,
        )
        return d
Exemple #6
0
class Container(Base, PropsMixin):
    __tablename__ = 'container'

    host_id = db.Column(db.Integer, db.ForeignKey('host.id'))
    app_id = db.Column(db.Integer, db.ForeignKey('app.id'))
    version_id = db.Column(db.Integer, db.ForeignKey('version.id'))
    container_id = db.Column(db.CHAR(64), nullable=False, index=True)
    name = db.Column(db.CHAR(255), nullable=False)
    entrypoint = db.Column(db.CHAR(255), nullable=False)
    memory = db.Column(db.Integer, nullable=False, default=40960)
    env = db.Column(db.CHAR(255), nullable=False)
    created = db.Column(db.DateTime, default=datetime.now)
    is_alive = db.Column(db.Integer, default=1)

    ips = db.relationship('IP',
                          backref='container',
                          lazy='dynamic',
                          cascade='save-update, merge, delete')

    callback_url = PropsItem('callback_url')
    eip = PropsItem('eip')
    in_removal = PropsItem('in_removal', default=0)

    def __init__(self, container_id, host, version, name, entrypoint, env):
        self.container_id = container_id
        self.host_id = host.id
        self.version_id = version.id
        self.app_id = version.app_id
        self.name = name
        self.entrypoint = entrypoint
        self.env = env

    def get_uuid(self):
        return '/eru/container/%s' % self.id

    @classmethod
    def create(cls,
               container_id,
               host,
               version,
               name,
               entrypoint,
               cores,
               env,
               nshare=0,
               callback_url=''):
        """创建一个容器. cores 是 {'full': [core, ...], 'part': [core, ...]}"""
        try:
            container = cls(container_id, host, version, name, entrypoint, env)
            db.session.add(container)
            host.count = host.__class__.count - \
                    D(len(cores.get('full', []))) - \
                    D(format(D(nshare) / D(host.core_share), '.3f'))
            db.session.add(host)
            db.session.commit()

            cores['nshare'] = nshare
            container.cores = cores
            container.callback_url = callback_url

            container.publish_status('create')
            return container
        except sqlalchemy.exc.IntegrityError:
            db.session.rollback()
            return None

    @classmethod
    def get_multi_by_host(cls, host):
        return cls.query.filter(cls.host_id == host.id).all()

    @classmethod
    def get_by_container_id(cls, cid):
        return cls.query.filter(cls.container_id.like(
            '{}%'.format(cid))).first()

    @classmethod
    def delete_by_container_id(cls, cid):
        cls.query.filter_by(container_id=cid).delete()
        db.session.commit()

    @property
    def appname(self):
        return self.name.rsplit('_', 2)[0]

    @property
    def short_id(self):
        return self.container_id[:7]

    @property
    def short_sha(self):
        return self.version.short_sha

    @property
    def network_mode(self):
        entry = self.get_entry()
        return entry.get('network_mode', 'bridge')

    @property
    def meta(self):
        """一定会加入__version__这个变量, 7位的git sha1值"""
        m = self.version.appconfig.get('meta', {})
        m['__version__'] = self.version.short_sha
        return m

    @property
    def ident_id(self):
        return self.name.rsplit('_', 2)[-1]

    @property
    def _cores_key(self):
        return 'eru:container:%s:cores' % self.id

    def _get_cores(self):
        try:
            return cPickle.loads(rds.get(self._cores_key))
        except (EOFError, TypeError):
            return {}

    def _set_cores(self, cores):
        rds.set(self._cores_key, cPickle.dumps(cores))

    def _del_cores(self):
        rds.delete(self._cores_key)

    cores = property(_get_cores, _set_cores, _del_cores)
    del _get_cores, _set_cores, _del_cores

    @property
    def full_cores(self):
        return self.cores.get('full', [])

    @property
    def part_cores(self):
        return self.cores.get('part', [])

    @property
    def ncore(self):
        return D(len(self.cores.get('full', []))) + D(
            format(D(self.nshare) / D(self.host.core_share), '.3f'))

    @property
    def nshare(self):
        return self.cores.get('nshare', 0)

    def get_entry(self):
        appconfig = self.version.appconfig
        return appconfig.entrypoints.get(self.entrypoint, {})

    def get_ports(self):
        entry = self.get_entry()
        ports = entry.get('ports', [])
        return [int(p.split('/')[0]) for p in ports]

    def get_ips(self):
        if self.network_mode == 'host':
            return [self.host.ip]
        ips = ipam.get_ip_by_container(self.container_id)
        return [str(ip) for ip in ips]

    def get_backends(self):
        """daemon的话是个空列表"""
        ips = self.get_ips()
        ports = self.get_ports()
        return [
            '{0}:{1}'.format(ip, port)
            for ip, port in itertools.product(ips, ports)
        ]

    def delete(self):
        """删除这条记录, 记得要释放自己占用的资源"""
        # release ip
        ipam.release_ip_by_container(self.container_id)
        # release eip
        self.release_eip()

        # release core and increase core count
        host = self.host
        cores = self.cores
        host.release_cores(cores, self.nshare)
        host.count = host.__class__.count + self.ncore
        db.session.add(host)

        # remove property
        del self.cores
        self.destroy_props()

        # remove container
        db.session.delete(self)
        db.session.commit()
        self.publish_status('delete')

    def kill(self):
        self.is_alive = 0
        db.session.add(self)
        db.session.commit()
        self.publish_status('down')

        remove_container_backends(self)
        publish_to_service_discovery(self.appname)

    def cure(self):
        self.is_alive = 1
        db.session.add(self)
        db.session.commit()
        self.publish_status('up')

        add_container_backends(self)
        publish_to_service_discovery(self.appname)

    def callback_report(self, **kwargs):
        """调用创建的时候设置的回调url, 失败就不care了"""
        callback_url = self.props.get('callback_url', '')
        if not callback_url:
            return

        data = self.to_dict()
        data.update(**kwargs)

        try:
            requests.post(callback_url,
                          data=json.dumps(data, cls=EruJSONEncoder),
                          timeout=5,
                          headers={'content-type': 'application/json'})
        except:
            pass

    def publish_status(self, status):
        d = {'container': self.container_id, 'status': status}
        rds.publish(_CONTAINER_PUB_KEY % self.appname, json.dumps(d))

    def to_dict(self):
        d = super(Container, self).to_dict()
        ips = ipam.get_ip_by_container(self.container_id)
        d.update(
            host=self.host.addr.split(':')[0],
            hostname=self.host.name,
            cores={
                'full': [c.label for c in self.full_cores],
                'part': [c.label for c in self.part_cores],
                'nshare': self.nshare,
            },
            version=self.short_sha,
            networks=ips,
            backends=self.get_backends(),
            appname=self.appname,
            eip=self.eip,
            in_removal=self.in_removal,
            short_id=self.short_id,
        )
        return d

    def bind_eip(self, eip=None):
        if self.eip:
            return

        if eip is None:
            for e in self.host.eips:
                if not check_eip_bound(e):
                    eip = e
                    break

        if eip is None:
            return

        agent = get_agent(self.host)
        agent.publish_container(eip, self)
        self.eip = str(eip)
        set_eip_bound(eip, self.container_id)
        return True

    def release_eip(self):
        if not self.eip:
            return
        agent = get_agent(self.host)
        agent.unpublish_container(self.eip, self)
        clean_eip_bound(self.eip)
        del self.eip
        return True