endpoint

package
v0.9.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 12, 2024 License: MIT Imports: 27 Imported by: 0

Documentation

Overview

Package endpoint implements replication endpoints for use with package replication.

Index

Constants

View Source
const (
	ClientIdentityKey contextKey = iota
)
View Source
const (
	LastReceivedHoldTagNamePrefix = "zrepl_last_received_J_"
)

Variables

View Source
var ErrV1ReplicationCursor = errors.New("bookmark name is a v1-replication cursor")

Functions

func AbstractionEquals

func AbstractionEquals(a, b Abstraction) bool

func AbstractionsCacheInvalidate

func AbstractionsCacheInvalidate(fs string)

func BatchDestroy

func BatchDestroy(ctx context.Context, abs []Abstraction) <-chan BatchDestroyResult

func GetMostRecentReplicationCursorOfJob

func GetMostRecentReplicationCursorOfJob(ctx context.Context, fs string, jobID JobID) (*zfs.FilesystemVersion, error)

may return nil for both values, indicating there is no cursor

func GetReplicationCursors

func GetReplicationCursors(ctx context.Context, dp *zfs.DatasetPath, jobID JobID) ([]zfs.FilesystemVersion, error)

func LastReceivedHoldTag

func LastReceivedHoldTag(jobID JobID) (string, error)

func ListAbstractions

func ListAbstractions(ctx context.Context, query ListZFSHoldsAndBookmarksQuery,
) (out []Abstraction, outErrs []ListAbstractionsError, err error)

func ListAbstractionsStreamed

func ListAbstractionsStreamed(ctx context.Context,
	query ListZFSHoldsAndBookmarksQuery,
) (_ <-chan Abstraction, _ <-chan ListAbstractionsError, drainDone func(),
	_ error,
)

if err != nil, the returned channels are both nil

if err == nil, both channels must be fully drained by the caller to avoid leaking goroutines.

After draining is done, the caller must call the returned drainDone func.

func RegisterMetrics

func RegisterMetrics(r prometheus.Registerer)

func ReplicationCursorBookmarkName

func ReplicationCursorBookmarkName(fs string, guid uint64, id JobID) (string, error)

func StepHoldTag

func StepHoldTag(jobid JobID) (string, error)

func TentativeReplicationCursorBookmarkName

func TentativeReplicationCursorBookmarkName(fs string, guid uint64, id JobID) (string, error)

v must be validated by caller

Types

type Abstraction

type Abstraction interface {
	GetType() AbstractionType
	GetFS() string
	GetName() string
	GetFullPath() string
	GetJobID() *JobID // may return nil if the abstraction does not have a JobID
	GetCreateTXG() uint64
	GetFilesystemVersion() zfs.FilesystemVersion
	String() string
	// destroy the abstraction: either releases the hold or destroys the bookmark
	Destroy(context.Context) error
	json.Marshaler
}

Implementation Note: Whenever you add a new accessor, adjust AbstractionJSON.MarshalJSON accordingly

func CreateLastReceivedHold

func CreateLastReceivedHold(ctx context.Context, fs string, to zfs.FilesystemVersion, jobID JobID) (Abstraction, error)

func CreateReplicationCursor

func CreateReplicationCursor(ctx context.Context, fs string, target zfs.FilesystemVersion, jobID JobID) (a Abstraction, err error)

idempotently create a replication cursor targeting `target`

returns ErrBookmarkCloningNotSupported if version is a bookmark and bookmarking bookmarks is not supported by ZFS

func CreateTentativeReplicationCursor

func CreateTentativeReplicationCursor(ctx context.Context, fs string, target zfs.FilesystemVersion, jobID JobID) (a Abstraction, err error)

func HoldStep

func HoldStep(ctx context.Context, fs string, v zfs.FilesystemVersion, jobID JobID) (Abstraction, error)

idempotently hold `version`

func LastReceivedHoldExtractor

func LastReceivedHoldExtractor(fs *zfs.DatasetPath, v zfs.FilesystemVersion, holdTag string) Abstraction

func ReplicationCursorV1Extractor

func ReplicationCursorV1Extractor(fs *zfs.DatasetPath, v zfs.FilesystemVersion) (_ Abstraction)

func ReplicationCursorV2Extractor

