521 lines
15 KiB
Go
521 lines
15 KiB
Go
package container
|
|
|
|
import (
|
|
"crypto/ecdsa"
|
|
"crypto/sha256"
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container"
|
|
v2netmap "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/netmap"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
|
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
|
frostfscrypto "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto"
|
|
frostfsecdsa "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto/ecdsa"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/version"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// Container represents descriptor of the FrostFS container. Container logically
|
|
// stores FrostFS objects. Container is one of the basic and at the same time
|
|
// necessary data storage units in the FrostFS. Container includes data about the
|
|
// owner, rules for placing objects and other information necessary for the
|
|
// system functioning.
|
|
//
|
|
// Container type instances can represent different container states in the
|
|
// system, depending on the context. To create new container in FrostFS zero
|
|
// instance SHOULD be declared, initialized using Init method and filled using
|
|
// dedicated methods. Once container is saved in the FrostFS network, it can't be
|
|
// changed: containers stored in the system are immutable, and FrostFS is a CAS
|
|
// of containers that are identified by a fixed length value (see cid.ID type).
|
|
// Instances for existing containers can be initialized using decoding methods
|
|
// (e.g Unmarshal).
|
|
//
|
|
// Container is mutually compatible with git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container.Container
|
|
// message. See ReadFromV2 / WriteToV2 methods.
|
|
type Container struct {
|
|
v2 container.Container
|
|
}
|
|
|
|
const (
|
|
attributeName = "Name"
|
|
attributeTimestamp = "Timestamp"
|
|
)
|
|
|
|
// reads Container from the container.Container message. If checkFieldPresence is set,
|
|
// returns an error on absence of any protocol-required field.
|
|
func (x *Container) readFromV2(m container.Container, checkFieldPresence bool) error {
|
|
var err error
|
|
|
|
ownerV2 := m.GetOwnerID()
|
|
if ownerV2 != nil {
|
|
var owner user.ID
|
|
|
|
err = owner.ReadFromV2(*ownerV2)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid owner: %w", err)
|
|
}
|
|
} else if checkFieldPresence {
|
|
return errors.New("missing owner")
|
|
}
|
|
|
|
binNonce := m.GetNonce()
|
|
if len(binNonce) > 0 {
|
|
var nonce uuid.UUID
|
|
|
|
err = nonce.UnmarshalBinary(binNonce)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid nonce: %w", err)
|
|
} else if ver := nonce.Version(); ver != 4 {
|
|
return fmt.Errorf("invalid nonce UUID version %d", ver)
|
|
}
|
|
} else if checkFieldPresence {
|
|
return errors.New("missing nonce")
|
|
}
|
|
|
|
ver := m.GetVersion()
|
|
if checkFieldPresence && ver == nil {
|
|
return errors.New("missing version")
|
|
}
|
|
|
|
policyV2 := m.GetPlacementPolicy()
|
|
if policyV2 != nil {
|
|
var policy netmap.PlacementPolicy
|
|
|
|
err = policy.ReadFromV2(*policyV2)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid placement policy: %w", err)
|
|
}
|
|
} else if checkFieldPresence {
|
|
return errors.New("missing placement policy")
|
|
}
|
|
|
|
if err := checkAttributes(m); err != nil {
|
|
return err
|
|
}
|
|
|
|
x.v2 = m
|
|
|
|
return nil
|
|
}
|
|
|
|
func checkAttributes(m container.Container) error {
|
|
attrs := m.GetAttributes()
|
|
mAttr := make(map[string]struct{}, len(attrs))
|
|
var key, val string
|
|
var was bool
|
|
|
|
for i := range attrs {
|
|
key = attrs[i].GetKey()
|
|
if key == "" {
|
|
return errors.New("empty attribute key")
|
|
}
|
|
|
|
_, was = mAttr[key]
|
|
if was {
|
|
return fmt.Errorf("duplicated attribute %s", key)
|
|
}
|
|
|
|
val = attrs[i].GetValue()
|
|
if val == "" {
|
|
return fmt.Errorf("empty attribute value %s", key)
|
|
}
|
|
|
|
var err error
|
|
if key == attributeTimestamp {
|
|
_, err = strconv.ParseInt(val, 10, 64)
|
|
}
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("invalid attribute value %s: %s (%w)", key, val, err)
|
|
}
|
|
|
|
mAttr[key] = struct{}{}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ReadFromV2 reads Container from the container.Container message. Checks if the
|
|
// message conforms to FrostFS API V2 protocol.
|
|
//
|
|
// See also WriteToV2.
|
|
func (x *Container) ReadFromV2(m container.Container) error {
|
|
return x.readFromV2(m, true)
|
|
}
|
|
|
|
// WriteToV2 writes Container into the container.Container message.
|
|
// The message MUST NOT be nil.
|
|
//
|
|
// See also ReadFromV2.
|
|
func (x Container) WriteToV2(m *container.Container) {
|
|
*m = x.v2
|
|
}
|
|
|
|
// Marshal encodes Container into a binary format of the FrostFS API protocol
|
|
// (Protocol Buffers with direct field order).
|
|
//
|
|
// See also Unmarshal.
|
|
func (x Container) Marshal() []byte {
|
|
return x.v2.StableMarshal(nil)
|
|
}
|
|
|
|
// Unmarshal decodes FrostFS API protocol binary format into the Container
|
|
// (Protocol Buffers with direct field order). Returns an error describing
|
|
// a format violation.
|
|
//
|
|
// See also Marshal.
|
|
func (x *Container) Unmarshal(data []byte) error {
|
|
var m container.Container
|
|
|
|
err := m.Unmarshal(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return x.readFromV2(m, false)
|
|
}
|
|
|
|
// MarshalJSON encodes Container into a JSON format of the FrostFS API protocol
|
|
// (Protocol Buffers JSON).
|
|
//
|
|
// See also UnmarshalJSON.
|
|
func (x Container) MarshalJSON() ([]byte, error) {
|
|
return x.v2.MarshalJSON()
|
|
}
|
|
|
|
// UnmarshalJSON decodes FrostFS API protocol JSON format into the Container
|
|
// (Protocol Buffers JSON). Returns an error describing a format violation.
|
|
//
|
|
// See also MarshalJSON.
|
|
func (x *Container) UnmarshalJSON(data []byte) error {
|
|
return x.v2.UnmarshalJSON(data)
|
|
}
|
|
|
|
// Init initializes all internal data of the Container required by FrostFS API
|
|
// protocol. Init MUST be called when creating a new container. Init SHOULD NOT
|
|
// be called multiple times. Init SHOULD NOT be called if the Container instance
|
|
// is used for decoding only.
|
|
func (x *Container) Init() {
|
|
var ver refs.Version
|
|
version.Current().WriteToV2(&ver)
|
|
|
|
x.v2.SetVersion(&ver)
|
|
|
|
nonce, err := uuid.New().MarshalBinary()
|
|
if err != nil {
|
|
panic(fmt.Sprintf("unexpected error from UUID.MarshalBinary: %v", err))
|
|
}
|
|
|
|
x.v2.SetNonce(nonce)
|
|
}
|
|
|
|
// SetOwner specifies the owner of the Container. Each Container has exactly
|
|
// one owner, so SetOwner MUST be called for instances to be saved in the
|
|
// FrostFS.
|
|
//
|
|
// See also Owner.
|
|
func (x *Container) SetOwner(owner user.ID) {
|
|
var m refs.OwnerID
|
|
owner.WriteToV2(&m)
|
|
|
|
x.v2.SetOwnerID(&m)
|
|
}
|
|
|
|
// Owner returns owner of the Container set using SetOwner.
|
|
//
|
|
// Zero Container has no owner which is incorrect according to FrostFS API
|
|
// protocol.
|
|
func (x Container) Owner() (res user.ID) {
|
|
m := x.v2.GetOwnerID()
|
|
if m != nil {
|
|
err := res.ReadFromV2(*m)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("unexpected error from user.ID.ReadFromV2: %v", err))
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// SetBasicACL specifies basic part of the Container ACL. Basic ACL is used
|
|
// to control access inside container storage.
|
|
//
|
|
// See also BasicACL.
|
|
func (x *Container) SetBasicACL(basicACL acl.Basic) {
|
|
x.v2.SetBasicACL(basicACL.Bits())
|
|
}
|
|
|
|
// BasicACL returns basic ACL set using SetBasicACL.
|
|
//
|
|
// Zero Container has zero basic ACL which structurally correct but doesn't
|
|
// make sense since it denies any access to any party.
|
|
func (x Container) BasicACL() (res acl.Basic) {
|
|
res.FromBits(x.v2.GetBasicACL())
|
|
return
|
|
}
|
|
|
|
// SetPlacementPolicy sets placement policy for the objects within the Container.
|
|
// FrostFS storage layer strives to follow the specified policy.
|
|
//
|
|
// See also PlacementPolicy.
|
|
func (x *Container) SetPlacementPolicy(policy netmap.PlacementPolicy) {
|
|
var m v2netmap.PlacementPolicy
|
|
policy.WriteToV2(&m)
|
|
|
|
x.v2.SetPlacementPolicy(&m)
|
|
}
|
|
|
|
// PlacementPolicy returns placement policy set using SetPlacementPolicy.
|
|
//
|
|
// Zero Container has no placement policy which is incorrect according to
|
|
// FrostFS API protocol.
|
|
func (x Container) PlacementPolicy() (res netmap.PlacementPolicy) {
|
|
m := x.v2.GetPlacementPolicy()
|
|
if m != nil {
|
|
err := res.ReadFromV2(*m)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("unexpected error from PlacementPolicy.ReadFromV2: %v", err))
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// SetAttribute sets Container attribute value by key. Both key and value
|
|
// MUST NOT be empty. Attributes set by the creator (owner) are most commonly
|
|
// ignored by the FrostFS system and used for application layer. Some attributes
|
|
// are so-called system or well-known attributes: they are reserved for system
|
|
// needs. System attributes SHOULD NOT be modified using SetAttribute, use
|
|
// corresponding methods/functions. List of the reserved keys is documented
|
|
// in the particular protocol version.
|
|
//
|
|
// SetAttribute overwrites existing attribute value.
|
|
//
|
|
// See also Attribute, IterateAttributes, IterateUserAttributes.
|
|
func (x *Container) SetAttribute(key, value string) {
|
|
if key == "" {
|
|
panic("empty attribute key")
|
|
} else if value == "" {
|
|
panic("empty attribute value")
|
|
}
|
|
|
|
attrs := x.v2.GetAttributes()
|
|
ln := len(attrs)
|
|
|
|
for i := 0; i < ln; i++ {
|
|
if attrs[i].GetKey() == key {
|
|
attrs[i].SetValue(value)
|
|
return
|
|
}
|
|
}
|
|
|
|
attrs = append(attrs, container.Attribute{})
|
|
attrs[ln].SetKey(key)
|
|
attrs[ln].SetValue(value)
|
|
|
|
x.v2.SetAttributes(attrs)
|
|
}
|
|
|
|
// Attribute reads value of the Container attribute by key. Empty result means
|
|
// attribute absence.
|
|
//
|
|
// See also SetAttribute, IterateAttributes, IterateUserAttributes.
|
|
func (x Container) Attribute(key string) string {
|
|
attrs := x.v2.GetAttributes()
|
|
for i := range attrs {
|
|
if attrs[i].GetKey() == key {
|
|
return attrs[i].GetValue()
|
|
}
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// IterateAttributes iterates over all Container attributes and passes them
|
|
// into f. The handler MUST NOT be nil.
|
|
//
|
|
// See also SetAttribute, Attribute.
|
|
func (x Container) IterateAttributes(f func(key, val string)) {
|
|
attrs := x.v2.GetAttributes()
|
|
for i := range attrs {
|
|
f(attrs[i].GetKey(), attrs[i].GetValue())
|
|
}
|
|
}
|
|
|
|
// IterateUserAttributes iterates over user Container attributes and passes them
|
|
// into f. The handler MUST NOT be nil.
|
|
//
|
|
// See also SetAttribute, Attribute.
|
|
func (x Container) IterateUserAttributes(f func(key, val string)) {
|
|
attrs := x.v2.GetAttributes()
|
|
for _, attr := range attrs {
|
|
key := attr.GetKey()
|
|
if !strings.HasPrefix(key, container.SysAttributePrefix) &&
|
|
!strings.HasPrefix(key, container.SysAttributePrefixNeoFS) {
|
|
f(key, attr.GetValue())
|
|
}
|
|
}
|
|
}
|
|
|
|
// SetName sets human-readable name of the Container. Name MUST NOT be empty.
|
|
//
|
|
// See also Name.
|
|
func SetName(cnr *Container, name string) {
|
|
cnr.SetAttribute(attributeName, name)
|
|
}
|
|
|
|
// Name returns container name set using SetName.
|
|
//
|
|
// Zero Container has no name.
|
|
func Name(cnr Container) string {
|
|
return cnr.Attribute(attributeName)
|
|
}
|
|
|
|
// SetCreationTime writes container's creation time in Unix Timestamp format.
|
|
//
|
|
// See also CreatedAt.
|
|
func SetCreationTime(cnr *Container, t time.Time) {
|
|
cnr.SetAttribute(attributeTimestamp, strconv.FormatInt(t.Unix(), 10))
|
|
}
|
|
|
|
// CreatedAt returns container's creation time set using SetCreationTime.
|
|
//
|
|
// Zero Container has zero timestamp (in seconds).
|
|
func CreatedAt(cnr Container) time.Time {
|
|
var sec int64
|
|
|
|
attr := cnr.Attribute(attributeTimestamp)
|
|
if attr != "" {
|
|
var err error
|
|
|
|
sec, err = strconv.ParseInt(cnr.Attribute(attributeTimestamp), 10, 64)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("parse container timestamp: %v", err))
|
|
}
|
|
}
|
|
|
|
return time.Unix(sec, 0)
|
|
}
|
|
|
|
const attributeHomoHashEnabled = "true"
|
|
|
|
// DisableHomomorphicHashing sets flag to disable homomorphic hashing of the
|
|
// Container data.
|
|
//
|
|
// See also IsHomomorphicHashingDisabled.
|
|
func DisableHomomorphicHashing(cnr *Container) {
|
|
cnr.SetAttribute(container.SysAttributeHomomorphicHashing, attributeHomoHashEnabled)
|
|
}
|
|
|
|
// IsHomomorphicHashingDisabled checks if DisableHomomorphicHashing was called.
|
|
//
|
|
// Zero Container has enabled hashing.
|
|
func IsHomomorphicHashingDisabled(cnr Container) bool {
|
|
return cnr.Attribute(container.SysAttributeHomomorphicHashing) == attributeHomoHashEnabled ||
|
|
cnr.Attribute(container.SysAttributeHomomorphicHashingNeoFS) == attributeHomoHashEnabled
|
|
}
|
|
|
|
// Domain represents information about container domain registered in the NNS
|
|
// contract deployed in the FrostFS network.
|
|
type Domain struct {
|
|
name, zone string
|
|
}
|
|
|
|
// SetName sets human-friendly container domain name.
|
|
func (x *Domain) SetName(name string) {
|
|
x.name = name
|
|
}
|
|
|
|
// Name returns name set using SetName.
|
|
//
|
|
// Zero Domain has zero name.
|
|
func (x Domain) Name() string {
|
|
return x.name
|
|
}
|
|
|
|
// SetZone sets zone which is used as a TLD of a domain name in NNS contract.
|
|
func (x *Domain) SetZone(zone string) {
|
|
x.zone = zone
|
|
}
|
|
|
|
// Zone returns domain zone set using SetZone.
|
|
//
|
|
// Zero Domain has "container" zone.
|
|
func (x Domain) Zone() string {
|
|
if x.zone != "" {
|
|
return x.zone
|
|
}
|
|
|
|
return "container"
|
|
}
|
|
|
|
// WriteDomain writes Domain into the Container. Name MUST NOT be empty.
|
|
func WriteDomain(cnr *Container, domain Domain) {
|
|
cnr.SetAttribute(container.SysAttributeName, domain.Name())
|
|
cnr.SetAttribute(container.SysAttributeZone, domain.Zone())
|
|
}
|
|
|
|
// ReadDomain reads Domain from the Container. Returns value with empty name
|
|
// if domain is not specified.
|
|
func ReadDomain(cnr Container) (res Domain) {
|
|
if name := cnr.Attribute(container.SysAttributeName); name != "" {
|
|
res.SetName(name)
|
|
res.SetZone(cnr.Attribute(container.SysAttributeZone))
|
|
} else if name = cnr.Attribute(container.SysAttributeNameNeoFS); name != "" {
|
|
res.SetName(name)
|
|
res.SetZone(cnr.Attribute(container.SysAttributeZoneNeoFS))
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// CalculateSignature calculates signature of the Container using provided signer
|
|
// and writes it into dst. Signature instance MUST NOT be nil. CalculateSignature
|
|
// is expected to be called after all the Container data is filled and before
|
|
// saving the Container in the FrostFS network. Note that мany subsequent change
|
|
// will most likely break the signature.
|
|
//
|
|
// See also VerifySignature.
|
|
func CalculateSignature(dst *frostfscrypto.Signature, cnr Container, signer ecdsa.PrivateKey) error {
|
|
return dst.Calculate(frostfsecdsa.SignerRFC6979(signer), cnr.Marshal())
|
|
}
|
|
|
|
// VerifySignature verifies Container signature calculated using CalculateSignature.
|
|
// Result means signature correctness.
|
|
func VerifySignature(sig frostfscrypto.Signature, cnr Container) bool {
|
|
return sig.Verify(cnr.Marshal())
|
|
}
|
|
|
|
// CalculateIDFromBinary calculates identifier of the binary-encoded container
|
|
// in CAS of the FrostFS containers and writes it into dst. ID instance MUST NOT
|
|
// be nil.
|
|
//
|
|
// See also CalculateID, AssertID.
|
|
func CalculateIDFromBinary(dst *cid.ID, cnr []byte) {
|
|
dst.SetSHA256(sha256.Sum256(cnr))
|
|
}
|
|
|
|
// CalculateID encodes the given Container and passes the result into
|
|
// CalculateIDFromBinary.
|
|
//
|
|
// See also Container.Marshal, AssertID.
|
|
func CalculateID(dst *cid.ID, cnr Container) {
|
|
CalculateIDFromBinary(dst, cnr.Marshal())
|
|
}
|
|
|
|
// AssertID checks if the given Container matches its identifier in CAS of the
|
|
// FrostFS containers.
|
|
//
|
|
// See also CalculateID.
|
|
func AssertID(id cid.ID, cnr Container) bool {
|
|
var id2 cid.ID
|
|
CalculateID(&id2, cnr)
|
|
|
|
return id2.Equals(id)
|
|
}
|