Skip to content

Commit

Permalink
feat: Ibppeer API & Channel API(#13#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
dayuy authored and Carrotzpc committed Feb 22, 2023
1 parent 635f6ca commit e6b9bde
Show file tree
Hide file tree
Showing 47 changed files with 1,554 additions and 40 deletions.
6 changes: 6 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ import { DataLoaderInterceptor } from './common/dataloader';
import { ServeStaticModule } from '@nestjs/serve-static';
import { Response } from 'express';
import { JSONObjectScalar, JSONScalar } from './common/scalars/json.scalar';
import { ChannelModule } from './channel/channel.module';
import { IbppeerModule } from './ibppeer/ibppeer.module';
import { ConfigmapModule } from './configmap/configmap.module';
import imageConfig from './config/image.config';

const GRAPHQL_PATH = '/bff';
Expand Down Expand Up @@ -82,6 +85,9 @@ const GRAPHQL_PATH = '/bff';
ProposalModule,
VoteModule,
NetworkModule,
ChannelModule,
IbppeerModule,
ConfigmapModule,
],
controllers: [AppController],
providers: [
Expand Down
31 changes: 31 additions & 0 deletions src/channel/channel.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# 创建通道
mutation createChannel($network: String!, $channel: NewChannel!) {
channelCreate(network: $network, channel: $channel) {
name
members{
name
}
peers {
name
namespace
}
creationTimestamp
status
}
}

# 更新通道
mutation updateChannel($channel: UpdateChannel!, $name: String!) {
channelUpdate(channel: $channel, name: $name) {
name
members{
name
}
peers {
name
namespace
}
creationTimestamp
status
}
}
21 changes: 21 additions & 0 deletions src/channel/channel.loader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Injectable, Scope } from '@nestjs/common';
import { OrderedNestDataLoader } from 'src/common/dataloader';
import { JwtAuth } from 'src/types';
import { ChannelService } from './channel.service';
import { Channel } from './models/channel.model';

@Injectable({ scope: Scope.REQUEST })
export class ChannelLoader extends OrderedNestDataLoader<
Channel['name'],
Channel
> {
constructor(private readonly channelService: ChannelService) {
super();
}

protected getOptions = (auth: JwtAuth) => ({
propertyKey: 'name',
// TODO:list/channel 有权限问题,期望用read代替list,再次read的时候能缓存
query: () => this.channelService.getChannels(auth),
});
}
10 changes: 10 additions & 0 deletions src/channel/channel.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { ChannelService } from './channel.service';
import { ChannelResolver } from './channel.resolver';
import { ChannelLoader } from './channel.loader';

@Module({
providers: [ChannelService, ChannelResolver, ChannelLoader],
exports: [ChannelLoader, ChannelService], // TODO: 使用 ChannelLoader后去除CHannelService
})
export class ChannelModule {}
30 changes: 30 additions & 0 deletions src/channel/channel.resolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Args, Mutation, Resolver } from '@nestjs/graphql';
import { Auth } from 'src/common/decorators/auth.decorator';
import { JwtAuth } from 'src/types';
import { ChannelService } from './channel.service';
import { NewChannel } from './dto/new-channel.input';
import { UpdateChannel } from './dto/update-channel.input';
import { Channel } from './models/channel.model';

@Resolver()
export class ChannelResolver {
constructor(private readonly channelService: ChannelService) {}

@Mutation(() => Channel, { description: '创建通道' })
async channelCreate(
@Auth() auth: JwtAuth,
@Args('network') network: string,
@Args('channel') channel: NewChannel,
): Promise<Channel> {
return this.channelService.createChannel(auth, network, channel);
}

@Mutation(() => Channel, { description: '加入/去除Peer节点' })
async channelUpdate(
@Auth() auth: JwtAuth,
@Args('name') name: string,
@Args('channel') channel: UpdateChannel,
): Promise<Channel> {
return this.channelService.updateChannel(auth, name, channel);
}
}
96 changes: 96 additions & 0 deletions src/channel/channel.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { Injectable } from '@nestjs/common';
import { filter, find, isEqual, uniqWith } from 'lodash';
import { KubernetesService } from 'src/kubernetes/kubernetes.service';
import { CRD } from 'src/kubernetes/lib';
import { JwtAuth } from 'src/types';
import { NewChannel } from './dto/new-channel.input';
import { Operate } from './dto/operate.enum';
import { UpdateChannel } from './dto/update-channel.input';
import { Channel } from './models/channel.model';