func ReplicationCursorV2Extractor(fs *zfs.DatasetPath, v zfs.FilesystemVersion) (_ Abstraction)

func StepHoldExtractor

func StepHoldExtractor(fs *zfs.DatasetPath, v zfs.FilesystemVersion, holdTag string) Abstraction

func TentativeReplicationCursorExtractor

func TentativeReplicationCursorExtractor(fs *zfs.DatasetPath, v zfs.FilesystemVersion) (_ Abstraction)

type AbstractionJSON

type AbstractionJSON struct{ Abstraction }

func (AbstractionJSON) MarshalJSON

func (a AbstractionJSON) MarshalJSON() ([]byte, error)

type AbstractionType

type AbstractionType string
const (
	AbstractionStepHold                           AbstractionType = "step-hold"
	AbstractionLastReceivedHold                   AbstractionType = "last-received-hold"
	AbstractionTentativeReplicationCursorBookmark AbstractionType = "tentative-replication-cursor-bookmark-v2"
	AbstractionReplicationCursorBookmarkV1        AbstractionType = "replication-cursor-bookmark-v1"
	AbstractionReplicationCursorBookmarkV2        AbstractionType = "replication-cursor-bookmark-v2"
)

Implementation note: There are a lot of exhaustive switches on AbstractionType in the code base. When adding a new abstraction type, make sure to search and update them!

func (AbstractionType) BookmarkExtractor

func (t AbstractionType) BookmarkExtractor() BookmarkExtractor

returns nil if the abstraction type is not bookmark-based

func (AbstractionType) BookmarkNamer

func (t AbstractionType) BookmarkNamer() func(fs string, guid uint64, jobId JobID) (string, error)

func (AbstractionType) HoldExtractor

func (t AbstractionType) HoldExtractor() HoldExtractor

returns nil if the abstraction type is not hold-based

func (AbstractionType) IsSnapshotOrBookmark

func (t AbstractionType) IsSnapshotOrBookmark() bool

func (AbstractionType) MustValidate

func (t AbstractionType) MustValidate() error

func (AbstractionType) Validate

func (t AbstractionType) Validate() error

type AbstractionTypeSet

type AbstractionTypeSet map[AbstractionType]bool

func AbstractionTypeSetFromStrings

func AbstractionTypeSetFromStrings(sts []string) (AbstractionTypeSet, error)

func (AbstractionTypeSet) ContainsAll

func (s AbstractionTypeSet) ContainsAll(q AbstractionTypeSet) bool

func (AbstractionTypeSet) ContainsAnyOf

func (s AbstractionTypeSet) ContainsAnyOf(q AbstractionTypeSet) bool

func (AbstractionTypeSet) ExtractBookmark

Use the `BookmarkExtractor()` method of each abstraction type in this set to try extract an abstraction from the given FilesystemVersion.

Abstraction types in this set that don't have a bookmark extractor are skipped.

Panics if more than one abstraction type matches.

func (AbstractionTypeSet) String

func (s AbstractionTypeSet) String() string

func (AbstractionTypeSet) Validate

func (s AbstractionTypeSet) Validate() error

type BatchDestroyResult

type BatchDestroyResult struct {
	Abstraction
	DestroyErr error
}

func (BatchDestroyResult) MarshalJSON

func (r BatchDestroyResult) MarshalJSON() ([]byte, error)

type BookmarkExtractor

type BookmarkExtractor func(fs *zfs.DatasetPath, v zfs.FilesystemVersion) Abstraction

type CreateTXGRange

type CreateTXGRange struct {
	// if not nil: The hold's snapshot or the bookmark's createtxg must be greater than (or equal) Since
	// else: CreateTXG of the hold or bookmark can be any value accepted by Until
	Since *CreateTXGRangeBound
	// if not nil: The hold's snapshot or the bookmark's createtxg must be less than (or equal) Until
	// else: CreateTXG of the hold or bookmark can be any value accepted by Since
	Until *CreateTXGRangeBound
}

A non-empty range of CreateTXGs

If both Since and Until are nil, any CreateTXG is acceptable

func (*CreateTXGRange) Contains

func (r *CreateTXGRange) Contains(qCreateTxg uint64) bool

panics if not .Validate()

func (*CreateTXGRange) IsUnbounded

func (r *CreateTXGRange) IsUnbounded() bool

