From 87f61af283698fbe3ff40abbd623e11984d356fb Mon Sep 17 00:00:00 2001 From: bjwswang Date: Wed, 1 Feb 2023 10:42:52 +0800 Subject: [PATCH] feat: able to join peer into channels;bug fix on channel creation Signed-off-by: bjwswang Changelogs 1. fixed channel creation bug: tlsca not working in fabric-ca 2. able to join peers into channel 3. save channel list in network.status.channels 4. fix orderer override on network creation which should use tlsca in tls 5. fix peer unit test 6. fix golangci-lint test: bug from latest version of golangci-lint(https://github.com/golangci/golangci-lint-action/issues/535) --- .github/workflows/golangci.yaml | 2 +- .github/workflows/integration-tests.yaml | 1 + api/v1beta1/channel.go | 42 +++ api/v1beta1/channel_types.go | 33 +- api/v1beta1/ibppeer.go | 15 + api/v1beta1/network.go | 38 +++ api/v1beta1/network_types.go | 2 + api/v1beta1/organization.go | 27 -- api/v1beta1/zz_generated.deepcopy.go | 50 +-- config/crd/bases/ibp.com_channels.yaml | 35 +- config/crd/bases/ibp.com_networks.yaml | 5 + ...ml => ibp.com_v1beta1_channel_create.yaml} | 3 +- .../samples/ibp.com_v1beta1_channel_join.yaml | 17 + config/samples/ibp.com_v1beta1_network.yaml | 2 +- .../ibp.com_v1beta1_network_size_3.yaml | 2 +- config/samples/orgs/org1.yaml | 4 +- config/samples/orgs/org2.yaml | 4 +- config/samples/orgs/org3.yaml | 4 +- .../peers/ibp.com_v1beta1_peer_org1peer1.yaml | 87 +++++ .../peers/ibp.com_v1beta1_peer_org2peer1.yaml | 87 +++++ config/samples/readme.md | 120 ++++++- controllers/channel/channel_controller.go | 21 +- controllers/channel/predict.go | 33 +- controllers/channel/update.go | 22 +- controllers/network/network_controller.go | 10 + controllers/network/predict.go | 86 +++++ controllers/organization/predict.go | 5 +- go.mod | 15 +- go.sum | 41 ++- pkg/connector/connector.go | 75 +++++ pkg/connector/profile.go | 312 ++++++++++++++++++ pkg/initializer/channel/initializer.go | 81 +++-- pkg/initializer/channel/orderer.go | 7 + pkg/initializer/channel/osnadmin.go | 102 +++--- pkg/initializer/orderer/configtx/configtx.go | 89 ++++- pkg/initializer/orderer/configtx/profile.go | 7 +- pkg/offering/base/channel/channel.go | 177 ++++++++-- pkg/offering/base/channel/peer.go | 170 ++++++++++ pkg/offering/base/network/override/order.go | 2 +- pkg/offering/base/organization/initializer.go | 2 +- pkg/offering/base/peer/peer.go | 25 ++ pkg/offering/base/peer/peer_test.go | 4 + pkg/offering/k8s/peer/peer_test.go | 4 + pkg/offering/openshift/peer/peer_test.go | 4 + pkg/user/user.go | 10 +- pkg/util/util.go | 27 ++ 46 files changed, 1673 insertions(+), 238 deletions(-) rename config/samples/{ibp.com_v1beta1_channel.yaml => ibp.com_v1beta1_channel_create.yaml} (81%) create mode 100644 config/samples/ibp.com_v1beta1_channel_join.yaml create mode 100644 config/samples/peers/ibp.com_v1beta1_peer_org1peer1.yaml create mode 100644 config/samples/peers/ibp.com_v1beta1_peer_org2peer1.yaml create mode 100644 pkg/connector/connector.go create mode 100644 pkg/connector/profile.go create mode 100644 pkg/offering/base/channel/peer.go diff --git a/.github/workflows/golangci.yaml b/.github/workflows/golangci.yaml index 2327e91b..e4601552 100644 --- a/.github/workflows/golangci.yaml +++ b/.github/workflows/golangci.yaml @@ -21,4 +21,4 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@v3 with: - version: latest + version: v1.47.3 diff --git a/.github/workflows/integration-tests.yaml b/.github/workflows/integration-tests.yaml index 0806e472..87e39881 100644 --- a/.github/workflows/integration-tests.yaml +++ b/.github/workflows/integration-tests.yaml @@ -26,6 +26,7 @@ on: env: KUBECONFIG_PATH: /tmp/kubeconfig.yaml OPERATOR_NAMESPACE: inttest + OPERATOR_USER_TYPE: sa DOCKERCONFIGJSON: ${{ secrets.DOCKERCONFIGJSON }} GO_VER: 1.18 diff --git a/api/v1beta1/channel.go b/api/v1beta1/channel.go index ae48a750..e897125b 100644 --- a/api/v1beta1/channel.go +++ b/api/v1beta1/channel.go @@ -22,6 +22,10 @@ func init() { SchemeBuilder.Register(&Channel{}, &ChannelList{}) } +func (channel *Channel) GetConnectionPorfile() string { + return "chan-" + channel.GetName() + "-connection-profile" +} + func (channel *Channel) GetChannelID() string { return channel.GetName() } @@ -30,6 +34,17 @@ func (channel *Channel) GetMembers() []Member { return channel.Spec.Members } +func (channel *Channel) GetPeerCondition(peer NamespacedName) (int, PeerCondition) { + for index, p := range channel.Status.PeerConditions { + if p.String() == peer.String() { + return index, p + } + } + return -1, PeerCondition{ + NamespacedName: peer, + } +} + func (channel *Channel) HasType() bool { return channel.Status.CRStatus.Type != "" } @@ -41,3 +56,30 @@ func (channel *Channel) HasNetwork() bool { func (channel *Channel) HashMembers() bool { return len(channel.Spec.Members) > 0 } + +func DifferChannelPeers(old []NamespacedName, new []NamespacedName) (added []NamespacedName, removed []NamespacedName) { + // cache in map + oldMapper := make(map[string]NamespacedName, len(old)) + for _, m := range old { + oldMapper[m.Name] = m + } + + // calculate differences + for _, m := range new { + + // added: in new ,but not in old + if _, ok := oldMapper[m.Name]; !ok { + added = append(added, m) + continue + } + + // delete the intersection + delete(oldMapper, m.Name) + } + + for _, m := range oldMapper { + removed = append(removed, m) + } + + return +} diff --git a/api/v1beta1/channel_types.go b/api/v1beta1/channel_types.go index 0000a413..69d02464 100644 --- a/api/v1beta1/channel_types.go +++ b/api/v1beta1/channel_types.go @@ -36,23 +36,44 @@ type ChannelSpec struct { // +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors=true Members []Member `json:"members"` + // Peers list all fabric peers joined at this channel + Peers []NamespacedName `json:"peers,omitempty"` + // Description for this Channel // +operator-sdk:gen-csv:customresourcedefinitions.specDescriptors=true Description string `json:"description,omitempty"` } +type PeerConditionType string + +const ( + PeerJoined PeerConditionType = "PeerJoined" + PeerError PeerConditionType = "PeerError" +) + // ChannelPeer is the IBPPeer which joins this channel -type ChannelPeer struct { +type PeerCondition struct { NamespacedName `json:",inline"` - JoinedTime metav1.Time `json:"joinedTime,omitempty"` + // Type is the type of the condition. + Type PeerConditionType `json:"type"` + // Status is the status of the condition. + // Can be True, False, Unknown. + Status metav1.ConditionStatus `json:"status"` + // Last time the condition transitioned from one status to another. + // +optional + LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"` + // Unique, one-word, CamelCase reason for the condition's last transition. + // +optional + Reason string `json:"reason,omitempty"` + // Human-readable message indicating details about last transition. + // +optional + Message string `json:"message,omitempty"` } // ChannelStatus defines the observed state of Channel type ChannelStatus struct { - CRStatus `json:",inline"` - - // Peers has been joined into this channel - Peers []ChannelPeer `json:"peers,omitempty"` + CRStatus `json:",inline"` + PeerConditions []PeerCondition `json:"peerConditions,omitempty"` } //+kubebuilder:object:root=true diff --git a/api/v1beta1/ibppeer.go b/api/v1beta1/ibppeer.go index 03b5670a..3589c877 100644 --- a/api/v1beta1/ibppeer.go +++ b/api/v1beta1/ibppeer.go @@ -26,6 +26,7 @@ import ( "github.com/IBM-Blockchain/fabric-operator/pkg/util/image" "github.com/IBM-Blockchain/fabric-operator/version" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" ) // +kubebuilder:object:generate=false @@ -310,6 +311,20 @@ func (i *PeerImages) Override(requested *PeerImages, registryURL string, arch st i.EnrollerTag = image.GetTag(arch, i.EnrollerTag, requested.EnrollerTag) } +func (p *IBPPeer) GetNamespacedName() types.NamespacedName { + return types.NamespacedName{Namespace: p.Namespace, Name: p.Name} +} + +/* Enrollment when IAM Enabled*/ + +func (p *IBPPeer) GetEnrollUser() string { + return p.Spec.Secret.Enrollment.Component.EnrollUser +} + +func (p *IBPPeer) GetEnrollToken() string { + return p.Spec.Secret.Enrollment.Component.EnrollToken +} + func init() { SchemeBuilder.Register(&IBPPeer{}, &IBPPeerList{}) } diff --git a/api/v1beta1/network.go b/api/v1beta1/network.go index d55204de..fc8f2966 100644 --- a/api/v1beta1/network.go +++ b/api/v1beta1/network.go @@ -51,3 +51,41 @@ func (network *Network) HasType() bool { func (network *Network) HasMembers() bool { return len(network.Spec.Members) != 0 } + +func (networkStatus *NetworkStatus) AddChannel(channel string) bool { + var conflict bool + + for _, f := range networkStatus.Channels { + if f == channel { + conflict = true + break + } + } + + if !conflict { + networkStatus.Channels = append(networkStatus.Channels, channel) + } + + return conflict +} + +func (networkStatus *NetworkStatus) DeleteChannel(channel string) bool { + var exist bool + var index int + + channels := networkStatus.Channels + + for curr, f := range channels { + if f == channel { + exist = true + index = curr + break + } + } + + if exist { + networkStatus.Channels = append(channels[:index], channels[index+1:]...) + } + + return exist +} diff --git a/api/v1beta1/network_types.go b/api/v1beta1/network_types.go index 55d72563..8fec1bde 100644 --- a/api/v1beta1/network_types.go +++ b/api/v1beta1/network_types.go @@ -48,6 +48,8 @@ type NetworkSpec struct { // NetworkStatus defines the observed state of Network type NetworkStatus struct { CRStatus `json:",inline"` + // Channels in this network + Channels []string `json:"channels,omitempty"` } // +kubebuilder:object:root=true diff --git a/api/v1beta1/organization.go b/api/v1beta1/organization.go index 6b7796d0..514d819b 100644 --- a/api/v1beta1/organization.go +++ b/api/v1beta1/organization.go @@ -58,33 +58,6 @@ func (organization *Organization) HasType() bool { return organization.Status.CRStatus.Type != "" } -func DifferClients(old []string, new []string) (added []string, removed []string) { - // cache in map - oldMapper := make(map[string]struct{}, len(old)) - for _, c := range old { - oldMapper[c] = struct{}{} - } - - // calculate differences - for _, c := range new { - - // added: in new ,but not in old - if _, ok := oldMapper[c]; !ok { - added = append(added, c) - continue - } - - // delete the intersection - delete(oldMapper, c) - } - - for c := range oldMapper { - removed = append(removed, c) - } - - return -} - func (organizationStatus *OrganizationStatus) AddFederation(federation NamespacedName) bool { var conflict bool diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 87822c64..57347daa 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -389,23 +389,6 @@ func (in *ChannelList) DeepCopyObject() runtime.Object { return nil } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ChannelPeer) DeepCopyInto(out *ChannelPeer) { - *out = *in - out.NamespacedName = in.NamespacedName - in.JoinedTime.DeepCopyInto(&out.JoinedTime) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChannelPeer. -func (in *ChannelPeer) DeepCopy() *ChannelPeer { - if in == nil { - return nil - } - out := new(ChannelPeer) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ChannelSpec) DeepCopyInto(out *ChannelSpec) { *out = *in @@ -417,6 +400,11 @@ func (in *ChannelSpec) DeepCopyInto(out *ChannelSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.Peers != nil { + in, out := &in.Peers, &out.Peers + *out = make([]NamespacedName, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChannelSpec. @@ -433,9 +421,9 @@ func (in *ChannelSpec) DeepCopy() *ChannelSpec { func (in *ChannelStatus) DeepCopyInto(out *ChannelStatus) { *out = *in in.CRStatus.DeepCopyInto(&out.CRStatus) - if in.Peers != nil { - in, out := &in.Peers, &out.Peers - *out = make([]ChannelPeer, len(*in)) + if in.PeerConditions != nil { + in, out := &in.PeerConditions, &out.PeerConditions + *out = make([]PeerCondition, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -1839,6 +1827,11 @@ func (in *NetworkSpec) DeepCopy() *NetworkSpec { func (in *NetworkStatus) DeepCopyInto(out *NetworkStatus) { *out = *in in.CRStatus.DeepCopyInto(&out.CRStatus) + if in.Channels != nil { + in, out := &in.Channels, &out.Channels + *out = make([]string, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkStatus. @@ -2164,6 +2157,23 @@ func (in *PeerAction) DeepCopy() *PeerAction { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PeerCondition) DeepCopyInto(out *PeerCondition) { + *out = *in + out.NamespacedName = in.NamespacedName + in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PeerCondition. +func (in *PeerCondition) DeepCopy() *PeerCondition { + if in == nil { + return nil + } + out := new(PeerCondition) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PeerConnectionProfile) DeepCopyInto(out *PeerConnectionProfile) { *out = *in diff --git a/config/crd/bases/ibp.com_channels.yaml b/config/crd/bases/ibp.com_channels.yaml index b393479c..29682e7f 100644 --- a/config/crd/bases/ibp.com_channels.yaml +++ b/config/crd/bases/ibp.com_channels.yaml @@ -73,6 +73,16 @@ spec: network: description: Network which this channel belongs to type: string + peers: + description: Peers list all fabric peers joined at this channel + items: + properties: + name: + type: string + namespace: + type: string + type: object + type: array required: - license - members @@ -93,18 +103,37 @@ spec: description: Message provides a message for the status to be shown to customer type: string - peers: - description: Peers has been joined into this channel + peerConditions: items: description: ChannelPeer is the IBPPeer which joins this channel properties: - joinedTime: + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. format: date-time type: string + message: + description: Human-readable message indicating details about + last transition. + type: string name: type: string namespace: type: string + reason: + description: Unique, one-word, CamelCase reason for the condition's + last transition. + type: string + status: + description: Status is the status of the condition. Can be True, + False, Unknown. + type: string + type: + description: Type is the type of the condition. + type: string + required: + - status + - type type: object type: array reason: diff --git a/config/crd/bases/ibp.com_networks.yaml b/config/crd/bases/ibp.com_networks.yaml index a043285b..19b6edf7 100644 --- a/config/crd/bases/ibp.com_networks.yaml +++ b/config/crd/bases/ibp.com_networks.yaml @@ -968,6 +968,11 @@ spec: status: description: NetworkStatus defines the observed state of Network properties: + channels: + description: Channels in this network + items: + type: string + type: array errorcode: description: ErrorCode is the code of classification of errors type: integer diff --git a/config/samples/ibp.com_v1beta1_channel.yaml b/config/samples/ibp.com_v1beta1_channel_create.yaml similarity index 81% rename from config/samples/ibp.com_v1beta1_channel.yaml rename to config/samples/ibp.com_v1beta1_channel_create.yaml index 83685105..90f08e77 100644 --- a/config/samples/ibp.com_v1beta1_channel.yaml +++ b/config/samples/ibp.com_v1beta1_channel_create.yaml @@ -9,5 +9,4 @@ spec: members: - name: org1 - name: org2 - description: "channel with org0 & org1" - + description: "channel with org1 & org2" \ No newline at end of file diff --git a/config/samples/ibp.com_v1beta1_channel_join.yaml b/config/samples/ibp.com_v1beta1_channel_join.yaml new file mode 100644 index 00000000..76bb20c2 --- /dev/null +++ b/config/samples/ibp.com_v1beta1_channel_join.yaml @@ -0,0 +1,17 @@ +apiVersion: ibp.com/v1beta1 +kind: Channel +metadata: + name: channel-sample +spec: + license: + accept: true + network: "network-sample3" + members: + - name: org1 + - name: org2 + peers: + - name: org1peer1 + namespace: org1 + - name: org2peer1 + namespace: org1 + description: "channel with org1 & org2" diff --git a/config/samples/ibp.com_v1beta1_network.yaml b/config/samples/ibp.com_v1beta1_network.yaml index adaccf01..10a92fb2 100644 --- a/config/samples/ibp.com_v1beta1_network.yaml +++ b/config/samples/ibp.com_v1beta1_network.yaml @@ -6,7 +6,7 @@ spec: license: accept: true federation: federation-sample - initialToken: + initialToken: orderSpec: license: accept: true diff --git a/config/samples/ibp.com_v1beta1_network_size_3.yaml b/config/samples/ibp.com_v1beta1_network_size_3.yaml index 124cc05e..6a69366a 100644 --- a/config/samples/ibp.com_v1beta1_network_size_3.yaml +++ b/config/samples/ibp.com_v1beta1_network_size_3.yaml @@ -6,7 +6,7 @@ spec: license: accept: true federation: federation-sample - initialToken: + initialToken: orderSpec: license: accept: true diff --git a/config/samples/orgs/org1.yaml b/config/samples/orgs/org1.yaml index cb410972..4b150db9 100644 --- a/config/samples/orgs/org1.yaml +++ b/config/samples/orgs/org1.yaml @@ -7,7 +7,7 @@ spec: accept: true displayName: "test organization" admin: org1admin - admintoken: + admintoken: clients: - client description: "test org1" @@ -18,7 +18,7 @@ spec: class: "portal-ingress" images: caImage: hyperledgerk8s/fabric-ca - caTag: "1.5.5-iam" + caTag: "iam-20230131" caInitImage: hyperledgerk8s/ubi-minimal caInitTag: latest resources: diff --git a/config/samples/orgs/org2.yaml b/config/samples/orgs/org2.yaml index 25799351..417bd940 100644 --- a/config/samples/orgs/org2.yaml +++ b/config/samples/orgs/org2.yaml @@ -7,7 +7,7 @@ spec: accept: true displayName: "test organization" admin: org2admin - admintoken: + admintoken: clients: - client description: "test org2" @@ -18,7 +18,7 @@ spec: class: "portal-ingress" images: caImage: hyperledgerk8s/fabric-ca - caTag: "1.5.5-iam" + caTag: "iam-20230131" caInitImage: hyperledgerk8s/ubi-minimal caInitTag: latest resources: diff --git a/config/samples/orgs/org3.yaml b/config/samples/orgs/org3.yaml index 0b38f187..f2cafde4 100644 --- a/config/samples/orgs/org3.yaml +++ b/config/samples/orgs/org3.yaml @@ -7,7 +7,7 @@ spec: accept: true displayName: "test organization" admin: org3admin - admintoken: + admintoken: clients: - client description: "test org3" @@ -18,7 +18,7 @@ spec: class: "portal-ingress" images: caImage: hyperledgerk8s/fabric-ca - caTag: "1.5.5-iam" + caTag: "iam-20230131" caInitImage: hyperledgerk8s/ubi-minimal caInitTag: latest resources: diff --git a/config/samples/peers/ibp.com_v1beta1_peer_org1peer1.yaml b/config/samples/peers/ibp.com_v1beta1_peer_org1peer1.yaml new file mode 100644 index 00000000..1ba01c16 --- /dev/null +++ b/config/samples/peers/ibp.com_v1beta1_peer_org1peer1.yaml @@ -0,0 +1,87 @@ +# +# Copyright contributors to the Hyperledger Fabric Operator project +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +--- +apiVersion: ibp.com/v1beta1 +kind: IBPPeer +metadata: + name: org1peer1 + namespace: org1 +spec: + license: + accept: true + mspID: org1 # organization name + domain: "172.18.0.2.nip.io" # by webhook + version: "2.4.7" + ingress: + class: "portal-ingress" + secret: + enrollment: + component: + caname: "ca" + cahost: "org1-org1-ca.172.18.0.2.nip.io" + caport: "443" + catls: + cacert: + enrollid: "org1peer1" + enrollsecret: "do-not-need" + enrolluser: "org1admin" + enrolltoken: + tls: + caname: "tlsca" + cahost: "org1-org1-ca.172.18.0.2.nip.io" + caport: "443" + catls: + cacert: + enrollid: "org1peer1" + enrollsecret: "do-not-need" + enrolluser: "org1admin" + enrolltoken: + chaincodeBuilderConfig: + peername: org1-peer1 + service: + type: ClusterIP + stateDb: leveldb + storage: + peer: + class: "standard" + size: 5G + statedb: + class: "standard" + size: 10Gi + resources: + init: + limits: + cpu: 100m + memory: 200M + requests: + cpu: 10m + memory: 10M + peer: + limits: + cpu: 500m + memory: 1G + requests: + cpu: 10m + memory: 10M + proxy: + limits: + cpu: 100m + memory: 200M + requests: + cpu: 10m + memory: 10M \ No newline at end of file diff --git a/config/samples/peers/ibp.com_v1beta1_peer_org2peer1.yaml b/config/samples/peers/ibp.com_v1beta1_peer_org2peer1.yaml new file mode 100644 index 00000000..2db6cb8f --- /dev/null +++ b/config/samples/peers/ibp.com_v1beta1_peer_org2peer1.yaml @@ -0,0 +1,87 @@ +# +# Copyright contributors to the Hyperledger Fabric Operator project +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +--- +apiVersion: ibp.com/v1beta1 +kind: IBPPeer +metadata: + name: org2peer1 + namespace: org2 +spec: + license: + accept: true + mspID: org2 # organization name + domain: "172.18.0.2.nip.io" # by webhook + version: "2.4.7" + ingress: + class: "portal-ingress" + secret: + enrollment: + component: + caname: "ca" + cahost: "org2-org2-ca.172.18.0.2.nip.io" + caport: "443" + catls: + cacert: + enrollid: "org2peer1" + enrollsecret: "do-not-need" + enrolluser: "org2admin" + enrolltoken: + tls: + caname: "tlsca" + cahost: "org2-org2-ca.172.18.0.2.nip.io" + caport: "443" + catls: + cacert: + enrollid: "org2peer1" + enrollsecret: "do-not-need" + enrolluser: "org2admin" + enrolltoken: + chaincodeBuilderConfig: + peername: org2-peer1 + service: + type: ClusterIP + stateDb: leveldb + storage: + peer: + class: "standard" + size: 5G + statedb: + class: "standard" + size: 10Gi + resources: + init: + limits: + cpu: 100m + memory: 200M + requests: + cpu: 10m + memory: 10M + peer: + limits: + cpu: 500m + memory: 1G + requests: + cpu: 10m + memory: 10M + proxy: + limits: + cpu: 100m + memory: 200M + requests: + cpu: 10m + memory: 10M \ No newline at end of file diff --git a/config/samples/readme.md b/config/samples/readme.md index 91cba359..42e835bc 100644 --- a/config/samples/readme.md +++ b/config/samples/readme.md @@ -2014,7 +2014,7 @@ status: #### 1.1 创建单 orderer 网络 Network 的 CR -需要先将 yaml 中的 `` 替换为 发起者 initiator 组织 admin 用户的 token。 +需要先将 yaml 中的 `` 替换为 发起者 initiator 组织 org1 管理员用户的 token。 ```bash kubectl apply -f config/samples/ibp.com_v1beta1_network.yaml @@ -2257,7 +2257,7 @@ metadata: #### 1.2 创建三 orderer 网络 Network 的 CR -需要先将 yaml 中的 `` 替换为 发起者 initiator 组织 admin 用户的 token。 +需要先将 yaml 中的 `` 替换为 发起者 initiator 组织 org1 管理员用户的 token。 ```bash kubectl apply -f config/samples/ibp.com_v1beta1_network_size_3.yaml @@ -2723,7 +2723,7 @@ metadata: 删除网络需要创建提案 Proposal, 根据提案的 policy, 各参与组织同意后,才会自动删除。 ```bash -kubectl apply -f ibp.com_v1beta1_proposal_dissolve_network.yaml +kubectl apply -f config/samples/ibp.com_v1beta1_proposal_dissolve_network.yaml ```
@@ -2780,3 +2780,117 @@ kubectl patch vote -n org2 vote-org2-dissolve-network-sample --type='json' -p='[ ``` 稍后,operator 会自动删除 network。网络删除完成。 + +### 节点管理 + +#### 1. 创建org1peer1节点 +创建一个peer节点,需要按照一下步骤: + +##### 1.1 获取组织org1的CA连接信息 + +```bash + kubectl get cm -norg1 org1-connection-profile -ojson | jq -r '.binaryData."profile.json"' | base64 --decode +``` + +详细json为: +
+ +```json +{ + "endpoints": { + "api": "https://org1-org1-ca.172.18.0.2.nip.io:443", + "operations": "https://org1-org1-operations.172.18.0.2.nip.io:443" + }, + "tls": { + "cert": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNXekNDQWdHZ0F3SUJBZ0lRZnJGV01iNXE2MFRueitYZUwrVEIwREFLQmdncWhrak9QUVFEQWpDQmd6RUwKTUFrR0ExVUVCaE1DVlZNeEZ6QVZCZ05WQkFnVERrNXZjblJvSUVOaGNtOXNhVzVoTVE4d0RRWURWUVFIRXdaRQpkWEpvWVcweEREQUtCZ05WQkFvVEEwbENUVEVUTUJFR0ExVUVDeE1LUW14dlkydGphR0ZwYmpFbk1DVUdBMVVFCkF4TWViM0puTVMxdmNtY3hMV05oTGpFM01pNHhPQzR3TGpJdWJtbHdMbWx2TUI0WERUSXpNREV6TVRBek1qZzEKT1ZvWERUTXpNREV5T0RBek1qZzFPVm93Z1lNeEN6QUpCZ05WQkFZVEFsVlRNUmN3RlFZRFZRUUlFdzVPYjNKMAphQ0JEWVhKdmJHbHVZVEVQTUEwR0ExVUVCeE1HUkhWeWFHRnRNUXd3Q2dZRFZRUUtFd05KUWsweEV6QVJCZ05WCkJBc1RDa0pzYjJOclkyaGhhVzR4SnpBbEJnTlZCQU1USG05eVp6RXRiM0puTVMxallTNHhOekl1TVRndU1DNHkKTG01cGNDNXBiekJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCSVJOUllMRUlnRXBTWTI2ZzMzRAozWElmN1d6YVJITURLTGNqd292a3ZBaHNyVDh2T1VUUEoxMWVIaC9seDRjWlkxUC82WE9pL2wzeTFudXdrNndOCkh2T2pWVEJUTUZFR0ExVWRFUVJLTUVpQ0htOXlaekV0YjNKbk1TMWpZUzR4TnpJdU1UZ3VNQzR5TG01cGNDNXAKYjRJbWIzSm5NUzF2Y21jeExXOXdaWEpoZEdsdmJuTXVNVGN5TGpFNExqQXVNaTV1YVhBdWFXOHdDZ1lJS29aSQp6ajBFQXdJRFNBQXdSUUloQUt5ZlFKTXpZV1lOSEdOMTlQZE5YSlIycjBhYjgwVE1KKzlrT0M2N0VKRFhBaUJGCjhTN3JHZmJmcC91N2hLNlRZb0NFRjF1RGR2OWxkYm5SeGg5WFdnNFhydz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K" + }, + "ca": { + "signcerts": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNFakNDQWJtZ0F3SUJBZ0lVSC9lNjEwWW1Wc0QyR2swa1MvWFBEN0pSZXc0d0NnWUlLb1pJemowRUF3SXcKWHpFTE1Ba0dBMVVFQmhNQ1ZWTXhGekFWQmdOVkJBZ1REazV2Y25Sb0lFTmhjbTlzYVc1aE1SUXdFZ1lEVlFRSwpFd3RJZVhCbGNteGxaR2RsY2pFUE1BMEdBMVVFQ3hNR1JtRmljbWxqTVJBd0RnWURWUVFERXdkdmNtY3hMV05oCk1CNFhEVEl6TURFek1UQXpNalF3TUZvWERUTTRNREV5TnpBek1qUXdNRm93WHpFTE1Ba0dBMVVFQmhNQ1ZWTXgKRnpBVkJnTlZCQWdURGs1dmNuUm9JRU5oY205c2FXNWhNUlF3RWdZRFZRUUtFd3RJZVhCbGNteGxaR2RsY2pFUApNQTBHQTFVRUN4TUdSbUZpY21sak1SQXdEZ1lEVlFRREV3ZHZjbWN4TFdOaE1Ga3dFd1lIS29aSXpqMENBUVlJCktvWkl6ajBEQVFjRFFnQUVIdFhNdGh2ckdmUkNFczhCdElPVnpvL2c2c0tDVUpTOWx5QXNIVE1iMW80ZDhwUmkKRjRZcUtlejZ2WXNOM2s4TEZhNytXenRuYkU2YW1MSHNKTEZ5ZWFOVE1GRXdEZ1lEVlIwUEFRSC9CQVFEQWdFRwpNQThHQTFVZEV3RUIvd1FGTUFNQkFmOHdIUVlEVlIwT0JCWUVGQ09pbjNjOFFOUGEvS2JwSklFSVJsNk9vYTZkCk1BOEdBMVVkRVFRSU1BYUhCSDhBQUFFd0NnWUlLb1pJemowRUF3SURSd0F3UkFJZ1Y2Z2dzMkM0cGpPeUJJVlEKdm5KdGR3TGZTZER5bkJSaXpjVGJ4SC9NNlF3Q0lIbDZPNGRmUURGYSt5ditWODlkV2hvY3RLSDYxQWVBZ01aNwpldXdFNVdpagotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==" + }, + "tlsca": { + "signcerts": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNCekNDQWE2Z0F3SUJBZ0lVQ0ptV2NLQXVYaklvKzd6V2MxczVwQjZwK2tBd0NnWUlLb1pJemowRUF3SXcKWWpFTE1Ba0dBMVVFQmhNQ1ZWTXhGekFWQmdOVkJBZ1REazV2Y25Sb0lFTmhjbTlzYVc1aE1SUXdFZ1lEVlFRSwpFd3RJZVhCbGNteGxaR2RsY2pFUE1BMEdBMVVFQ3hNR1JtRmljbWxqTVJNd0VRWURWUVFERXdwdmNtY3hMWFJzCmMyTmhNQjRYRFRJek1ERXpNVEF6TWpRd01Gb1hEVE00TURFeU56QXpNalF3TUZvd1lqRUxNQWtHQTFVRUJoTUMKVlZNeEZ6QVZCZ05WQkFnVERrNXZjblJvSUVOaGNtOXNhVzVoTVJRd0VnWURWUVFLRXd0SWVYQmxjbXhsWkdkbApjakVQTUEwR0ExVUVDeE1HUm1GaWNtbGpNUk13RVFZRFZRUURFd3B2Y21jeExYUnNjMk5oTUZrd0V3WUhLb1pJCnpqMENBUVlJS29aSXpqMERBUWNEUWdBRWthZFhoTExBWFlRWkRkcktUNXNpdkV1clRQb0FrWU5JTDZpeFBMTWoKcDF3NGVUMWhqOVQ4U1psMTR2R0VqMjlnZjBvaDd4MU1ILzRmeDQ0VVlSZlRzNk5DTUVBd0RnWURWUjBQQVFILwpCQVFEQWdFR01BOEdBMVVkRXdFQi93UUZNQU1CQWY4d0hRWURWUjBPQkJZRUZNbU85WWF5WWV0RFg0cEREQnEwCnJkTjFpZkNWTUFvR0NDcUdTTTQ5QkFNQ0EwY0FNRVFDSUYweFQvVDFFaXB5ZmRxWEh0dXMydk4yNHprZzJxT1UKNG96ekRwdHlINXZPQWlCZzcyQ2pSNlQyRGV6a2ZiTTYwekdNbzBtclFXRzVFSTFFR09OOEE5aTVVZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K" + } +} +``` + +
+ +需要: +- `endpoints.api` : ca地址 +- `tls.cert` : ca tls 证书 + +##### 1.2 更新peer的创建yaml + +yaml位于 `config/samples/peers/ibp.com_v1beta1_peer_org1peer1.yaml` ,需要: + +- 替换`` 为 上一步骤得到的`tls.cert` +- 替换`` 为用户org1admin的token + +##### 1.3 创建org1peer1 + +```bash +kubectl apply -f config/samples/peers/ibp.com_v1beta1_peer_org1peer1.yaml +``` + +### 2. 创建org2peer1节点 + + +##### 2.1 获取组织org2的CA连接信息 + +```bash + kubectl get cm -norg2 org2-connection-profile -ojson | jq -r '.binaryData."profile.json"' | base64 --decode +``` + +详细json为: +
+ +```json +{ + "endpoints": { + "api": "https://org2-org2-ca.172.18.0.2.nip.io:443", + "operations": "https://org2-org2-operations.172.18.0.2.nip.io:443" + }, + "tls": { + "cert": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNXekNDQWdHZ0F3SUJBZ0lRZnJGV01iNXE2MFRueitYZUwrVEIwREFLQmdncWhrak9QUVFEQWpDQmd6RUwKTUFrR0ExVUVCaE1DVlZNeEZ6QVZCZ05WQkFnVERrNXZjblJvSUVOaGNtOXNhVzVoTVE4d0RRWURWUVFIRXdaRQpkWEpvWVcweEREQUtCZ05WQkFvVEEwbENUVEVUTUJFR0ExVUVDeE1LUW14dlkydGphR0ZwYmpFbk1DVUdBMVVFCkF4TWViM0puTVMxdmNtY3hMV05oTGpFM01pNHhPQzR3TGpJdWJtbHdMbWx2TUI0WERUSXpNREV6TVRBek1qZzEKT1ZvWERUTXpNREV5T0RBek1qZzFPVm93Z1lNeEN6QUpCZ05WQkFZVEFsVlRNUmN3RlFZRFZRUUlFdzVPYjNKMAphQ0JEWVhKdmJHbHVZVEVQTUEwR0ExVUVCeE1HUkhWeWFHRnRNUXd3Q2dZRFZRUUtFd05KUWsweEV6QVJCZ05WCkJBc1RDa0pzYjJOclkyaGhhVzR4SnpBbEJnTlZCQU1USG05eVp6RXRiM0puTVMxallTNHhOekl1TVRndU1DNHkKTG01cGNDNXBiekJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCSVJOUllMRUlnRXBTWTI2ZzMzRAozWElmN1d6YVJITURLTGNqd292a3ZBaHNyVDh2T1VUUEoxMWVIaC9seDRjWlkxUC82WE9pL2wzeTFudXdrNndOCkh2T2pWVEJUTUZFR0ExVWRFUVJLTUVpQ0htOXlaekV0YjNKbk1TMWpZUzR4TnpJdU1UZ3VNQzR5TG01cGNDNXAKYjRJbWIzSm5NUzF2Y21jeExXOXdaWEpoZEdsdmJuTXVNVGN5TGpFNExqQXVNaTV1YVhBdWFXOHdDZ1lJS29aSQp6ajBFQXdJRFNBQXdSUUloQUt5ZlFKTXpZV1lOSEdOMTlQZE5YSlIycjBhYjgwVE1KKzlrT0M2N0VKRFhBaUJGCjhTN3JHZmJmcC91N2hLNlRZb0NFRjF1RGR2OWxkYm5SeGg5WFdnNFhydz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K" + }, + "ca": { + "signcerts": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNFakNDQWJtZ0F3SUJBZ0lVSC9lNjEwWW1Wc0QyR2swa1MvWFBEN0pSZXc0d0NnWUlLb1pJemowRUF3SXcKWHpFTE1Ba0dBMVVFQmhNQ1ZWTXhGekFWQmdOVkJBZ1REazV2Y25Sb0lFTmhjbTlzYVc1aE1SUXdFZ1lEVlFRSwpFd3RJZVhCbGNteGxaR2RsY2pFUE1BMEdBMVVFQ3hNR1JtRmljbWxqTVJBd0RnWURWUVFERXdkdmNtY3hMV05oCk1CNFhEVEl6TURFek1UQXpNalF3TUZvWERUTTRNREV5TnpBek1qUXdNRm93WHpFTE1Ba0dBMVVFQmhNQ1ZWTXgKRnpBVkJnTlZCQWdURGs1dmNuUm9JRU5oY205c2FXNWhNUlF3RWdZRFZRUUtFd3RJZVhCbGNteGxaR2RsY2pFUApNQTBHQTFVRUN4TUdSbUZpY21sak1SQXdEZ1lEVlFRREV3ZHZjbWN4TFdOaE1Ga3dFd1lIS29aSXpqMENBUVlJCktvWkl6ajBEQVFjRFFnQUVIdFhNdGh2ckdmUkNFczhCdElPVnpvL2c2c0tDVUpTOWx5QXNIVE1iMW80ZDhwUmkKRjRZcUtlejZ2WXNOM2s4TEZhNytXenRuYkU2YW1MSHNKTEZ5ZWFOVE1GRXdEZ1lEVlIwUEFRSC9CQVFEQWdFRwpNQThHQTFVZEV3RUIvd1FGTUFNQkFmOHdIUVlEVlIwT0JCWUVGQ09pbjNjOFFOUGEvS2JwSklFSVJsNk9vYTZkCk1BOEdBMVVkRVFRSU1BYUhCSDhBQUFFd0NnWUlLb1pJemowRUF3SURSd0F3UkFJZ1Y2Z2dzMkM0cGpPeUJJVlEKdm5KdGR3TGZTZER5bkJSaXpjVGJ4SC9NNlF3Q0lIbDZPNGRmUURGYSt5ditWODlkV2hvY3RLSDYxQWVBZ01aNwpldXdFNVdpagotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==" + }, + "tlsca": { + "signcerts": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNCekNDQWE2Z0F3SUJBZ0lVQ0ptV2NLQXVYaklvKzd6V2MxczVwQjZwK2tBd0NnWUlLb1pJemowRUF3SXcKWWpFTE1Ba0dBMVVFQmhNQ1ZWTXhGekFWQmdOVkJBZ1REazV2Y25Sb0lFTmhjbTlzYVc1aE1SUXdFZ1lEVlFRSwpFd3RJZVhCbGNteGxaR2RsY2pFUE1BMEdBMVVFQ3hNR1JtRmljbWxqTVJNd0VRWURWUVFERXdwdmNtY3hMWFJzCmMyTmhNQjRYRFRJek1ERXpNVEF6TWpRd01Gb1hEVE00TURFeU56QXpNalF3TUZvd1lqRUxNQWtHQTFVRUJoTUMKVlZNeEZ6QVZCZ05WQkFnVERrNXZjblJvSUVOaGNtOXNhVzVoTVJRd0VnWURWUVFLRXd0SWVYQmxjbXhsWkdkbApjakVQTUEwR0ExVUVDeE1HUm1GaWNtbGpNUk13RVFZRFZRUURFd3B2Y21jeExYUnNjMk5oTUZrd0V3WUhLb1pJCnpqMENBUVlJS29aSXpqMERBUWNEUWdBRWthZFhoTExBWFlRWkRkcktUNXNpdkV1clRQb0FrWU5JTDZpeFBMTWoKcDF3NGVUMWhqOVQ4U1psMTR2R0VqMjlnZjBvaDd4MU1ILzRmeDQ0VVlSZlRzNk5DTUVBd0RnWURWUjBQQVFILwpCQVFEQWdFR01BOEdBMVVkRXdFQi93UUZNQU1CQWY4d0hRWURWUjBPQkJZRUZNbU85WWF5WWV0RFg0cEREQnEwCnJkTjFpZkNWTUFvR0NDcUdTTTQ5QkFNQ0EwY0FNRVFDSUYweFQvVDFFaXB5ZmRxWEh0dXMydk4yNHprZzJxT1UKNG96ekRwdHlINXZPQWlCZzcyQ2pSNlQyRGV6a2ZiTTYwekdNbzBtclFXRzVFSTFFR09OOEE5aTVVZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K" + } +} +``` + +
+ +需要: +- `endpoints.api` : ca地址 +- `tls.cert` : ca tls 证书 + +##### 1.2 更新peer的创建yaml + +yaml位于 `config/samples/peers/ibp.com_v1beta1_peer_org2peer1.yaml` ,需要: + +- 替换`` 为 上一步骤得到的`tls.cert` +- 替换`` 为用户org2admin的token + +##### 1.3 创建org2peer1 + +```bash +kubectl apply -f config/samples/peers/ibp.com_v1beta1_peer_org2peer1.yaml +``` + +### 通道管理 + +#### 1. 创建通道 + +```bash +kubectl apply -f config/samples/ibp.com_v1beta1_channel_create.yaml +``` + +#### 2. peer节点加入通道 + +```bash +kubectl apply -f config/samples/ibp.com_v1beta1_channel_join.yaml +``` diff --git a/controllers/channel/channel_controller.go b/controllers/channel/channel_controller.go index c13e34cd..71e390b4 100644 --- a/controllers/channel/channel_controller.go +++ b/controllers/channel/channel_controller.go @@ -112,17 +112,6 @@ func add(mgr manager.Manager, r *ReconcileChannel) error { return err } - // Watch ibppeer - peerPredictFuncs := predicate.Funcs{ - CreateFunc: r.PeerCreateFunc, - UpdateFunc: r.PeerUpdateFunc, - DeleteFunc: r.PeerDeleteFunc, - } - err = c.Watch(&source.Kind{Type: ¤t.IBPPeer{}}, &handler.EnqueueRequestForObject{}, peerPredictFuncs) - if err != nil { - return err - } - return nil } @@ -249,7 +238,8 @@ func (r *ReconcileChannel) SetStatus(instance *current.Channel, reconcileStatus status.LastHeartbeatTime = metav1.Now() instance.Status = current.ChannelStatus{ - CRStatus: status, + CRStatus: status, + PeerConditions: instance.Status.PeerConditions, } log.Info(fmt.Sprintf("Updating status of Channel custom resource to %s phase", instance.Status.Type)) @@ -274,6 +264,10 @@ func (r *ReconcileChannel) SetStatus(instance *current.Channel, reconcileStatus func (r *ReconcileChannel) SetErrorStatus(instance *current.Channel, reconcileErr error) error { var err error + if err = r.client.Get(context.TODO(), types.NamespacedName{Name: instance.GetName(), Namespace: instance.GetNamespace()}, instance); err != nil { + return err + } + if err = r.SaveSpecState(instance); err != nil { return errors.Wrap(err, "failed to save spec state") } @@ -289,7 +283,8 @@ func (r *ReconcileChannel) SetErrorStatus(instance *current.Channel, reconcileEr status.ErrorCode = operatorerrors.GetErrorCode(reconcileErr) instance.Status = current.ChannelStatus{ - CRStatus: status, + CRStatus: status, + PeerConditions: instance.Status.PeerConditions, } log.Info(fmt.Sprintf("Updating status of Channel custom resource to %s phase", instance.Status.Type)) diff --git a/controllers/channel/predict.go b/controllers/channel/predict.go index 2c3bc056..527b893e 100644 --- a/controllers/channel/predict.go +++ b/controllers/channel/predict.go @@ -60,7 +60,7 @@ func (r *ReconcileChannel) CreateFunc(e event.CreateEvent) bool { update.specUpdated = true } - added, removed := current.DifferMembers(channel.Spec.Members, existingChannel.Spec.Members) + added, removed := current.DifferMembers(existingChannel.Spec.Members, channel.Spec.Members) if len(added) != 0 || len(removed) != 0 { log.Info(fmt.Sprintf("Channel '%s' members was updated while operator was down", channel.GetName())) log.Info(fmt.Sprintf("Difference detected: added members %v", added)) @@ -68,11 +68,23 @@ func (r *ReconcileChannel) CreateFunc(e event.CreateEvent) bool { update.memberUpdated = true } + addedPeers, removedPeers := current.DifferChannelPeers(existingChannel.Spec.Peers, channel.Spec.Peers) + if len(addedPeers) != 0 || len(removedPeers) != 0 { + log.Info(fmt.Sprintf("Channel '%s' peers was updated while operator was down", channel.GetName())) + log.Info(fmt.Sprintf("Difference detected: added peers %v", addedPeers)) + log.Info(fmt.Sprintf("Difference detected: removed peers %v", removedPeers)) + update.peerUpdated = true + } + log.Info(fmt.Sprintf("Create event triggering reconcile for updating Channel '%s'", channel.GetName())) r.PushUpdate(channel.GetName(), update) return true } + if len(channel.Spec.Peers) != 0 { + update.peerUpdated = true + } + update.specUpdated = true update.memberUpdated = true r.PushUpdate(channel.GetName(), update) @@ -100,21 +112,16 @@ func (r *ReconcileChannel) UpdateFunc(e event.UpdateEvent) bool { update.memberUpdated = true } + addedPeers, removedPeers := current.DifferChannelPeers(oldChan.Spec.Peers, newChan.Spec.Peers) + if len(addedPeers) != 0 || len(removedPeers) != 0 { + log.Info(fmt.Sprintf("Difference detected: added peers %v", addedPeers)) + log.Info(fmt.Sprintf("Difference detected: removed peers %v", removedPeers)) + update.peerUpdated = true + } + r.PushUpdate(oldChan.GetName(), update) log.Info(fmt.Sprintf("Spec update triggering reconcile on Channel custom resource %s: update [ %+v ]", oldChan.Name, update.GetUpdateStackWithTrues())) return true } - -func (r *ReconcileChannel) PeerCreateFunc(e event.CreateEvent) bool { - return false -} - -func (r *ReconcileChannel) PeerUpdateFunc(e event.UpdateEvent) bool { - return false -} - -func (r *ReconcileChannel) PeerDeleteFunc(e event.DeleteEvent) bool { - return false -} diff --git a/controllers/channel/update.go b/controllers/channel/update.go index acba4d49..8fcc2cec 100644 --- a/controllers/channel/update.go +++ b/controllers/channel/update.go @@ -26,8 +26,10 @@ import ( // Update defines a list of elements that we detect spec updates on type Update struct { - specUpdated bool - memberUpdated bool + specUpdated bool + memberUpdated bool + networkUpdated bool + peerUpdated bool } func (u *Update) SpecUpdated() bool { @@ -38,6 +40,14 @@ func (u *Update) MemberUpdated() bool { return u.memberUpdated } +func (u *Update) NetworkUpdated() bool { + return u.networkUpdated +} + +func (u *Update) PeerUpdated() bool { + return u.peerUpdated +} + // GetUpdateStackWithTrues is a helper method to print updates that have been detected func (u *Update) GetUpdateStackWithTrues() string { stack := "" @@ -50,6 +60,14 @@ func (u *Update) GetUpdateStackWithTrues() string { stack += "memberUpdated " } + if u.networkUpdated { + stack += "networkUpdated " + } + + if u.peerUpdated { + stack += "peerUpdated " + } + if len(stack) == 0 { stack = "emptystack " } diff --git a/controllers/network/network_controller.go b/controllers/network/network_controller.go index 7cf3aaf2..1bb18d1e 100644 --- a/controllers/network/network_controller.go +++ b/controllers/network/network_controller.go @@ -117,6 +117,16 @@ func add(mgr manager.Manager, r *ReconcileNetwork) error { return err } + channelPredictFuncs := predicate.Funcs{ + CreateFunc: r.ChannelCreateFunc, + DeleteFunc: r.ChannelDeleteFunc, + } + + err = c.Watch(&source.Kind{Type: ¤t.Channel{}}, &handler.EnqueueRequestForObject{}, channelPredictFuncs) + if err != nil { + return err + } + return nil } diff --git a/controllers/network/predict.go b/controllers/network/predict.go index be266103..a3bd340e 100644 --- a/controllers/network/predict.go +++ b/controllers/network/predict.go @@ -24,11 +24,14 @@ import ( "reflect" current "github.com/IBM-Blockchain/fabric-operator/api/v1beta1" + k8sclient "github.com/IBM-Blockchain/fabric-operator/pkg/k8s/controllerclient" bcrbac "github.com/IBM-Blockchain/fabric-operator/pkg/rbac" "github.com/IBM-Blockchain/fabric-operator/pkg/user" "github.com/go-test/deep" + "github.com/pkg/errors" "gopkg.in/yaml.v2" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/event" ) @@ -137,3 +140,86 @@ func (r *ReconcileNetwork) DeleteFunc(e event.DeleteEvent) bool { } return false } + +func (r *ReconcileNetwork) ChannelCreateFunc(e event.CreateEvent) bool { + channel := e.Object.(*current.Channel) + log.Info(fmt.Sprintf("Create event detected for channel '%s'", channel.GetName())) + + err := r.AddChannel(channel.Spec.Network, channel.Name) + if err != nil { + log.Error(err, fmt.Sprintf("Channel %s in Network %s", channel.GetName(), channel.Spec.Network)) + } + return false +} + +func (r *ReconcileNetwork) AddChannel(netns string, channs string) error { + var err error + network := ¤t.Network{} + err = r.client.Get(context.TODO(), types.NamespacedName{ + Name: netns, + }, network) + if err != nil { + return err + } + + conflict := network.Status.AddChannel(channs) + // conflict detected,do not need to PatchStatus + if conflict { + return errors.Errorf("channel %s already exist in network %s", channs, netns) + } + + err = r.client.PatchStatus(context.TODO(), network, nil, k8sclient.PatchOption{ + Resilient: &k8sclient.ResilientPatch{ + Retry: 2, + Into: ¤t.Network{}, + Strategy: client.MergeFrom, + }, + }) + if err != nil { + return err + } + + return nil +} + +func (r *ReconcileNetwork) ChannelDeleteFunc(e event.DeleteEvent) bool { + channel := e.Object.(*current.Channel) + log.Info(fmt.Sprintf("Delete event detected for channel '%s'", channel.GetName())) + + err := r.DeleteChannel(channel.Spec.Network, channel.Name) + if err != nil { + log.Error(err, fmt.Sprintf("Channel %s in Network %s", channel.GetName(), channel.Spec.Network)) + } + return false +} + +func (r *ReconcileNetwork) DeleteChannel(netns, channs string) error { + var err error + network := ¤t.Network{} + err = r.client.Get(context.TODO(), types.NamespacedName{ + Name: netns, + }, network) + if err != nil { + return err + } + + exist := network.Status.DeleteChannel(channs) + + // channel do not exist in this network ,do not need to PatchStatus + if !exist { + return errors.Errorf("channel %s not exist in network %s", channs, netns) + } + + err = r.client.PatchStatus(context.TODO(), network, nil, k8sclient.PatchOption{ + Resilient: &k8sclient.ResilientPatch{ + Retry: 2, + Into: ¤t.Network{}, + Strategy: client.MergeFrom, + }, + }) + if err != nil { + return err + } + + return nil +} diff --git a/controllers/organization/predict.go b/controllers/organization/predict.go index 7412d6c3..d8f9a403 100644 --- a/controllers/organization/predict.go +++ b/controllers/organization/predict.go @@ -28,6 +28,7 @@ import ( current "github.com/IBM-Blockchain/fabric-operator/api/v1beta1" k8sclient "github.com/IBM-Blockchain/fabric-operator/pkg/k8s/controllerclient" "github.com/IBM-Blockchain/fabric-operator/pkg/user" + "github.com/IBM-Blockchain/fabric-operator/pkg/util" "github.com/go-test/deep" "github.com/pkg/errors" "gopkg.in/yaml.v2" @@ -98,7 +99,7 @@ func (r *ReconcileOrganization) PredictOrganizationCreate(organization *current. } } - added, removed := current.DifferClients(existingOrg.Spec.Clients, organization.Spec.Clients) + added, removed := util.DifferArray(existingOrg.Spec.Clients, organization.Spec.Clients) if len(added) != 0 || len(removed) != 0 { update.clientsUpdated = true update.clientsRemoved = strings.Join(removed, ",") @@ -155,7 +156,7 @@ func (r *ReconcileOrganization) PredictOrganizationUpdate(oldOrg *current.Organi } } - added, removed := current.DifferClients(oldOrg.Spec.Clients, newOrg.Spec.Clients) + added, removed := util.DifferArray(oldOrg.Spec.Clients, newOrg.Spec.Clients) if len(added) != 0 || len(removed) != 0 { update.clientsUpdated = true update.clientsRemoved = strings.Join(removed, ",") diff --git a/go.mod b/go.mod index 14a22fc7..b7a90e3c 100644 --- a/go.mod +++ b/go.mod @@ -10,10 +10,11 @@ require ( github.com/go-test/deep v1.0.2 github.com/gogo/protobuf v1.3.2 github.com/hyperledger/fabric v1.4.11 - github.com/hyperledger/fabric-protos-go v0.0.0-20210911123859-041d13f0980c + github.com/hyperledger/fabric-protos-go v0.0.0-20211118165945-23d738fc3553 + github.com/hyperledger/fabric-sdk-go v0.0.0-20221020141211-7af45cede6af github.com/imdario/mergo v0.3.12 github.com/lib/pq v1.8.0 - github.com/maxbrunsfeld/counterfeiter/v6 v6.2.3 + github.com/maxbrunsfeld/counterfeiter/v6 v6.5.0 github.com/onsi/ginkgo/v2 v2.1.4 github.com/onsi/gomega v1.19.0 github.com/openshift/api v3.9.1-0.20190924102528-32369d4db2ad+incompatible @@ -63,6 +64,7 @@ require ( github.com/go-sql-driver/mysql v1.5.0 // indirect github.com/go-stack/stack v1.8.1 // indirect github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect + github.com/golang/mock v1.4.3 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/certificate-transparency-go v1.0.21 // indirect @@ -78,6 +80,7 @@ require ( github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hyperledger/fabric-amcl v0.0.0-20210603140002-2670f91851c8 // indirect + github.com/hyperledger/fabric-config v0.0.5 // indirect github.com/hyperledger/fabric-lib-go v1.0.0 // indirect github.com/jcmturner/aescts/v2 v2.0.0 // indirect github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect @@ -90,7 +93,7 @@ require ( github.com/json-iterator/go v1.1.10 // indirect github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46 // indirect github.com/klauspost/compress v1.13.6 // indirect - github.com/magiconair/properties v1.8.1 // indirect + github.com/magiconair/properties v1.8.5 // indirect github.com/mailru/easyjson v0.7.0 // indirect github.com/mattn/go-sqlite3 v1.14.9 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect @@ -101,7 +104,7 @@ require ( github.com/modern-go/reflect2 v1.0.1 // indirect github.com/onsi/ginkgo v1.16.5 // indirect github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 // indirect - github.com/pelletier/go-toml v1.2.0 // indirect + github.com/pelletier/go-toml v1.9.4 // indirect github.com/pierrec/lz4 v2.6.1+incompatible // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.11.0 // indirect @@ -109,9 +112,9 @@ require ( github.com/prometheus/common v0.10.0 // indirect github.com/prometheus/procfs v0.2.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect - github.com/spf13/afero v1.2.2 // indirect + github.com/spf13/afero v1.6.0 // indirect github.com/spf13/cast v1.3.1 // indirect - github.com/spf13/jwalterweatherman v1.0.0 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/testify v1.7.1-0.20210116013205-6990a05d54c2 // indirect github.com/subosito/gotenv v1.2.0 // indirect diff --git a/go.sum b/go.sum index 2870d108..9940bdf2 100644 --- a/go.sum +++ b/go.sum @@ -40,7 +40,6 @@ github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSY github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= @@ -50,6 +49,7 @@ github.com/IBM/idemix v0.0.0-20220113150823-80dd4cb2d74e/go.mod h1:tBeRCKH37b2Ok github.com/IBM/mathlib v0.0.0-20220112091634-0a7378db6912/go.mod h1:WZGhleRZVSAg25iKkiWXHacTkui2CY1cyJMBOgpQwh8= github.com/IBM/mathlib v0.0.0-20220414125002-6f78dce8f91c h1:lM14BP0219xYH0wSthXTcK0ARbmw0vCGxysyJSDWKmk= github.com/IBM/mathlib v0.0.0-20220414125002-6f78dce8f91c/go.mod h1:WZGhleRZVSAg25iKkiWXHacTkui2CY1cyJMBOgpQwh8= +github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= @@ -212,6 +212,7 @@ github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -320,10 +321,16 @@ github.com/hyperledger/fabric v0.0.0-20191027202024-115c7a2205a6 h1:Nsiq4GTvhs5t github.com/hyperledger/fabric v0.0.0-20191027202024-115c7a2205a6/go.mod h1:tGFAOCT696D3rG0Vofd2dyWYLySHlh0aQjf7Q1HAju0= github.com/hyperledger/fabric-amcl v0.0.0-20210603140002-2670f91851c8 h1:BCR8ZlOZ+deUbWxyY6fpoY8LbB7PR5wGGwCTvWQOU2g= github.com/hyperledger/fabric-amcl v0.0.0-20210603140002-2670f91851c8/go.mod h1:X+DIyUsaTmalOpmpQfIvFZjKHQedrURQ5t4YqquX7lE= +github.com/hyperledger/fabric-config v0.0.5 h1:khRkm8U9Ghdg8VmZfptgzCFlCzrka8bPfUkM+/j6Zlg= +github.com/hyperledger/fabric-config v0.0.5/go.mod h1:YpITBI/+ZayA3XWY5lF302K7PAsFYjEEPM/zr3hegA8= github.com/hyperledger/fabric-lib-go v1.0.0 h1:UL1w7c9LvHZUSkIvHTDGklxFv2kTeva1QI2emOVc324= github.com/hyperledger/fabric-lib-go v1.0.0/go.mod h1:H362nMlunurmHwkYqR5uHL2UDWbQdbfz74n8kbCFsqc= -github.com/hyperledger/fabric-protos-go v0.0.0-20210911123859-041d13f0980c h1:QPhSriw6EzMOj/d7gcGiKEvozVvQ5HLk9UWie4KAvSs= +github.com/hyperledger/fabric-protos-go v0.0.0-20200424173316-dd554ba3746e/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0= github.com/hyperledger/fabric-protos-go v0.0.0-20210911123859-041d13f0980c/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0= +github.com/hyperledger/fabric-protos-go v0.0.0-20211118165945-23d738fc3553 h1:E9f0v1q4EDfrE+0LdkxVtdYKAZ7PGCaj1bBx45R9yEQ= +github.com/hyperledger/fabric-protos-go v0.0.0-20211118165945-23d738fc3553/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0= +github.com/hyperledger/fabric-sdk-go v0.0.0-20221020141211-7af45cede6af h1:kxJp8MbPmku9oy8BXbydGcP68MW9kp45hz6mRKZ4N0c= +github.com/hyperledger/fabric-sdk-go v0.0.0-20221020141211-7af45cede6af/go.mod h1:JRplpKBeAvXjsBhOCCM/KvMRUbdDyhsAh80qbXzKc10= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= @@ -349,8 +356,6 @@ github.com/jmhodges/clock v0.0.0-20160418191101-880ee4c33548/go.mod h1:hGT6jSUVz github.com/jmoiron/sqlx v0.0.0-20180124204410-05cef0741ade/go.mod h1:IiEW3SEiiErVyFdH8NTuWjSifiEQKUoyK3LNqr2kCHU= github.com/jmoiron/sqlx v1.3.4 h1:wv+0IJZfL5z0uZoUjlpKgHkgaFSYD+r9CfrXjEXsO7w= github.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= -github.com/joefitzgerald/rainbow-reporter v0.1.0 h1:AuMG652zjdzI0YCCnXAqATtRBpGXMcAnrajcaTrSeuo= -github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -372,6 +377,7 @@ github.com/kisom/goutils v1.1.0/go.mod h1:+UBTfd78habUYWFbNWTJNG+jNG/i/lGURakr4A github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -392,8 +398,9 @@ github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg= github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM= @@ -416,8 +423,8 @@ github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/maxbrunsfeld/counterfeiter/v6 v6.2.3 h1:z1lXirM9f9WTcdmzSZahKh/t+LCqPiiwK2/DB1kLlI4= -github.com/maxbrunsfeld/counterfeiter/v6 v6.2.3/go.mod h1:1ftk08SazyElaaNvmqAfZWGwJzshjCfBXDLoQtPAMNk= +github.com/maxbrunsfeld/counterfeiter/v6 v6.5.0 h1:rBhB9Rls+yb8kA4x5a/cWxOufWfXt24E+kq4YlbGj3g= +github.com/maxbrunsfeld/counterfeiter/v6 v6.5.0/go.mod h1:fJ0UAZc1fx3xZhU4eSHQDJ1ApFmTVhp5VTpV9tm2ogg= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/pkcs11 v1.0.3 h1:iMwmD7I5225wv84WxIG/bmxz9AXjWvTWIbM/TYHvWtw= github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= @@ -430,6 +437,7 @@ github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS4 github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8= github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= @@ -456,7 +464,6 @@ github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+ github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= @@ -482,8 +489,9 @@ github.com/operator-framework/api v0.10.0/go.mod h1:tV0BUNvly7szq28ZPBXhjp1Sqg5y github.com/operator-framework/operator-lib v0.8.0 h1:w3y2/VEQXYui7DPAe0DAIEmTO22VDRzl2qRSxVDqeCg= github.com/operator-framework/operator-lib v0.8.0/go.mod h1:2Z32GTTJUz2/f+OKcoJXsVnAyRwcXx7mGmQsdhIAIIE= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= +github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= @@ -491,6 +499,7 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= @@ -510,9 +519,7 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8= -github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -525,8 +532,9 @@ github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIK github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= @@ -534,13 +542,15 @@ github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3 github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= -github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.1.1/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= @@ -625,7 +635,9 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -685,7 +697,6 @@ golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -740,7 +751,6 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -841,7 +851,6 @@ golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200301222351-066e0c02454c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= diff --git a/pkg/connector/connector.go b/pkg/connector/connector.go new file mode 100644 index 00000000..c2dfb46b --- /dev/null +++ b/pkg/connector/connector.go @@ -0,0 +1,75 @@ +/* + * Copyright contributors to the Hyperledger Fabric Operator project + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package connector + +import ( + fabsdkconfig "github.com/hyperledger/fabric-sdk-go/pkg/core/config" + "github.com/hyperledger/fabric-sdk-go/pkg/fabsdk" + "github.com/pkg/errors" +) + +// Connector is used to connect with blockchain network +type Connector struct { + // profile which defines all required connection info + profile []byte + + sdk *fabsdk.FabricSDK +} + +// ProfileFunc defines how to get the profile(marshalled) +type ProfileFunc func() ([]byte, error) + +func NewConnector(profile ProfileFunc) (*Connector, error) { + binaryData, err := profile() + if err != nil { + return nil, errors.Wrap(err, "failed to get connector profile") + } + c := &Connector{ + profile: binaryData, + } + err = c.init() + if err != nil { + return nil, errors.Wrap(err, "failed to init connector") + + } + return c, nil +} + +func (c *Connector) init() error { + // only yaml supported for now + sdk, err := fabsdk.New(fabsdkconfig.FromRaw(c.profile, "yaml")) + if err != nil { + return err + } + c.sdk = sdk + + return nil +} + +func (c *Connector) Close() { + if c.sdk == nil { + return + } + c.sdk.Close() + c.sdk = nil +} + +func (c *Connector) SDK() *fabsdk.FabricSDK { + return c.sdk +} diff --git a/pkg/connector/profile.go b/pkg/connector/profile.go new file mode 100644 index 00000000..c67e53a5 --- /dev/null +++ b/pkg/connector/profile.go @@ -0,0 +1,312 @@ +/* + * Copyright contributors to the Hyperledger Fabric Operator project + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package connector + +import ( + "context" + "encoding/base64" + "encoding/json" + "net/url" + + current "github.com/IBM-Blockchain/fabric-operator/api/v1beta1" + "github.com/IBM-Blockchain/fabric-operator/pkg/k8s/controllerclient" + "github.com/IBM-Blockchain/fabric-operator/pkg/util/pointer" + "github.com/pkg/errors" + "gopkg.in/yaml.v2" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" +) + +// Profile contasins all we need to connect with a blockchain network. Currently we use embeded pem by default +type Profile struct { + Version string `yaml:"version,omitempty"` + Client `yaml:"client,omitempty"` + Channels map[string]ChannelInfo `yaml:"channels,omitempty"` + Organizations map[string]OrganizationInfo `yaml:"organizations,omitempty"` + // Orderers defines all orderer endpoints which can be used + Orderers map[string]NodeEndpoint `yaml:"orderers,omitempty"` + // Peers defines all peer endpoints which can be used + Peers map[string]NodeEndpoint `yaml:"peers,omitempty"` +} + +// Client defines who is trying to connect with network +type Client struct { + Organization string `yaml:"organization,omitempty"` + Logging `yaml:"logging,omitempty"` + // CryptoConfig `yaml:"cryptoconfig,omitempty"` +} + +type Logging struct { + Level string `yaml:"level,omitempty"` +} + +type CryptoConfig struct { + Path string `yaml:"path,omitempty"` +} + +// ChannelInfo defines configurations when connect to this channel +type ChannelInfo struct { + // Peers which can be used to connect to this channel + Peers map[string]PeerInfo `yaml:"peers,omitempty"` +} + +type PeerInfo struct { + EndorsingPeer *bool `yaml:"endorsingPeer,omitempty"` + ChaincodeQuery *bool `yaml:"chaincodeQuery,omitempty"` + LedgerQuery *bool `yaml:"ledgerQuery,omitempty"` + EventSource *bool `yaml:"eventSource,omitempty"` +} + +// OrganizationInfo defines a organization along with its users and peers +type OrganizationInfo struct { + MSPID string `yaml:"mspid,omitempty"` + Users map[string]User `yaml:"users,omitempty"` + // CryptoPath string `yaml:"cryptoPath,omitempty"` + Peers []string `yaml:"peers,omitempty"` +} + +// User is the ca identity which has a private key(embeded pem) and signed certificate(embeded pem) +type User struct { + Name string `yaml:"name,omitempty"` + Key Pem `yaml:"key,omitempty"` + Cert Pem `yaml:"cert,omitempty"` +} + +type Pem struct { + Pem string `yaml:"pem,omitempty"` +} + +type NodeEndpoint struct { + URL string `yaml:"url,omitempty"` + TLSCACerts `yaml:"tlsCACerts,omitempty"` +} + +type TLSCACerts struct { + Path string `yaml:"path,omitempty"` + Pem string `yaml:"pem,omitempty"` +} + +/* Default in Profile */ + +func DefaultProfile(baseDir string, org string) *Profile { + return &Profile{ + Version: "1.0.0", + Client: *DefaultClient(baseDir, org), + Channels: make(map[string]ChannelInfo), + Organizations: make(map[string]OrganizationInfo), + Peers: make(map[string]NodeEndpoint), + Orderers: make(map[string]NodeEndpoint), + } +} + +func DefaultClient(baseDir string, org string) *Client { + return &Client{ + Organization: org, + Logging: Logging{ + Level: "info", + }, + // CryptoConfig: CryptoConfig{ + // Path: baseDir, + // }, + } +} + +func DefaultChannelInfo() *ChannelInfo { + return &ChannelInfo{ + Peers: make(map[string]PeerInfo), + } +} + +func DefaultPeerInfo() *PeerInfo { + return &PeerInfo{ + EndorsingPeer: pointer.True(), + ChaincodeQuery: pointer.True(), + LedgerQuery: pointer.True(), + EventSource: pointer.True(), + } +} + +/* Client settings in Profile */ + +func (profile *Profile) SetClient(clientorg string) { + // profile.Client.CryptoConfig.Path = baseDir + profile.Client.Organization = clientorg +} + +func (profile *Profile) SetChannel(channelID string, peers ...current.NamespacedName) { + info, ok := profile.Channels[channelID] + if !ok { + info.Peers = make(map[string]PeerInfo) + } + for _, p := range peers { + info.Peers[p.String()] = *DefaultPeerInfo() + } +} + +/* Channel settings in Profile */ + +func (profile *Profile) GetChannel(channelID string) ChannelInfo { + if profile.Channels == nil { + profile.Channels = make(map[string]ChannelInfo) + return ChannelInfo{ + Peers: map[string]PeerInfo{}, + } + } + return profile.Channels[channelID] +} + +/* Organization settings in Profile */ + +func (profile *Profile) GetOrganization(organization string) OrganizationInfo { + if profile.Organizations == nil { + profile.Organizations = make(map[string]OrganizationInfo) + return OrganizationInfo{ + MSPID: organization, + Users: make(map[string]User), + // CryptoPath: filepath.Join(organization, "users", "{username}", "msp"), // {org_name}/users/{username}/msp + Peers: make([]string, 0), + } + } + return profile.Organizations[organization] +} + +func (profile *Profile) SetOrganization(organization string, peers []string, users ...User) { + if profile.Organizations == nil { + profile.Organizations = make(map[string]OrganizationInfo) + } + info := OrganizationInfo{ + MSPID: organization, + Users: make(map[string]User), + // CryptoPath: filepath.Join(organization, "users", "{username}", "msp"), // {org_name}/users/{username}/msp + Peers: peers, + } + for _, user := range users { + info.Users[user.Name] = user + } + profile.Organizations[organization] = info +} + +func (profile *Profile) SetOrganizationUsers(organization string, users ...User) { + orgInfo := profile.GetOrganization(organization) + if orgInfo.Users == nil { + orgInfo.Users = make(map[string]User) + } + for _, user := range users { + orgInfo.Users[user.Name] = user + } + profile.Organizations[organization] = orgInfo +} + +func (profile *Profile) RemoveOrganization(organization string) { + if profile.Organizations == nil { + profile.Organizations = make(map[string]OrganizationInfo) + return + } + delete(profile.Organizations, organization) +} + +/* Peer settings in Profile */ + +func (profile *Profile) SetPeer(client controllerclient.Client, peer current.NamespacedName) error { + if profile.Peers == nil { + profile.Peers = make(map[string]NodeEndpoint) + } + endpoint, err := GetNodeEndpoint(client, peer) + if err != nil { + return err + } + profile.Peers[peer.String()] = endpoint + + // add peer to its organization + orgInfo := profile.GetOrganization(peer.Namespace) + orgInfo.Peers = append(orgInfo.Peers, peer.String()) + if profile.Organizations == nil { + profile.Organizations = make(map[string]OrganizationInfo) + } + profile.Organizations[peer.Namespace] = orgInfo + + return nil +} + +func (profile *Profile) RemovePeer(peer current.NamespacedName) { + if profile.Peers == nil { + profile.Peers = make(map[string]NodeEndpoint) + } + delete(profile.Peers, peer.String()) +} + +/* Orderer settings in Profile */ + +func (profile *Profile) SetOrderer(client controllerclient.Client, orderer current.NamespacedName) error { + if profile.Orderers == nil { + profile.Orderers = make(map[string]NodeEndpoint) + } + endpoint, err := GetNodeEndpoint(client, orderer) + if err != nil { + return err + } + profile.Orderers[orderer.String()] = endpoint + return nil +} + +func (profile *Profile) RemoveOrderer(orderer current.NamespacedName) { + if profile.Orderers == nil { + profile.Orderers = make(map[string]NodeEndpoint) + } + delete(profile.Orderers, orderer.String()) +} + +// GetNodeEndpoint with node(peer/orderer)'s connection profile +func GetNodeEndpoint(client controllerclient.Client, node current.NamespacedName) (NodeEndpoint, error) { + cm := &corev1.ConfigMap{} + err := client.Get(context.TODO(), types.NamespacedName{Namespace: node.Namespace, Name: node.Name + "-connection-profile"}, cm) + if err != nil { + return NodeEndpoint{}, err + } + + conn := ¤t.PeerConnectionProfile{} + if err := json.Unmarshal(cm.BinaryData["profile.json"], conn); err != nil { + return NodeEndpoint{}, err + } + + apiURL, err := url.Parse(conn.Endpoints.API) + if err != nil { + return NodeEndpoint{}, errors.Wrap(err, "invalid node api") + } + + tlsPem, err := base64.StdEncoding.DecodeString(conn.TLS.SignCerts) + if err != nil { + return NodeEndpoint{}, errors.Wrap(err, "not a valid pem format cert") + } + return NodeEndpoint{ + URL: apiURL.Host, + TLSCACerts: TLSCACerts{ + Pem: string(tlsPem), + }, + }, nil +} + +/* Marshal/Unmarshal in Profile*/ +func (profile *Profile) Marshal() ([]byte, error) { + return yaml.Marshal(profile) +} + +func (profile *Profile) Unmarshal(in []byte) error { + return yaml.Unmarshal(in, profile) +} diff --git a/pkg/initializer/channel/initializer.go b/pkg/initializer/channel/initializer.go index c210a29b..23618ccd 100644 --- a/pkg/initializer/channel/initializer.go +++ b/pkg/initializer/channel/initializer.go @@ -25,7 +25,6 @@ import ( "path/filepath" current "github.com/IBM-Blockchain/fabric-operator/api/v1beta1" - "github.com/IBM-Blockchain/fabric-operator/pkg/initializer/common/secretmanager" "github.com/IBM-Blockchain/fabric-operator/pkg/initializer/orderer/configtx" "github.com/IBM-Blockchain/fabric-operator/pkg/k8s/controllerclient" k8sclient "github.com/IBM-Blockchain/fabric-operator/pkg/k8s/controllerclient" @@ -41,19 +40,22 @@ const ( NODE = "node" ) +var ( + ErrChannelAlreadyExist = errors.New("channel already exist in target node") +) + var log = logf.Log.WithName("base_channel_initializer") type Config struct { - ConfigtxFile string - StoragePath string + StoragePath string } +// Initializer is for channel initialization type Initializer struct { Config *Config + Scheme *runtime.Scheme Client k8sclient.Client - - SecretManager *secretmanager.SecretManager } func New(client controllerclient.Client, scheme *runtime.Scheme, cfg *Config) *Initializer { @@ -63,20 +65,13 @@ func New(client controllerclient.Client, scheme *runtime.Scheme, cfg *Config) *I Config: cfg, } - initializer.SecretManager = secretmanager.New(client, scheme, nil) - return initializer } -func (i *Initializer) GetStoragePath(instance *current.Channel) string { - return filepath.Join("/", i.Config.StoragePath, instance.GetName()) -} - -func (i *Initializer) GetOrgMSPDir(instance *current.Channel, orgMSPID string) string { - return filepath.Join(i.GetStoragePath(instance), orgMSPID, "msp") -} - -func (i *Initializer) CreateOrUpdateChannel(instance *current.Channel) error { +// CreateChannel used to help create a channel within network,including: +// - create a genesis block for channel +// - join all orderer nodes into channel +func (i *Initializer) CreateChannel(instance *current.Channel) error { var err error network := ¤t.Network{} @@ -86,6 +81,8 @@ func (i *Initializer) CreateOrUpdateChannel(instance *current.Channel) error { } ordererorg := network.Labels["bestchains.network.initiator"] + + // get network's orderer nodes parentOrderer, err := i.GetParentNode(ordererorg, network.GetName()) if err != nil { return err @@ -98,31 +95,27 @@ func (i *Initializer) CreateOrUpdateChannel(instance *current.Channel) error { return err } - osn, err := NewOSNAdmin(i.Client, ordererorg, clusterNodes.Items...) + // create genesis block for channel + block, err := i.CreateGenesisBlock(instance, ordererorg, parentOrderer, clusterNodes) if err != nil { return err } - var exist = true - resp, err := osn.Query(clusterNodes.Items[0].GetName(), instance.GetName()) - if err != nil { - return err - } - if resp.StatusCode == http.StatusNotFound { - exist = false - } - // DO NOT SUPPORT UPDATE FOR NOW - if exist { - return nil - } - - block, err := i.CreateGenesisBlock(instance, ordererorg, parentOrderer, clusterNodes) + // Join all cluster nodes into this channel + osn, err := NewOSNAdmin(i.Client, ordererorg, clusterNodes.Items...) if err != nil { return err } - - // Join all cluster nodes into this channel for _, target := range clusterNodes.Items { + // make sure orderer not joined yet + resp, err := osn.Query(target.GetName(), instance.GetName()) + if err != nil { + return err + } + // continue if current orderer node already joins + if resp.StatusCode != http.StatusNotFound { + continue + } err = osn.Join(target.GetName(), block) if err != nil { return err @@ -132,18 +125,25 @@ func (i *Initializer) CreateOrUpdateChannel(instance *current.Channel) error { return nil } +// CreateGenesisBlock configures and generate a genesis block for channel startup.Here we have these limitations: +// - system channel not supported +// - Capability use `V2_0` func (i *Initializer) CreateGenesisBlock(instance *current.Channel, ordererorg string, parentOrderer *current.IBPOrderer, clusterNodes *current.IBPOrdererList) ([]byte, error) { configTx := configtx.New() - profile, err := configTx.GetProfile("Initial") + + // `Application` defines a application channel + profile, err := configTx.GetProfile("Application") if err != nil { return nil, err } + // add orderer settings into profile mspConfigs, err := i.ConfigureOrderer(instance, profile, ordererorg, parentOrderer, clusterNodes) if err != nil { return nil, err } + // add application settings into profile isUsingChannelLess := true if !isUsingChannelLess { return nil, errors.New("system channel not supported yet") @@ -153,6 +153,7 @@ func (i *Initializer) CreateGenesisBlock(instance *current.Channel, ordererorg s return nil, err } } + channelID := instance.GetName() block, err := profile.GenerateBlock(channelID, mspConfigs) if err != nil { @@ -162,6 +163,7 @@ func (i *Initializer) CreateGenesisBlock(instance *current.Channel, ordererorg s return block, nil } +// GetParentNode returns the IBPOrderer which acts as the parent in consenus cluster func (i *Initializer) GetParentNode(namespace string, parentNode string) (*current.IBPOrderer, error) { orderer := ¤t.IBPOrderer{} err := i.Client.Get(context.TODO(), types.NamespacedName{Namespace: namespace, Name: parentNode}, orderer) @@ -171,6 +173,7 @@ func (i *Initializer) GetParentNode(namespace string, parentNode string) (*curre return orderer, nil } +// GetClusterNodes returns the IBPOrderers which acts as the real consensus node func (i *Initializer) GetClusterNodes(namespace string, parentNode string) (*current.IBPOrdererList, error) { ordererList := ¤t.IBPOrdererList{} @@ -191,3 +194,13 @@ func (i *Initializer) GetClusterNodes(namespace string, parentNode string) (*cur return ordererList, nil } + +// GetStoragePath in formart `/chaninit/{channel_name}` +func (i *Initializer) GetStoragePath(instance *current.Channel) string { + return filepath.Join("/", i.Config.StoragePath, instance.GetName()) +} + +// GetOrgMSPDir returns channel organization's msp directory which will be used for genesis block generation +func (i *Initializer) GetOrgMSPDir(instance *current.Channel, orgMSPID string) string { + return filepath.Join(i.GetStoragePath(instance), orgMSPID, "msp") +} diff --git a/pkg/initializer/channel/orderer.go b/pkg/initializer/channel/orderer.go index 5a241be0..abb021fc 100644 --- a/pkg/initializer/channel/orderer.go +++ b/pkg/initializer/channel/orderer.go @@ -42,6 +42,13 @@ func (i *Initializer) ConfigureOrderer(instance *current.Channel, profile *confi return nil, err } + org := configtx.DefaultOrganization(ordererorg) + org.MSPDir = i.GetOrgMSPDir(instance, ordererorg) + err = profile.AddOrgToOrderer(org) + if err != nil { + return nil, err + } + conf := profile.Orderer mspConfigs := map[string]*msp.MSPConfig{} // only one orderer organization for now diff --git a/pkg/initializer/channel/osnadmin.go b/pkg/initializer/channel/osnadmin.go index 8e502cf0..ae7d9965 100644 --- a/pkg/initializer/channel/osnadmin.go +++ b/pkg/initializer/channel/osnadmin.go @@ -51,12 +51,14 @@ var ( ErrTargetNotFound = errors.New("target not found") ) -// OSNClient orderering service node client +// OSNAdmin wraps a client to call orderering service's admin api type OSNAdmin struct { - client k8sclient.Client - targets map[string]*Target + client k8sclient.Client + ordererorg string + targets map[string]*Target } +// Target wraps connection info of a orderer node type Target struct { URL string Client *http.Client @@ -64,32 +66,40 @@ type Target struct { func NewOSNAdmin(client k8sclient.Client, ordererorg string, targetOrderers ...current.IBPOrderer) (*OSNAdmin, error) { var err error - osn := &OSNAdmin{ - client: client, - targets: make(map[string]*Target), + + if ordererorg == "" { + return nil, errors.Errorf("osnadmin must have ordererorg configured") } + // get ordererorg' msp crypto organization := ¤t.Organization{} err = client.Get(context.TODO(), types.NamespacedName{Name: ordererorg}, organization) if err != nil { return nil, errors.Wrapf(err, "get ordererorg %s", ordererorg) } - orgmsp := &corev1.Secret{} - err = client.Get(context.TODO(), organization.GetMSPCrypto(), orgmsp) - if err != nil { - return nil, errors.Wrapf(err, "get ordererorg %s msp crypto", ordererorg) + + osn := &OSNAdmin{ + client: client, + ordererorg: ordererorg, + targets: make(map[string]*Target), } + // add all orderers into osn targets for index := range targetOrderers { - err = osn.AddTarget(orgmsp, &targetOrderers[index]) + err = osn.AddTarget(&targetOrderers[index]) if err != nil { return nil, err } } + return osn, nil } -func (osn *OSNAdmin) AddTarget(orderermsp *corev1.Secret, targetOrderer *current.IBPOrderer) error { +/* Target management */ + +func (osn *OSNAdmin) AddTarget(targetOrderer *current.IBPOrderer) error { + var err error + // get connection profile connProfile, err := GetTargetConnectionProfile(osn.client, targetOrderer) if err != nil { @@ -108,9 +118,14 @@ func (osn *OSNAdmin) AddTarget(orderermsp *corev1.Secret, targetOrderer *current return err } - // Load ordererorg's admin user tls key & cert - tlsClientKeyPem := orderermsp.Data["admin-tls-keystore"] - tlsClientCertPem := orderermsp.Data["admin-tls-signcert"] + // Load ordererorg's admin user tls key & cert from orderer org's msp crypto + ordererorgMSP := &corev1.Secret{} + err = osn.client.Get(context.TODO(), types.NamespacedName{Namespace: osn.ordererorg, Name: fmt.Sprintf("%s-msp-crypto", osn.ordererorg)}, ordererorgMSP) + if err != nil { + return errors.Wrapf(err, "get ordererorg %s msp crypto", osn.ordererorg) + } + tlsClientKeyPem := ordererorgMSP.Data["admin-tls-keystore"] + tlsClientCertPem := ordererorgMSP.Data["admin-tls-signcert"] tlsCert, err := tls.X509KeyPair(tlsClientCertPem, tlsClientKeyPem) if err != nil { return err @@ -151,30 +166,7 @@ func (osn *OSNAdmin) DeleteTarget(target string) { delete(osn.targets, target) } -func GetTargetConnectionProfile(client k8sclient.Client, orderer *current.IBPOrderer) (*current.OrdererConnectionProfile, error) { - var err error - - // consensus connection info - cm := &corev1.ConfigMap{} - err = client.Get( - context.TODO(), - types.NamespacedName{ - Name: orderer.GetName() + "-connection-profile", - Namespace: orderer.GetNamespace(), - }, - cm, - ) - if err != nil { - return nil, err - } - - connectionProfile := ¤t.OrdererConnectionProfile{} - if err := json.Unmarshal(cm.BinaryData["profile.json"], connectionProfile); err != nil { - return nil, err - } - - return connectionProfile, nil -} +/* Channel management */ // Join orderers into channel func (osn *OSNAdmin) Join(target string, blockBytes []byte) error { @@ -232,7 +224,7 @@ func checkJoinResponse(res *http.Response) bool { return false } -// List all channels +// List all channels which target has joined func (osn *OSNAdmin) List(target string) (*http.Response, error) { instance, err := osn.GetTarget(target) if err != nil { @@ -242,7 +234,7 @@ func (osn *OSNAdmin) List(target string) (*http.Response, error) { return instance.Client.Get(url) } -// Query a chanenl +// Query a channel from target func (osn *OSNAdmin) Query(target string, channelID string) (*http.Response, error) { instance, err := osn.GetTarget(target) if err != nil { @@ -256,7 +248,7 @@ func (osn *OSNAdmin) Query(target string, channelID string) (*http.Response, err return resp, err } -// ChainReady checks whether channel is ready +// WaitForChannel wait until channel is ready func (osn *OSNAdmin) WaitForChannel(target string, channelID string, duration time.Duration) error { instance, err := osn.GetTarget(target) if err != nil { @@ -279,3 +271,29 @@ func (osn *OSNAdmin) WaitForChannel(target string, channelID string, duration ti } } } + +// GetTargetConnectionProfile helps retrived target orderer's connection profile which contains its tls server cert +func GetTargetConnectionProfile(client k8sclient.Client, orderer *current.IBPOrderer) (*current.OrdererConnectionProfile, error) { + var err error + + // consensus connection info + cm := &corev1.ConfigMap{} + err = client.Get( + context.TODO(), + types.NamespacedName{ + Name: orderer.GetName() + "-connection-profile", + Namespace: orderer.GetNamespace(), + }, + cm, + ) + if err != nil { + return nil, err + } + + connectionProfile := ¤t.OrdererConnectionProfile{} + if err := json.Unmarshal(cm.BinaryData["profile.json"], connectionProfile); err != nil { + return nil, err + } + + return connectionProfile, nil +} diff --git a/pkg/initializer/orderer/configtx/configtx.go b/pkg/initializer/orderer/configtx/configtx.go index f717e70b..95a6573c 100644 --- a/pkg/initializer/orderer/configtx/configtx.go +++ b/pkg/initializer/orderer/configtx/configtx.go @@ -54,7 +54,7 @@ func GetGenesisDefaults() *TopLevel { }, }, Capabilities: map[string]bool{ - "V1_4_2": true, + "V2_0": true, }, Policies: map[string]*Policy{ "Readers": { @@ -80,7 +80,7 @@ func GetGenesisDefaults() *TopLevel { "SampleConsortium": {}, }, Capabilities: map[string]bool{ - "V1_4_3": true, + "V2_0": true, }, Policies: map[string]*Policy{ "Readers": { @@ -99,7 +99,90 @@ func GetGenesisDefaults() *TopLevel { Application: &Application{ Organizations: make([]*Organization, 0), Capabilities: map[string]bool{ - "V1_4_3": true, + "V2_0": true, + }, + Resources: &Resources{}, + Policies: map[string]*Policy{ + "Readers": { + Type: "ImplicitMeta", + Rule: "ANY Readers", + }, + "Writers": { + Type: "ImplicitMeta", + Rule: "ANY Writers", + }, + "Admins": { + Type: "ImplicitMeta", + Rule: "MAJORITY Admins", + }, + }, + ACLs: make(map[string]string), + }, + }, + "Application": { + Orderer: &Orderer{ + Organizations: []*Organization{}, + OrdererType: "etcdraft", + Addresses: []string{}, + BatchTimeout: 2 * time.Second, + BatchSize: BatchSize{ + MaxMessageCount: 500, + AbsoluteMaxBytes: 10 * 1024 * 1024, + PreferredMaxBytes: 2 * 1024 * 1024, + }, + EtcdRaft: &etcdraft.ConfigMetadata{ + Consenters: []*etcdraft.Consenter{}, + Options: &etcdraft.Options{ + TickInterval: "500ms", + ElectionTick: 10, + HeartbeatTick: 1, + MaxInflightBlocks: 5, + SnapshotIntervalSize: 20 * 1024 * 1024, // 20 MB + }, + }, + Capabilities: map[string]bool{ + "V2_0": true, + }, + Policies: map[string]*Policy{ + "Readers": { + Type: "ImplicitMeta", + Rule: "ANY Readers", + }, + "Writers": { + Type: "ImplicitMeta", + Rule: "ANY Writers", + }, + "Admins": { + Type: "ImplicitMeta", + Rule: "ANY Admins", + }, + "BlockValidation": { + Type: "ImplicitMeta", + Rule: "ANY Writers", + }, + }, + }, + Capabilities: map[string]bool{ + "V2_0": true, + }, + Policies: map[string]*Policy{ + "Readers": { + Type: "ImplicitMeta", + Rule: "ANY Readers", + }, + "Writers": { + Type: "ImplicitMeta", + Rule: "ANY Writers", + }, + "Admins": { + Type: "ImplicitMeta", + Rule: "MAJORITY Admins", + }, + }, + Application: &Application{ + Organizations: make([]*Organization, 0), + Capabilities: map[string]bool{ + "V2_0": true, }, Resources: &Resources{}, Policies: map[string]*Policy{ diff --git a/pkg/initializer/orderer/configtx/profile.go b/pkg/initializer/orderer/configtx/profile.go index f8d56947..9b094eb2 100644 --- a/pkg/initializer/orderer/configtx/profile.go +++ b/pkg/initializer/orderer/configtx/profile.go @@ -99,9 +99,10 @@ func (p *Profile) GenerateBlock(channelID string, mspConfigs map[string]*msp.MSP return nil, errors.Errorf("refusing to generate block which is missing orderer section") } - if p.Consortiums == nil { - return nil, errors.New("Genesis block does not contain a consortiums group definition. This block cannot be used for orderer bootstrap.") - } + // Application channel do not need Consortiums(we do not use system channel now) + // if p.Consortiums == nil { + // return nil, errors.New("Genesis block does not contain a consortiums group definition. This block cannot be used for orderer bootstrap.") + // } cg, err := p.NewChannelConfigGroup(mspConfigs) if err != nil { diff --git a/pkg/offering/base/channel/channel.go b/pkg/offering/base/channel/channel.go index a57d1d74..2c65a07b 100644 --- a/pkg/offering/base/channel/channel.go +++ b/pkg/offering/base/channel/channel.go @@ -24,13 +24,14 @@ import ( current "github.com/IBM-Blockchain/fabric-operator/api/v1beta1" config "github.com/IBM-Blockchain/fabric-operator/operatorconfig" + "github.com/IBM-Blockchain/fabric-operator/pkg/connector" chaninit "github.com/IBM-Blockchain/fabric-operator/pkg/initializer/channel" "github.com/IBM-Blockchain/fabric-operator/pkg/k8s/controllerclient" "github.com/IBM-Blockchain/fabric-operator/pkg/offering/common" - "github.com/IBM-Blockchain/fabric-operator/pkg/operatorerrors" bcrbac "github.com/IBM-Blockchain/fabric-operator/pkg/rbac" "github.com/IBM-Blockchain/fabric-operator/version" "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" @@ -44,6 +45,8 @@ var log = logf.Log.WithName("base_channel") type Update interface { SpecUpdated() bool MemberUpdated() bool + NetworkUpdated() bool + PeerUpdated() bool } //go:generate counterfeiter -o mocks/override.go -fake-name Override . Override @@ -57,7 +60,6 @@ type Channel interface { Initialize(instance *current.Channel, update Update) error ReconcileManagers(instance *current.Channel, update Update) error CheckStates(instance *current.Channel, update Update) (common.Result, error) - Reconcile(instance *current.Channel, update Update) (common.Result, error) } var _ Channel = (*BaseChannel)(nil) @@ -98,25 +100,6 @@ func (channel *BaseChannel) CreateManagers() { channel.RBACManager = bcrbac.NewRBACManager(channel.Client, nil) } -// Reconcile on Channel upon Update -func (channel *BaseChannel) Reconcile(instance *current.Channel, update Update) (common.Result, error) { - var err error - - if err = channel.PreReconcileChecks(instance, update); err != nil { - return common.Result{}, errors.Wrap(err, "failed on prereconcile checks") - } - - if err = channel.Initialize(instance, update); err != nil { - return common.Result{}, operatorerrors.Wrap(err, operatorerrors.ChannelInitializationFailed, "failed to initialize channel") - } - - if err = channel.ReconcileManagers(instance, update); err != nil { - return common.Result{}, errors.Wrap(err, "failed to reconcile managers") - } - - return channel.CheckStates(instance, update) -} - // PreReconcileChecks on Channel upon Update func (channel *BaseChannel) PreReconcileChecks(instance *current.Channel, update Update) error { var err error @@ -155,10 +138,13 @@ func (channel *BaseChannel) PreReconcileChecks(instance *current.Channel, update // Initialize on Channel upon Update func (baseChan *BaseChannel) Initialize(instance *current.Channel, update Update) error { - err := baseChan.Initializer.CreateOrUpdateChannel(instance) + err := baseChan.Initializer.CreateChannel(instance) if err != nil { return err } + + // Patch status + return nil } @@ -177,9 +163,37 @@ func (baseChan *BaseChannel) ReconcileManagers(instance *current.Channel, update if err != nil { return err } + + // Channel changed or network changed or peer updated + if update.SpecUpdated() || update.NetworkUpdated() || update.PeerUpdated() { + err = baseChan.ReconcileConnectionProfile(instance, update) + if err != nil { + return err + } + } + + if update.PeerUpdated() { + for _, p := range instance.Spec.Peers { + err = baseChan.ReconcilePeer(instance, p) + if err != nil { + return errors.Wrap(err, "failed to patch channel status") + } + } + } + return nil } +// CheckStates on Channel(do nothing) +func (baseChan *BaseChannel) CheckStates(instance *current.Channel, update Update) (common.Result, error) { + return common.Result{ + Status: ¤t.CRStatus{ + Type: current.ChannelCreated, + Version: version.Operator, + }, + }, nil +} + func (baseChan *BaseChannel) SetOwnerReference(instance *current.Channel, update Update) error { var err error @@ -220,14 +234,119 @@ func (baseChan *BaseChannel) ReconcileRBAC(instance *current.Channel, update Upd return nil } -// CheckStates on Channel -func (baseChan *BaseChannel) CheckStates(instance *current.Channel, update Update) (common.Result, error) { - return common.Result{ - Status: ¤t.CRStatus{ - Type: current.ChannelCreated, - Version: version.Operator, +// ReconcileConnectionProfile generates connection profile for this channel +func (baseChan *BaseChannel) ReconcileConnectionProfile(instance *current.Channel, update Update) error { + var err error + + network := ¤t.Network{} + err = baseChan.Client.Get(context.TODO(), types.NamespacedName{Name: instance.Spec.Network}, network) + if err != nil { + return err + } + ordererorg := network.Labels["bestchains.network.initiator"] + clusterNodes, err := baseChan.Initializer.GetClusterNodes(ordererorg, network.GetName()) + if err != nil { + return err + } + profile, err := baseChan.GenerateChannelConnProfile("", instance, clusterNodes) + if err != nil { + return err + } + binaryData, err := profile.Marshal() + if err != nil { + return err + } + cm := &corev1.ConfigMap{ + ObjectMeta: v1.ObjectMeta{ + Name: instance.GetConnectionPorfile(), + Namespace: baseChan.Config.Operator.Namespace, }, - }, nil + BinaryData: map[string][]byte{ + "profile.yaml": binaryData, + }, + } + err = baseChan.Client.CreateOrUpdate(context.TODO(), cm, controllerclient.CreateOrUpdateOption{ + Owner: instance, + Scheme: baseChan.Scheme, + }) + if err != nil { + return err + } + return nil +} + +func (baseChan *BaseChannel) GenerateChannelConnProfile(clientOrg string, channel *current.Channel, clusterNodes *current.IBPOrdererList) (*connector.Profile, error) { + var err error + + basedir := baseChan.Initializer.GetStoragePath(channel) + + var orgs = make([]string, len(channel.Spec.Members)) + for index, m := range channel.Spec.Members { + orgs[index] = m.GetName() + } + if clientOrg == "" && len(orgs) > 0 { + clientOrg = orgs[0] + } + + // default connprofile with default client + profile := connector.DefaultProfile(basedir, clientOrg) + + // Channel + profile.SetChannel(channel.GetChannelID(), channel.Spec.Peers...) + + // Peers + peers := make(map[string][]string) + for _, p := range channel.Status.PeerConditions { + // only joined peer can be appended into connection profile + if p.Type != current.PeerJoined { + continue + } + err = profile.SetPeer(baseChan.Client, p.NamespacedName) + if err != nil { + return nil, err + } + // cache to peers + _, ok := peers[p.Namespace] + if !ok { + peers[p.Namespace] = make([]string, 0) + } + peers[p.Namespace] = append(peers[p.Namespace], p.String()) + } + + // Orderers + for _, o := range clusterNodes.Items { + err = profile.SetOrderer(baseChan.Client, current.NamespacedName{Namespace: o.GetNamespace(), Name: o.GetName()}) + if err != nil { + return nil, err + } + } + + // Organizations + for _, org := range orgs { + organization := ¤t.Organization{} + err = baseChan.Client.Get(context.TODO(), types.NamespacedName{Name: org}, organization) + if err != nil { + return nil, errors.Wrap(err, "failed to find organization") + } + // read organization admin's secret + orgMSPSecret := &corev1.Secret{} + err = baseChan.Client.Get(context.TODO(), types.NamespacedName{Namespace: org, Name: fmt.Sprintf("%s-msp-crypto", org)}, orgMSPSecret) + if err != nil { + return nil, errors.Wrap(err, "failed to get channel connection profile") + } + adminUser := connector.User{ + Name: organization.Spec.Admin, + Key: connector.Pem{ + Pem: string(orgMSPSecret.Data["admin-keystore"]), + }, + Cert: connector.Pem{ + Pem: string(orgMSPSecret.Data["admin-signcert"]), + }, + } + profile.SetOrganization(org, peers[org], adminUser) + } + + return profile, nil } // GetLabels from instance.GetLabels diff --git a/pkg/offering/base/channel/peer.go b/pkg/offering/base/channel/peer.go new file mode 100644 index 00000000..7f2762d9 --- /dev/null +++ b/pkg/offering/base/channel/peer.go @@ -0,0 +1,170 @@ +/* + * Copyright contributors to the Hyperledger Fabric Operator project + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package channel + +import ( + "context" + "fmt" + "time" + + current "github.com/IBM-Blockchain/fabric-operator/api/v1beta1" + "github.com/IBM-Blockchain/fabric-operator/pkg/connector" + "github.com/IBM-Blockchain/fabric-operator/pkg/k8s/controllerclient" + "github.com/hyperledger/fabric-sdk-go/pkg/client/resmgmt" + "github.com/hyperledger/fabric-sdk-go/pkg/fabsdk" + "github.com/pkg/errors" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/wait" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const ( + // pollDuration in each poll + pollDuration = 5 * time.Second + // pollTimeout in WaitPeer + pollTimeout = 10 * pollDuration +) + +// ReconcilePeer called when peer joines/leaves +func (baseChan *BaseChannel) ReconcilePeer(instance *current.Channel, peer current.NamespacedName) error { + var err error + + err = baseChan.CheckPeer(peer) + if err != nil { + return errors.Wrap(err, "check peer") + } + + err = baseChan.JoinChannel(instance.GetName(), peer) + + index, condition := instance.GetPeerCondition(peer) + if err != nil { + // patch error status to channel + log.Error(err, "failed to reconcile peer", "peer", peer.String()) + condition.Type = current.PeerError + condition.Status = v1.ConditionTrue + condition.Reason = err.Error() + condition.LastTransitionTime = v1.Now() + } else { + condition.Type = current.PeerJoined + condition.Status = v1.ConditionTrue + condition.LastTransitionTime = v1.Now() + } + + if index != -1 { + instance.Status.PeerConditions[index] = condition + } else { + instance.Status.PeerConditions = append(instance.Status.PeerConditions, condition) + } + err = baseChan.Client.PatchStatus(context.TODO(), instance, nil, controllerclient.PatchOption{ + Resilient: &controllerclient.ResilientPatch{ + Retry: 2, + Into: ¤t.Channel{}, + Strategy: client.MergeFrom, + }, + }) + if err != nil { + return errors.Wrap(err, "failed to patch channel status") + } + + return nil +} + +// CheckPeer make sure peer is at good status +func (baseChan *BaseChannel) CheckPeer(peer current.NamespacedName) error { + var err error + peerDeploy := appsv1.Deployment{} + err = wait.Poll(pollDuration, pollTimeout, func() (bool, error) { + log.Info(fmt.Sprintf("CheckPeer: poll deployment %s status", peer.String())) + err := baseChan.Client.Get(context.TODO(), types.NamespacedName{Namespace: peer.Namespace, Name: peer.Name}, &peerDeploy) + if err != nil { + if !k8serrors.IsNotFound(err) { + return false, err + } + return false, nil + } + + if peerDeploy.Status.AvailableReplicas != *peerDeploy.Spec.Replicas { + return false, nil + } + return true, nil + }) + if err != nil { + return errors.Errorf("exceed the poll timeout of %f seconds", pollTimeout.Seconds()) + } + return nil +} + +// JoinChannel calls peer api to join it into a existing channel +func (baseChan *BaseChannel) JoinChannel(channelID string, peer current.NamespacedName) error { + c, err := connector.NewConnector(baseChan.ConnectorProfile(channelID, peer)) + if err != nil { + return err + } + + peerOrg := peer.Namespace + organization := ¤t.Organization{} + err = baseChan.Client.Get(context.TODO(), types.NamespacedName{Name: peerOrg}, organization) + if err != nil { + return err + } + adminContext := c.SDK().Context(fabsdk.WithUser(organization.Spec.Admin), fabsdk.WithOrg(peerOrg)) + client, err := resmgmt.New(adminContext) + if err != nil { + return err + } + err = client.JoinChannel(channelID, resmgmt.WithTargetEndpoints(peer.String())) + if err != nil { + return errors.Wrap(err, "failed to join peer into channel") + } + return nil +} + +// ConnectorProfile customizes channel connection profile with peer info +func (baseChan *BaseChannel) ConnectorProfile(channelID string, peer current.NamespacedName) connector.ProfileFunc { + return func() ([]byte, error) { + var err error + + cm := &corev1.ConfigMap{} + err = baseChan.Client.Get(context.TODO(), types.NamespacedName{Namespace: baseChan.Config.Operator.Namespace, Name: fmt.Sprintf("chan-%s-connection-profile", channelID)}, cm) + if err != nil { + return nil, errors.Wrap(err, "failed to get channel connection profile") + } + profile := &connector.Profile{} + err = profile.Unmarshal(cm.BinaryData["profile.yaml"]) + if err != nil { + return nil, errors.Wrap(err, "invalid channel connection profile") + } + + profile.Client.Organization = peer.Namespace + // add peer under channel section + info := profile.GetChannel(channelID) + info.Peers[peer.String()] = *connector.DefaultPeerInfo() + // add peer under peers&organization + err = profile.SetPeer(baseChan.Client, current.NamespacedName{Name: peer.Name, Namespace: peer.Namespace}) + if err != nil { + return nil, errors.Wrap(err, "failed to add current peer into connection profile") + } + + return profile.Marshal() + } +} diff --git a/pkg/offering/base/network/override/order.go b/pkg/offering/base/network/override/order.go index 01afe434..b4db11b2 100644 --- a/pkg/offering/base/network/override/order.go +++ b/pkg/offering/base/network/override/order.go @@ -140,7 +140,7 @@ func (o *Override) updateEnrollment(instance *current.Network, orderer *current. v.Enrollment.TLS.CAPort = caURL.Port() } if v.Enrollment.TLS.CAName == "" { - v.Enrollment.TLS.CAName = "ca" + v.Enrollment.TLS.CAName = "tlsca" } if v.Enrollment.TLS.CATLS == nil { v.Enrollment.TLS.CATLS = ¤t.CATLS{CACert: profile.TLS.Cert} diff --git a/pkg/offering/base/organization/initializer.go b/pkg/offering/base/organization/initializer.go index 53f4682f..40b48eac 100644 --- a/pkg/offering/base/organization/initializer.go +++ b/pkg/offering/base/organization/initializer.go @@ -147,7 +147,7 @@ func (i *Initializer) WaitForCA(instance *current.Organization) error { caDeploy := appsv1.Deployment{} deployment := instance.GetNamespaced() err = wait.Poll(10*time.Second, 10*maxRetryCount*time.Second, func() (bool, error) { - log.Info(fmt.Sprintf("WatForCA: poll deployment %s status", deployment.String())) + log.Info(fmt.Sprintf("WaitForCA: poll deployment %s status", deployment.String())) err := i.Client.Get(context.TODO(), deployment, &caDeploy) if err != nil { if !k8serrors.IsNotFound(err) { diff --git a/pkg/offering/base/peer/peer.go b/pkg/offering/base/peer/peer.go index 7fb4b795..97ccbc6e 100644 --- a/pkg/offering/base/peer/peer.go +++ b/pkg/offering/base/peer/peer.go @@ -48,6 +48,7 @@ import ( "github.com/IBM-Blockchain/fabric-operator/pkg/offering/common/reconcilechecks" "github.com/IBM-Blockchain/fabric-operator/pkg/operatorerrors" "github.com/IBM-Blockchain/fabric-operator/pkg/restart" + "github.com/IBM-Blockchain/fabric-operator/pkg/user" "github.com/IBM-Blockchain/fabric-operator/pkg/util" "github.com/IBM-Blockchain/fabric-operator/version" "github.com/pkg/errors" @@ -58,6 +59,7 @@ import ( v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" k8sclient "sigs.k8s.io/controller-runtime/pkg/client" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -365,6 +367,12 @@ func (p *Peer) Initialize(instance *current.IBPPeer, update Update) error { // two, one should handle initialization during the create event of a CR and the other // should update events + // ReconcileUser when IAM Enabled + err = p.ReconcileUser(instance, update) + if err != nil { + return err + } + // Service account is required by HSM init job if err := p.ReconcilePeerRBAC(instance); err != nil { return err @@ -463,6 +471,23 @@ func (p *Peer) Initialize(instance *current.IBPPeer, update Update) error { return nil } +// ReconcileUser on IBPPeer upon Update +func (p *Peer) ReconcileUser(instance *current.IBPPeer, update Update) (err error) { + if !p.Config.OrganizationInitConfig.IAMEnabled { + return nil + } + org := ¤t.Organization{ObjectMeta: v1.ObjectMeta{Name: instance.Spec.MSPID}} + if err = p.Client.Get(context.TODO(), client.ObjectKeyFromObject(org), org); err != nil { + return err + } + err = user.Reconcile(p.Client, instance.GetEnrollUser(), org.Name, instance.GetName(), user.PEER, user.Add) + if err != nil { + return err + } + + return nil +} + func (p *Peer) InitializeUpdateConfigOverride(instance *current.IBPPeer, initPeer *initializer.Peer) error { var err error diff --git a/pkg/offering/base/peer/peer_test.go b/pkg/offering/base/peer/peer_test.go index de6db024..682237d6 100644 --- a/pkg/offering/base/peer/peer_test.go +++ b/pkg/offering/base/peer/peer_test.go @@ -41,6 +41,7 @@ import ( commonconfig "github.com/IBM-Blockchain/fabric-operator/pkg/initializer/common/config" "github.com/IBM-Blockchain/fabric-operator/pkg/initializer/common/enroller" "github.com/IBM-Blockchain/fabric-operator/pkg/initializer/common/mspparser" + orginit "github.com/IBM-Blockchain/fabric-operator/pkg/initializer/organization" peerinit "github.com/IBM-Blockchain/fabric-operator/pkg/initializer/peer" pconfig "github.com/IBM-Blockchain/fabric-operator/pkg/initializer/peer/config/v1" managermocks "github.com/IBM-Blockchain/fabric-operator/pkg/manager/resources/mocks" @@ -169,6 +170,9 @@ var _ = Describe("Base Peer", func() { OUFile: "../../../../defaultconfig/peer/ouconfig.yaml", CorePeerFile: "../../../../defaultconfig/peer/core.yaml", }, + OrganizationInitConfig: &orginit.Config{ + IAMEnabled: false, + }, Operator: config.Operator{ Versions: &deployer.Versions{ Peer: map[string]deployer.VersionPeer{ diff --git a/pkg/offering/k8s/peer/peer_test.go b/pkg/offering/k8s/peer/peer_test.go index be33a190..af9a710e 100644 --- a/pkg/offering/k8s/peer/peer_test.go +++ b/pkg/offering/k8s/peer/peer_test.go @@ -25,6 +25,7 @@ import ( cmocks "github.com/IBM-Blockchain/fabric-operator/controllers/mocks" config "github.com/IBM-Blockchain/fabric-operator/operatorconfig" "github.com/IBM-Blockchain/fabric-operator/pkg/initializer/common/enroller" + orginit "github.com/IBM-Blockchain/fabric-operator/pkg/initializer/organization" peerinit "github.com/IBM-Blockchain/fabric-operator/pkg/initializer/peer" managermocks "github.com/IBM-Blockchain/fabric-operator/pkg/manager/resources/mocks" basepeer "github.com/IBM-Blockchain/fabric-operator/pkg/offering/base/peer" @@ -142,6 +143,9 @@ var _ = Describe("K8s Peer", func() { OUFile: "../../../../defaultconfig/peer/ouconfig.yaml", CorePeerFile: "../../../../defaultconfig/peer/core.yaml", }, + OrganizationInitConfig: &orginit.Config{ + IAMEnabled: false, + }, } initializer := &mocks.InitializeIBPPeer{} initializer.GetInitPeerReturns(&peerinit.Peer{}, nil) diff --git a/pkg/offering/openshift/peer/peer_test.go b/pkg/offering/openshift/peer/peer_test.go index e9afbf38..3986e98f 100644 --- a/pkg/offering/openshift/peer/peer_test.go +++ b/pkg/offering/openshift/peer/peer_test.go @@ -25,6 +25,7 @@ import ( cmocks "github.com/IBM-Blockchain/fabric-operator/controllers/mocks" config "github.com/IBM-Blockchain/fabric-operator/operatorconfig" "github.com/IBM-Blockchain/fabric-operator/pkg/initializer/common/enroller" + orginit "github.com/IBM-Blockchain/fabric-operator/pkg/initializer/organization" peerinit "github.com/IBM-Blockchain/fabric-operator/pkg/initializer/peer" managermocks "github.com/IBM-Blockchain/fabric-operator/pkg/manager/resources/mocks" basepeer "github.com/IBM-Blockchain/fabric-operator/pkg/offering/base/peer" @@ -134,6 +135,9 @@ var _ = Describe("Openshift Peer", func() { OUFile: "../../../../defaultconfig/peer/ouconfig.yaml", CorePeerFile: "../../../../defaultconfig/peer/core.yaml", }, + OrganizationInitConfig: &orginit.Config{ + IAMEnabled: false, + }, } initializer := &peermocks.InitializeIBPPeer{} initializer.GetInitPeerReturns(&peerinit.Peer{}, nil) diff --git a/pkg/user/user.go b/pkg/user/user.go index c7e4ed24..1838dd04 100644 --- a/pkg/user/user.go +++ b/pkg/user/user.go @@ -71,7 +71,9 @@ func ReconcileAdd(u *iam.User, organization, enrollmentID string, idType IDType) var err error // add a organization label to targetUser - u.Labels[OrganizationLabel.String(organization)] = idType.String() + if idType == CLIENT || idType == ADMIN { + u.Labels[OrganizationLabel.String(organization)] = idType.String() + } // set annotation to current admin User annotationList := NewBlockchainAnnotationList() @@ -81,7 +83,7 @@ func ReconcileAdd(u *iam.User, organization, enrollmentID string, idType IDType) } var id ID switch idType { - case CLIENT: + case ADMIN: id = BuildAdminID(u.GetName()) case ORDERER: id = BuildOrdererID(enrollmentID) @@ -120,7 +122,9 @@ func ReconcileRemove(u *iam.User, organization, enrollmentID string, idType IDTy var err error // remove organization labels - delete(u.Labels, OrganizationLabel.String(organization)) + if idType == CLIENT || idType == ADMIN { + delete(u.Labels, OrganizationLabel.String(organization)) + } // remove annotation under relevant organization annotationList := NewBlockchainAnnotationList() diff --git a/pkg/util/util.go b/pkg/util/util.go index b9837a27..0beba1e2 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -1013,3 +1013,30 @@ func GetNamespace() (string, error) { } return string(namespace), nil } + +func DifferArray(old []string, new []string) (added []string, removed []string) { + // cache in map + oldMapper := make(map[string]struct{}, len(old)) + for _, c := range old { + oldMapper[c] = struct{}{} + } + + // calculate differences + for _, c := range new { + + // added: in new ,but not in old + if _, ok := oldMapper[c]; !ok { + added = append(added, c) + continue + } + + // delete the intersection + delete(oldMapper, c) + } + + for c := range oldMapper { + removed = append(removed, c) + } + + return +}