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
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
class Image(Base): __tablename__ = 'image' __table_args__ = (db.UniqueConstraint('app_id', 'version_id'), ) app_id = db.Column(db.Integer) version_id = db.Column(db.Integer) created = db.Column(db.DateTime, default=datetime.now) comment = db.Column(db.String(255), default='') image_url = db.Column(db.String(255)) def __init__(self, app_id, version_id, image_url): self.app_id = app_id self.version_id = version_id self.image_url = image_url @classmethod def create(cls, app_id, version_id, image_url): try: image = cls(app_id, version_id, image_url) db.session.add(image) db.session.commit() return image except sqlalchemy.exc.IntegrityError: pass @classmethod def get_by_app_and_version(cls, app_id, version_id): return cls.query.filter_by(app_id=app_id, version_id=version_id).first() @classmethod def list_by_app_id(cls, app_id, start=0, limit=20): q = cls.query.filter_by(app_id=app_id).order_by(cls.id.desc()) return q[start:start + limit] @property def version(self): from .app import Version return Version.get(self.version_id) @property def app(self): from .app import App return App.get(self.app_id)
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
class App(Base): __tablename__ = 'app' name = db.Column(db.CHAR(32), nullable=False, unique=True) git = db.Column(db.String(255), nullable=False) update = db.Column(db.DateTime, default=datetime.now) _user_id = db.Column(db.Integer, nullable=False, default=0) versions = db.relationship('Version', backref='app', lazy='dynamic', cascade='save-update, merge, delete') containers = db.relationship('Container', backref='app', lazy='dynamic', cascade='save-update, merge, delete') tasks = db.relationship('Task', backref='app', lazy='dynamic', cascade='save-update, merge, delete') def __init__(self, name, git): self.name = name self.git = git @classmethod def get_or_create(cls, name, git): app = cls.query.filter(cls.name == name).first() if app: return app try: app = cls(name, git) db.session.add(app) db.session.commit() return app except sqlalchemy.exc.IntegrityError: db.session.rollback() return None @classmethod def get_by_name(cls, name): return cls.query.filter(cls.name == name).first() @classmethod def list_all(cls, start=0, limit=20): q = cls.query.order_by(cls.name.asc()) return q[start:start + limit] @property def user_id(self): """默认使用id, 如果不对可以通过_user_id手动纠正.""" return self._user_id or self.id def get_version(self, version): return self.versions.filter(Version.sha.like( '{}%'.format(version))).first() def get_resource_config(self, env='prod'): return ResourceConfig.get_by_name_and_env(self.name, env) def list_resource_config(self): return ResourceConfig.list_env(self.name) def list_versions(self, start=0, limit=20): q = self.versions.order_by(Version.id.desc()).offset(start) if limit is not None: q = q.limit(limit) return q.all() 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 list_images(self, start=0, limit=20): from .image import Image return Image.list_by_app_id(self.id, start, limit) def add_version(self, sha): version = self.get_version(sha) if version: return version version = Version.create(sha, self.id) if not version: return None self.versions.append(version) db.session.add(self) db.session.commit() return version
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]
class Network(Base): __tablename__ = 'network' name = db.Column(db.CHAR(40), unique=True, nullable=False) netspace = db.Column(db.CHAR(40), nullable=False, default='', index=True) gateway_count = db.Column(db.Integer, nullable=False, default=100) ips = db.relationship('IP', backref='network', lazy='dynamic', cascade='save-update, merge, delete') gates = db.relationship('VLanGateway', backref='network', lazy='dynamic', cascade='save-update, merge, delete') def __init__(self, name, netspace, gateway_count): self.name = name self.netspace = netspace self.gateway_count = gateway_count @classmethod def create(cls, name, netspace, gateway_count=100): """create network and store ips(int) under this network in redis""" try: n = cls(name, netspace, gateway_count) db.session.add(n) db.session.commit() # create sub IPs network = n.network base = network.first # 一次写500个吧 # 写容器可用IP for ipnums in more_itertools.chunked( xrange(base + gateway_count, base + network.size), 500): rds.sadd(n.storekey, *ipnums) # 写宿主机可用IP rds.sadd(n.gatekey, *range(base, base + gateway_count)) return n except sqlalchemy.exc.IntegrityError: db.session.rollback() return None @classmethod def list_networks(cls, start=0, limit=20): q = cls.query.order_by(cls.id.asc()).offset(start) if limit is not None: q = q.limit(limit) return q.all() @classmethod def get_by_name(cls, name): return cls.query.filter_by(name=name).first() @classmethod def get_by_netspace(cls, netspace): return cls.query.filter_by(netspace=netspace).first() @property def storekey(self): return 'eru:network:%s:ips' % self.name @property def gatekey(self): return 'eru:network:%s:hosts' % self.name @property def hostmask_string(self): return self.netspace.split('/')[-1] @property def network(self): return IPNetwork(self.netspace) @property def pool_size(self): return rds.scard(self.storekey) @property def gate_pool_size(self): return rds.scard(self.gatekey) @property def used_count(self): return (self.network.size - self.gateway_count) - self.pool_size @property def used_gate_count(self): return self.gateway_count - self.gate_pool_size def __contains__(self, ip): return self.contains_ip(ip) @redis_lock('net:acquire_ip:{self.id}') def contains_ip(self, ip): """ip is unicode or IPAddress object""" if isinstance(ip, basestring): try: ip = IPAddress(ip) except AddrFormatError: return False return rds.sismember(self.storekey, ip.value) @redis_lock('net:acquire_ip:{self.id}') def acquire_ip(self): """take an IP from network, return an IP object""" ipnum = rds.spop(self.storekey) return ipnum and IP.create(ipnum, self) or None @redis_lock('net:acquire_ip:{self.id}') def acquire_specific_ip(self, ip_str): """take a specific IP from network""" try: ip = IPAddress(ip_str) except ValueError: return None if rds.sismember(self.storekey, ip.value): rds.srem(self.storekey, ip.value) return IP.create(ip.value, self) @redis_lock('net:acquire_ip:{self.id}') def release_ip(self, ip): rds.sadd(self.storekey, int(ip)) @redis_lock('net:gateway_ip:{self.id}') def acquire_gateway_ip(self, host): ipnum = rds.spop(self.gatekey) if not ipnum: return vg = VLanGateway.create(ipnum, self.id, host.id) if not vg: rds.sadd(self.gatekey, ipnum) return return vg @redis_lock('net:gateway_ip:{self.id}') def release_gateway(self, ip): rds.sadd(self.gatekey, int(ip)) def add_ip(self, ip): if isinstance(ip, basestring): try: ip = IPAddress(ip) except AddrFormatError: return False ipnum = ip.value if rds.sismember(self.gatekey, ipnum): rds.srem(self.gatekey, ipnum) rds.sadd(self.storekey, ipnum) return True def delete(self): IP.delete_by_network(self.id) rds.delete(self.storekey, self.gatekey) db.session.delete(self) db.session.commit() def to_dict(self): d = super(Network, self).to_dict() d.update( pool_size=self.pool_size, used_count=self.used_count, ) return d
def id(cls): return db.Column('id', db.Integer, primary_key=True, autoincrement=True)
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
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
class Pod(Base): __tablename__ = 'pod' name = db.Column(db.CHAR(30), nullable=False, unique=True) core_share = db.Column(db.Integer, nullable=False, default=DEFAULT_CORE_SHARE) max_share_core = db.Column(db.Integer, nullable=False, default=DEFAULT_MAX_SHARE_CORE) description = db.Column(db.Text) hosts = db.relationship('Host', backref='pod', lazy='dynamic', cascade='save-update, merge, delete') def __init__(self, name, description, core_share, max_share_core): self.name = name self.core_share = core_share self.max_share_core = max_share_core self.description = description @classmethod def create(cls, name, description='', core_share=DEFAULT_CORE_SHARE, max_share_core=DEFAULT_MAX_SHARE_CORE): try: pod = cls(name, description, core_share, max_share_core) db.session.add(pod) db.session.commit() return pod except sqlalchemy.exc.IntegrityError: db.session.rollback() return None @classmethod def list_all(cls, start=0, limit=20): q = cls.query.offset(start) if limit is not None: q = q.limit(limit) return q.all() @classmethod def get_by_name(cls, name): return cls.query.filter(cls.name == name).first() def get_core_allocation(self, core_require): """按照core_share来分配core_require的独占/共享份数""" # TODO: 更细粒度的应该是把丫丢host上 core_require = int(core_require * self.core_share) return core_require / self.core_share, core_require % self.core_share def list_hosts(self, start=0, limit=20, show_all=False): from .host import Host q = self.hosts if not show_all: q = q.filter_by(is_alive=True) q = q.order_by(Host.id.desc()).offset(start) if limit is not None: q = q.limit(limit) return q.all() def get_free_public_hosts(self, limit): hosts = [h for h in self.hosts if h.is_public and h.is_alive] random.shuffle(hosts) return hosts[:limit] if limit is not None else hosts def get_private_hosts(self): return [h for h in self.hosts if not h.is_public and h.is_alive] def host_count(self): return self.hosts.count() def to_dict(self): d = super(Pod, self).to_dict() d['host_count'] = self.host_count() return d