panics if not .Validate()

func (*CreateTXGRange) String

func (r *CreateTXGRange) String() string

func (*CreateTXGRange) Validate

func (r *CreateTXGRange) Validate() error

type CreateTXGRangeBound

type CreateTXGRangeBound struct {
	CreateTXG uint64
	Inclusive bool
}

func (*CreateTXGRangeBound) Validate

func (i *CreateTXGRangeBound) Validate() error

type FSFilter

type FSFilter interface {
	Empty() bool
	Filter(path *zfs.DatasetPath) (pass bool, err error)
	UserSpecifiedDatasets() zfs.UserSpecifiedDatasetsSet
}

type FSMap

type FSMap interface {
	FSFilter
	Map(path *zfs.DatasetPath) (*zfs.DatasetPath, error)
	Invert() (FSMap, error)
	AsFilter() FSFilter
}

FIXME: can we get away without error types here?

type HoldExtractor

type HoldExtractor = func(fs *zfs.DatasetPath, v zfs.FilesystemVersion, tag string) Abstraction

type JobID

type JobID struct {
	// contains filtered or unexported fields
}

JobID instances returned by MakeJobID() guarantee their JobID.String() can be used in ZFS dataset names and hold tags.

func MakeJobID

func MakeJobID(s string) (JobID, error)

func MustMakeJobID

func MustMakeJobID(s string) JobID

func ParseLastReceivedHoldTag

func ParseLastReceivedHoldTag(tag string) (JobID, error)

err != nil always means that the bookmark is not a step bookmark

func ParseReplicationCursorBookmarkName

func ParseReplicationCursorBookmarkName(fullname string) (uint64, JobID, error)

err != nil always means that the bookmark is not a valid replication bookmark

Returns ErrV1ReplicationCursor as error if the bookmark is a v1 replication cursor

func ParseStepHoldTag

func ParseStepHoldTag(tag string) (JobID, error)

err != nil always means that the bookmark is not a step bookmark

func ParseTentativeReplicationCursorBookmarkName

func ParseTentativeReplicationCursorBookmarkName(fullname string) (guid uint64, jobID JobID, err error)

name is the full bookmark name, including dataset path

err != nil always means that the bookmark is not a step bookmark

func (JobID) MarshalJSON

func (j JobID) MarshalJSON() ([]byte, error)

func (JobID) MustValidate

func (j JobID) MustValidate()

func (JobID) String

func (j JobID) String() string

func (*JobID) UnmarshalJSON

func (j *JobID) UnmarshalJSON(b []byte) error

type ListAbstractionsError

type ListAbstractionsError struct {
	FS   string
	Snap string
	What string
	Err  error
}

func (ListAbstractionsError) Error

func (e ListAbstractionsError) Error() string

type ListAbstractionsErrors

type ListAbstractionsErrors []ListAbstractionsError

func (ListAbstractionsErrors) Error

func (e ListAbstractionsErrors) Error() string

type ListStaleQueryError

type ListStaleQueryError struct {
	// contains filtered or unexported fields
}

type ListZFSHoldsAndBookmarksQuery

type ListZFSHoldsAndBookmarksQuery struct {
	FS ListZFSHoldsAndBookmarksQueryFilesystemFilter
	// What abstraction types should match (any contained in the set)
	What AbstractionTypeSet

	// if not nil: JobID of the hold or bookmark in question must be equal
	// else: JobID of the hold or bookmark can be any value
	JobID *JobID

	// zero-value means any CreateTXG is acceptable
	CreateTXG CreateTXGRange

	// Number of concurrently queried filesystems. Must be >= 1
	Concurrency int
}

func (*ListZFSHoldsAndBookmarksQuery) Validate

func (q *ListZFSHoldsAndBookmarksQuery) Validate() error

type ListZFSHoldsAndBookmarksQueryFilesystemFilter

type ListZFSHoldsAndBookmarksQueryFilesystemFilter struct {
	FS     *string
	Filter zfs.DatasetFilter
}

FS == nil XOR Filter == nil

func (*ListZFSHoldsAndBookmarksQueryFilesystemFilter) Filesystems

func (*ListZFSHoldsAndBookmarksQueryFilesystemFilter) Validate

type PlaceholderCreationEncryptionProperty