@Injectable()
export class ChannelService {
constructor(private readonly k8sService: KubernetesService) {}

format(channel: CRD.Channel): Channel {
return {
name: channel.metadata.name,
creationTimestamp: new Date(
channel.metadata?.creationTimestamp,
).toISOString(),
members: channel.spec?.members,
status: channel.status?.type,
peers: channel.spec?.peers,
};
}

// 权限问题,普通用户没有 list/channel
async getChannels(auth: JwtAuth): Promise<Channel[]> {
const k8s = await this.k8sService.getClient(auth);
const { body } = await k8s.channel.list();
return body.items.map((item) => this.format(item));
}

async getChannel(auth: JwtAuth, name: string): Promise<Channel> {
const k8s = await this.k8sService.getClient(auth);
const { body } = await k8s.channel.read(name);
return this.format(body);
}

async createChannel(
auth: JwtAuth,
network: string,
channel: NewChannel,
): Promise<Channel> {
const {
name,
description,
initiator,
organizations,
peers, // TODO:必须是用户管理的组织(在members中)下的节点(提供接口)
policy,
} = channel;
const members = (organizations || [])
.concat(initiator)
.map((d) => ({ name: d }));
const k8s = await this.k8sService.getClient(auth);
const { body } = await k8s.channel.create({
metadata: {
name,
},
spec: {
license: {
accept: true,
},
network,
description,
members,
peers,
},
});
return this.format(body);
}

async updateChannel(
auth: JwtAuth,
name: string,
channel: UpdateChannel,
): Promise<Channel> {
const { peers, operate } = channel;
const { peers: rawPeers } = await this.getChannel(auth, name);
let cPeers = rawPeers || [];
if (operate === Operate.add) {
cPeers = cPeers.concat(peers);
}
if (operate === Operate.remove) {
cPeers = filter(cPeers, (o) => !find(peers, o));
}
const k8s = await this.k8sService.getClient(auth);
const { body } = await k8s.channel.patchMerge(name, {
spec: {
peers: uniqWith(cPeers, isEqual),
},
});
return this.format(body);
}
}
10 changes: 10 additions & 0 deletions src/channel/dto/channel-peer.input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { InputType } from '@nestjs/graphql';

@InputType()
export class ChannelPeer {
/** 名称 */
name: string;

/** 命名空间(所属组织) */
namespace: string;
}
27 changes: 27 additions & 0 deletions src/channel/dto/new-channel.input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Field, InputType } from '@nestjs/graphql';
import { ChannelPeer } from './channel-peer.input';

@InputType()
export class NewChannel {
/** 通道名称(channel name) */
name: string;

/** 发起者(组织) */
initiator: string;

/** 配置成员(组织) */
organizations?: string[];

/** 准入门槛 */
policy?: string;

/** 描述 */
description?: string;

/** peer节点 */
@Field(() => [ChannelPeer], {
description:
'Peer节点,仅能选Deployed的(通过「getIbppeersForCreateChannel」API获取)',
})
peers: ChannelPeer[];
}
19 changes: 19 additions & 0 deletions src/channel/dto/operate.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { registerEnumType } from '@nestjs/graphql';

export enum Operate {
add = 'add',
remove = 'remove',
}

registerEnumType(Operate, {
name: 'Operator',
description: '操作',
valuesMap: {
add: {
description: '加入节点',
},
remove: {
description: '移除节点',
},
},
});
16 changes: 16 additions & 0 deletions src/channel/dto/update-channel.input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Field, InputType } from '@nestjs/graphql';
import { ChannelPeer } from './channel-peer.input';
import { Operate } from './operate.enum';

@InputType()
export class UpdateChannel {
/** 操作的节点 */
@Field(() => [ChannelPeer], {
description:
'被操作的节点,若是添加节点,则Peer节点仅能选Deployed的(通过「getIbppeersForCreateChannel」API获取)',
})
peers: ChannelPeer[];

/** 操作类型 */
operate: Operate;
}
23 changes: 23 additions & 0 deletions src/channel/models/channel-status.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { registerEnumType } from '@nestjs/graphql';

export enum ChannelStatus {
Deploying = 'Deploying',
Deployed = 'Deployed',
Error = 'Error',
}

registerEnumType(ChannelStatus, {
name: 'ChannelStatus',
description: '「通道」状态',
valuesMap: {
Deploying: {
description: 'Deploying',
},
Deployed: {
description: 'Deployed',
},
Error: {
description: '失败',
},
},
});
26 changes: 26 additions & 0 deletions src/channel/models/channel.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Field, ID, ObjectType } from '@nestjs/graphql';
import { SpecMember } from 'src/common/models/spec-member.model';
import { ChannelStatus } from './channel-status.enum';
import { SpecPeer } from './spec-peer.model';

@ObjectType()
export class Channel {
@Field(() => ID, { description: 'name' })
name: string;

/** 组织数量 */
members?: SpecMember[];

/** 我的节点 */
peers?: SpecPeer[];

/** 合约数量 */
// TODO

/** 创建时间 */
creationTimestamp?: string;

/** 状态 */
@Field(() => ChannelStatus, { description: '状态' })
status?: string;
}
12 changes: 12 additions & 0 deletions src/channel/models/spec-peer.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ObjectType } from '@nestjs/graphql';

@ObjectType()
export class SpecPeer {
/** 名称 */
name?: string;

/** 命名空间(所属组织) */
namespace?: string;

[k: string]: any;
}
19 changes: 19 additions & 0 deletions src/common/models/spec-member.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { ObjectType } from '@nestjs/graphql';

@ObjectType({ description: '成员个数' })
export class SpecMember {
/** 是否为发起者 */
initiator?: boolean;

/** 组织名称 */
name?: string;

namespace?: string;

/** 加入时间 */
joinedAt?: string;

joinedBy?: string;

[k: string]: any;
}
2 changes: 2 additions & 0 deletions src/common/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export const DEFAULT_STORAGE_CLASS = env.DEFAULT_STORAGE_CLASS;

export const DEFAULT_INGRESS_CLASS = env.DEFAULT_INGRESS_CLASS;

export const OPERATOR_INGRESS_DOMAIN = env.OPERATOR_INGRESS_DOMAIN;

/** k8s 注入到 pod 中的 service account token 路径 */
export const K8S_SA_TOKEN_PATH =
'/var/run/secrets/kubernetes.io/serviceaccount/token';
Expand Down
Loading

0 comments on commit e6b9bde

Please sign in to comment.