type PlaceholderCreationEncryptionProperty int
const (
	PlaceholderCreationEncryptionPropertyUnspecified PlaceholderCreationEncryptionProperty = 1 << iota
	PlaceholderCreationEncryptionPropertyInherit
	PlaceholderCreationEncryptionPropertyOff
)

Note: the constant names, transformed through enumer, are part of the config format!

func PlaceholderCreationEncryptionPropertyString

func PlaceholderCreationEncryptionPropertyString(s string) (PlaceholderCreationEncryptionProperty, error)

PlaceholderCreationEncryptionPropertyString retrieves an enum value from the enum constants string name. Throws an error if the param is not part of the enum.

func PlaceholderCreationEncryptionPropertyValues

func PlaceholderCreationEncryptionPropertyValues() []PlaceholderCreationEncryptionProperty

PlaceholderCreationEncryptionPropertyValues returns all values of the enum

func (PlaceholderCreationEncryptionProperty) IsAPlaceholderCreationEncryptionProperty

func (i PlaceholderCreationEncryptionProperty) IsAPlaceholderCreationEncryptionProperty() bool

IsAPlaceholderCreationEncryptionProperty returns "true" if the value is listed in the enum definition. "false" otherwise

func (PlaceholderCreationEncryptionProperty) String

type Receiver

type Receiver struct {
	Test_OverrideClientIdentityFunc func() string // for use by platformtest
	// contains filtered or unexported fields
}

Receiver implements replication.ReplicationEndpoint for a receiving side

func NewReceiver

func NewReceiver(config ReceiverConfig) *Receiver

func (*Receiver) DestroySnapshots

func (s *Receiver) DestroySnapshots(ctx context.Context, req *pdu.DestroySnapshotsReq) (*pdu.DestroySnapshotsRes, error)

func (*Receiver) ListFilesystemVersions

func (s *Receiver) ListFilesystemVersions(ctx context.Context,
	req *pdu.ListFilesystemVersionsReq,
) (*pdu.ListFilesystemVersionsRes, error)

func (*Receiver) ListFilesystems

func (s *Receiver) ListFilesystems(ctx context.Context) (*pdu.ListFilesystemRes,
	error,
)

func (*Receiver) Receive

func (s *Receiver) Receive(ctx context.Context, req *pdu.ReceiveReq,
	receive io.ReadCloser,
) error

func (*Receiver) ReplicationCursor

func (s *Receiver) ReplicationCursor(ctx context.Context, _ *pdu.ReplicationCursorReq) (*pdu.ReplicationCursorRes, error)

func (*Receiver) Send

func (s *Receiver) Send(ctx context.Context, req *pdu.SendReq) (*pdu.SendRes, io.ReadCloser, error)

func (*Receiver) SendCompleted

func (p *Receiver) SendCompleted(ctx context.Context, _ *pdu.SendCompletedReq,
) error

func (*Receiver) SendDry

func (s *Receiver) SendDry(ctx context.Context, req *pdu.SendDryReq,
) (*pdu.SendDryRes, error)

func (*Receiver) WaitForConnectivity

func (s *Receiver) WaitForConnectivity(ctx context.Context) error

func (*Receiver) WithClientIdentity

func (s *Receiver) WithClientIdentity(identity string) *Receiver

type ReceiverConfig

type ReceiverConfig struct {
	JobID JobID

	RootWithoutClientComponent *zfs.DatasetPath
	AppendClientIdentity       bool

	InheritProperties  []zfsprop.Property
	OverrideProperties map[zfsprop.Property]string

	PlaceholderEncryption PlaceholderCreationEncryptionProperty

	ExecPipe [][]string
}

NOTE: when adding members to this struct, remember to add them to `ReceiverConfig.copyIn()`

func (*ReceiverConfig) Validate

func (c *ReceiverConfig) Validate() error

type ReplicationCursorV1

type ReplicationCursorV1 struct {
	Type AbstractionType
	FS   string
	zfs.FilesystemVersion
}

func (ReplicationCursorV1) Destroy

func (c ReplicationCursorV1) Destroy(ctx context.Context) error

func (ReplicationCursorV1) GetFS

func (c ReplicationCursorV1) GetFS() string

func (ReplicationCursorV1) GetFilesystemVersion

func (c ReplicationCursorV1) GetFilesystemVersion() zfs.FilesystemVersion

func (ReplicationCursorV1) GetFullPath

func (c ReplicationCursorV1) GetFullPath() string

func (ReplicationCursorV1) GetJobID

func (c ReplicationCursorV1) GetJobID() *JobID

func (ReplicationCursorV1) GetType

func (ReplicationCursorV1) MarshalJSON

func (c ReplicationCursorV1) MarshalJSON() ([]byte, error)

func (ReplicationCursorV1) String

func (c ReplicationCursorV1) String() string

type ReplicationGuaranteeIncremental

type ReplicationGuaranteeIncremental struct{}

func (ReplicationGuaranteeIncremental) Kind

func (ReplicationGuaranteeIncremental) ReceiverPostRecv

func (g ReplicationGuaranteeIncremental) ReceiverPostRecv(ctx context.Context, jid JobID, fs string, toRecvd zfs.FilesystemVersion) (keep []Abstraction, err error)

func (ReplicationGuaranteeIncremental) SenderPostRecvConfirmed

func (g ReplicationGuaranteeIncremental) SenderPostRecvConfirmed(ctx context.Context, jid JobID, fs string, to zfs.FilesystemVersion) (keep []Abstraction, err error)

func (ReplicationGuaranteeIncremental) SenderPreSend

func (g ReplicationGuaranteeIncremental) SenderPreSend(ctx context.Context, jid JobID, sendArgs *zfs.ZFSSendArgsValidated) (keep []Abstraction, err error)

func (ReplicationGuaranteeIncremental) String

type ReplicationGuaranteeKind

type ReplicationGuaranteeKind int
const (
	ReplicationGuaranteeKindResumability ReplicationGuaranteeKind = 1 << iota
	ReplicationGuaranteeKindIncremental
	ReplicationGuaranteeKindNone
)

func ReplicationGuaranteeKindString

func ReplicationGuaranteeKindString(s string) (ReplicationGuaranteeKind, error)

ReplicationGuaranteeKindString retrieves an enum value from the enum constants string name. Throws an error if the param is not part of the enum.

func ReplicationGuaranteeKindValues

func ReplicationGuaranteeKindValues() []ReplicationGuaranteeKind

ReplicationGuaranteeKindValues returns all values of the enum

func (ReplicationGuaranteeKind) IsAReplicationGuaranteeKind

func (i ReplicationGuaranteeKind) IsAReplicationGuaranteeKind() bool

IsAReplicationGuaranteeKind returns "true" if the value is listed in the enum definition. "false" otherwise

func (ReplicationGuaranteeKind) MarshalJSON

func (i ReplicationGuaranteeKind) MarshalJSON() ([]byte, error)

MarshalJSON implements the json.Marshaler interface for ReplicationGuaranteeKind

func (ReplicationGuaranteeKind) String

func (i ReplicationGuaranteeKind) String() string

func (*ReplicationGuaranteeKind) UnmarshalJSON

func (i *ReplicationGuaranteeKind) UnmarshalJSON(data []byte) error

UnmarshalJSON implements the json.Unmarshaler interface for ReplicationGuaranteeKind

type ReplicationGuaranteeNone

type ReplicationGuaranteeNone struct{}

func (ReplicationGuaranteeNone) Kind

func (ReplicationGuaranteeNone) ReceiverPostRecv

func (g ReplicationGuaranteeNone) ReceiverPostRecv(ctx context.Context, jid JobID, fs string, toRecvd zfs.FilesystemVersion) (keep []Abstraction, err error)

func (ReplicationGuaranteeNone) SenderPostRecvConfirmed

func (g ReplicationGuaranteeNone) SenderPostRecvConfirmed(ctx context.Context, jid JobID, fs string, to zfs.FilesystemVersion) (keep []Abstraction, err error)

func (ReplicationGuaranteeNone) SenderPreSend

func (g ReplicationGuaranteeNone) SenderPreSend(ctx context.Context, jid JobID, sendArgs *zfs.ZFSSendArgsValidated) (keep []Abstraction, err error)

func (ReplicationGuaranteeNone) String

func (g ReplicationGuaranteeNone) String() string

type ReplicationGuaranteeOptions

type ReplicationGuaranteeOptions struct {
	Initial     ReplicationGuaranteeKind
	Incremental ReplicationGuaranteeKind
}

func (ReplicationGuaranteeOptions) Strategy

type ReplicationGuaranteeResumability

type ReplicationGuaranteeResumability struct{}

func (ReplicationGuaranteeResumability) Kind

func (ReplicationGuaranteeResumability) ReceiverPostRecv

func (g ReplicationGuaranteeResumability) ReceiverPostRecv(ctx context.Context, jid JobID, fs string, toRecvd zfs.FilesystemVersion) (keep []Abstraction, err error)

func (ReplicationGuaranteeResumability) SenderPostRecvConfirmed

func (g ReplicationGuaranteeResumability) SenderPostRecvConfirmed(ctx context.Context, jid JobID, fs string, to zfs.FilesystemVersion) (keep []Abstraction, err error)

func (ReplicationGuaranteeResumability) SenderPreSend

func (g ReplicationGuaranteeResumability) SenderPreSend(ctx context.Context, jid JobID, sendArgs *zfs.ZFSSendArgsValidated) (keep []Abstraction, err error)

func (ReplicationGuaranteeResumability) String

type ReplicationGuaranteeStrategy

type ReplicationGuaranteeStrategy interface {
	Kind() ReplicationGuaranteeKind
	SenderPreSend(ctx context.Context, jid JobID, sendArgs *zfs.ZFSSendArgsValidated) (keep []Abstraction, err error)
	ReceiverPostRecv(ctx context.Context, jid JobID, fs string, toRecvd zfs.FilesystemVersion) (keep []Abstraction, err error)
	SenderPostRecvConfirmed(ctx context.Context, jid JobID, fs string, to zfs.FilesystemVersion) (keep []Abstraction, err error)
}

type Sender

type Sender struct {
	FSFilter zfs.DatasetFilter
	// contains filtered or unexported fields
}

Sender implements replication.ReplicationEndpoint for a sending side

func NewSender

func NewSender(conf SenderConfig) *Sender

func (*Sender) DestroySnapshots

func (p *Sender) DestroySnapshots(ctx context.Context, req *pdu.DestroySnapshotsReq) (*pdu.DestroySnapshotsRes, error)

func (*Sender) ListFilesystemVersions

func (s *Sender) ListFilesystemVersions(ctx context.Context, r *pdu.ListFilesystemVersionsReq) (*pdu.ListFilesystemVersionsRes, error)

func (*Sender) ListFilesystems

func (s *Sender) ListFilesystems(ctx context.Context) (*pdu.ListFilesystemRes,
	error,
)

func (*Sender) Receive

func (p *Sender) Receive(ctx context.Context, r *pdu.ReceiveReq,
	_ io.ReadCloser,
) error

func (*Sender) ReplicationCursor

func (p *Sender) ReplicationCursor(ctx context.Context, req *pdu.ReplicationCursorReq) (*pdu.ReplicationCursorRes, error)

func (*Sender) Send

func (s *Sender) Send(ctx context.Context, r *pdu.SendReq) (*pdu.SendRes,
	io.ReadCloser, error,
)

func (*Sender) SendCompleted

func (p *Sender) SendCompleted(ctx context.Context, r *pdu.SendCompletedReq,
) error

func (*Sender) SendDry

func (s *Sender) SendDry(ctx context.Context, req *pdu.SendDryReq,
) (*pdu.SendDryRes, error)

func (*Sender) WaitForConnectivity

func (p *Sender) WaitForConnectivity(ctx context.Context) error

type SenderConfig

type SenderConfig struct {
	FSF   zfs.DatasetFilter
	JobID JobID

	ListPlaceholders bool

	Encrypt              bool
	SendRaw              bool
	SendProperties       bool
	SendBackupProperties bool
	SendLargeBlocks      bool
	SendCompressed       bool
	SendEmbeddedData     bool
	SendSaved            bool

	ExecPipe [][]string
}

func (*SenderConfig) Validate

func (c *SenderConfig) Validate() error

type StalenessInfo

type StalenessInfo struct {
	ConstructedWithQuery ListZFSHoldsAndBookmarksQuery
	Live                 []Abstraction
	Stale                []Abstraction
}

func ListStale

returns *ListStaleQueryError if the given query cannot be used for determining staleness info

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL