From 4c3207a56db6268380188f6e2db89f0f4177e871 Mon Sep 17 00:00:00 2001 From: Andy Arnold Date: Tue, 10 Oct 2023 21:10:05 +0100 Subject: [PATCH 01/32] A small typo duplicated csi-snapshot-data-movement.md in main and v.1.12 Signed-off-by: Andy Arnold --- site/content/docs/main/csi-snapshot-data-movement.md | 2 +- site/content/docs/v1.12/csi-snapshot-data-movement.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/site/content/docs/main/csi-snapshot-data-movement.md b/site/content/docs/main/csi-snapshot-data-movement.md index c9ff42d3e..fe02cf0ae 100644 --- a/site/content/docs/main/csi-snapshot-data-movement.md +++ b/site/content/docs/main/csi-snapshot-data-movement.md @@ -194,7 +194,7 @@ Or if you want to use a customized data mover: velero backup create NAME --snapshot-move-data --data-mover DATA-MOVER-NAME OPTIONS... ``` -When the backup starts, you will see the `VolumeSnapshot` and `VolumeSnapshotContent` objects created, but after the backup finishes, the objects will disppear. +When the backup starts, you will see the `VolumeSnapshot` and `VolumeSnapshotContent` objects created, but after the backup finishes, the objects will disappear. After snapshots are created, you will see one or more `DataUpload` CRs created. You may also see some intermediate objects (i.e., pods, PVCs, PVs) created in Velero namespace or the cluster scope, they are to help data movers to move data. And they will be removed after the backup completes. The phase of a `DataUpload` CR changes several times during the backup process and finally goes to one of the terminal status, `Completed`, `Failed` or `Cancelled`. You can see the phase changes as well as the data upload progress by watching the `DataUpload` CRs: diff --git a/site/content/docs/v1.12/csi-snapshot-data-movement.md b/site/content/docs/v1.12/csi-snapshot-data-movement.md index 6705a3145..ccbc11278 100644 --- a/site/content/docs/v1.12/csi-snapshot-data-movement.md +++ b/site/content/docs/v1.12/csi-snapshot-data-movement.md @@ -207,7 +207,7 @@ Or if you want to use a customized data mover: velero backup create NAME --snapshot-move-data --data-mover DATA-MOVER-NAME OPTIONS... ``` -When the backup starts, you will see the `VolumeSnapshot` and `VolumeSnapshotContent` objects created, but after the backup finishes, the objects will disppear. +When the backup starts, you will see the `VolumeSnapshot` and `VolumeSnapshotContent` objects created, but after the backup finishes, the objects will disappear. After snapshots are created, you will see one or more `DataUpload` CRs created. You may also see some intermediate objects (i.e., pods, PVCs, PVs) created in Velero namespace or the cluster scope, they are to help data movers to move data. And they will be removed after the backup completes. The phase of a `DataUpload` CR changes several times during the backup process and finally goes to one of the terminal status, `Completed`, `Failed` or `Cancelled`. You can see the phase changes as well as the data upload progress by watching the `DataUpload` CRs: From 8e4cefbb0d98f4c536b90e2122cb03178ebd5ffe Mon Sep 17 00:00:00 2001 From: "zhongjun.li" Date: Tue, 2 Jan 2024 11:32:42 +0800 Subject: [PATCH 02/32] Reduce backup DeepCopy Signed-off-by: zhongjun.li --- pkg/controller/backup_controller.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/controller/backup_controller.go b/pkg/controller/backup_controller.go index 9fcb14e99..7228719ea 100644 --- a/pkg/controller/backup_controller.go +++ b/pkg/controller/backup_controller.go @@ -252,8 +252,6 @@ func (b *backupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr if err := kubeutil.PatchResource(original, request.Backup, b.kbClient); err != nil { return ctrl.Result{}, errors.Wrapf(err, "error updating Backup status to %s", request.Status.Phase) } - // store ref to just-updated item for creating patch - original = request.Backup.DeepCopy() backupScheduleName := request.GetLabels()[velerov1api.ScheduleNameLabel] @@ -265,6 +263,9 @@ func (b *backupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr return ctrl.Result{}, nil } + // store ref to just-updated item for creating patch + original = request.Backup.DeepCopy() + b.backupTracker.Add(request.Namespace, request.Name) defer func() { switch request.Status.Phase { From 3ea4f345c6c10310fc7be8e97bf32d7b8a9a5218 Mon Sep 17 00:00:00 2001 From: Xun Jiang Date: Mon, 8 Jan 2024 10:20:55 +0800 Subject: [PATCH 03/32] Add detail for parameter s3ForcePathStyle in MinIO page. Signed-off-by: Xun Jiang --- site/content/docs/main/contributions/minio.md | 11 ++++++++--- site/content/docs/v1.13/contributions/minio.md | 11 ++++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/site/content/docs/main/contributions/minio.md b/site/content/docs/main/contributions/minio.md index b04354a1f..a44062c77 100644 --- a/site/content/docs/main/contributions/minio.md +++ b/site/content/docs/main/contributions/minio.md @@ -81,11 +81,16 @@ These instructions start the Velero server and a Minio instance that is accessib --backup-location-config region=minio,s3ForcePathStyle="true",s3Url=http://minio.velero.svc:9000 ``` - This example assumes that it is running within a local cluster without a volume provider capable of snapshots, so no `VolumeSnapshotLocation` is created (`--use-volume-snapshots=false`). You may need to update AWS plugin version to one that is [compatible](https://github.com/vmware-tanzu/velero-plugin-for-aws#compatibility) with the version of Velero you are installing. + * This example assumes that it is running within a local cluster without a volume provider capable of snapshots, so no `VolumeSnapshotLocation` is created (`--use-volume-snapshots=false`). You may need to update AWS plugin version to one that is [compatible](https://github.com/vmware-tanzu/velero-plugin-for-aws#compatibility) with the version of Velero you are installing. - Additionally, you can specify `--use-node-agent` to enable File System Backup support, and `--wait` to wait for the deployment to be ready. + * Additionally, you can specify `--use-node-agent` to enable File System Backup support, and `--wait` to wait for the deployment to be ready. - This example also assumes you have named your Minio bucket "velero". + * This example also assumes you have named your Minio bucket "velero". + + * Please make sure to set parameter `s3ForcePathStyle=true`. The parameter is used to set the Velero integrated AWS SDK data query address style. There are two types of the address: [virtual-host and path-style](https://docs.aws.amazon.com/AmazonS3/latest/userguide/VirtualHosting.html). If the `s3ForcePathStyle=true` is not set, the default value is false, then the AWS SDK will query in virtual-host style, but the MinIO server only support path-style address by default. The miss match will cause Velero can upload data to MinIO, but cannot download from MinIO. This [link](https://github.com/vmware-tanzu/velero/issues/7268) is an example of this issue. + It can be resolved by two ways: + * Set `s3ForcePathStyle=true` for parameter `--backup-location-config` when installing Velero. This is the preferred way. + * Make MinIO server support virtual-host style address. Add the [MINIO_DOMAIN environment variable](https://min.io/docs/minio/linux/reference/minio-server/settings/core.html#id5) for MinIO server will do the magic. 1. Deploy the example nginx application: diff --git a/site/content/docs/v1.13/contributions/minio.md b/site/content/docs/v1.13/contributions/minio.md index b04354a1f..a44062c77 100644 --- a/site/content/docs/v1.13/contributions/minio.md +++ b/site/content/docs/v1.13/contributions/minio.md @@ -81,11 +81,16 @@ These instructions start the Velero server and a Minio instance that is accessib --backup-location-config region=minio,s3ForcePathStyle="true",s3Url=http://minio.velero.svc:9000 ``` - This example assumes that it is running within a local cluster without a volume provider capable of snapshots, so no `VolumeSnapshotLocation` is created (`--use-volume-snapshots=false`). You may need to update AWS plugin version to one that is [compatible](https://github.com/vmware-tanzu/velero-plugin-for-aws#compatibility) with the version of Velero you are installing. + * This example assumes that it is running within a local cluster without a volume provider capable of snapshots, so no `VolumeSnapshotLocation` is created (`--use-volume-snapshots=false`). You may need to update AWS plugin version to one that is [compatible](https://github.com/vmware-tanzu/velero-plugin-for-aws#compatibility) with the version of Velero you are installing. - Additionally, you can specify `--use-node-agent` to enable File System Backup support, and `--wait` to wait for the deployment to be ready. + * Additionally, you can specify `--use-node-agent` to enable File System Backup support, and `--wait` to wait for the deployment to be ready. - This example also assumes you have named your Minio bucket "velero". + * This example also assumes you have named your Minio bucket "velero". + + * Please make sure to set parameter `s3ForcePathStyle=true`. The parameter is used to set the Velero integrated AWS SDK data query address style. There are two types of the address: [virtual-host and path-style](https://docs.aws.amazon.com/AmazonS3/latest/userguide/VirtualHosting.html). If the `s3ForcePathStyle=true` is not set, the default value is false, then the AWS SDK will query in virtual-host style, but the MinIO server only support path-style address by default. The miss match will cause Velero can upload data to MinIO, but cannot download from MinIO. This [link](https://github.com/vmware-tanzu/velero/issues/7268) is an example of this issue. + It can be resolved by two ways: + * Set `s3ForcePathStyle=true` for parameter `--backup-location-config` when installing Velero. This is the preferred way. + * Make MinIO server support virtual-host style address. Add the [MINIO_DOMAIN environment variable](https://min.io/docs/minio/linux/reference/minio-server/settings/core.html#id5) for MinIO server will do the magic. 1. Deploy the example nginx application: From 306a8fda3e71a48320900687924c983fa37a6c80 Mon Sep 17 00:00:00 2001 From: "zhongjun.li" Date: Mon, 8 Jan 2024 11:29:33 +0800 Subject: [PATCH 04/32] fix-item-operation-timeout-explain Signed-off-by: zhongjun.li --- config/crd/v1/bases/velero.io_backups.yaml | 2 +- config/crd/v1/bases/velero.io_restores.yaml | 2 +- config/crd/v1/bases/velero.io_schedules.yaml | 2 +- config/crd/v1/crds/crds.go | 6 +++--- pkg/apis/velero/v1/backup_types.go | 2 +- pkg/apis/velero/v1/restore_types.go | 2 +- site/content/docs/main/api-types/backup.md | 4 ++-- site/content/docs/main/api-types/restore.md | 4 ++-- site/content/docs/v1.13/api-types/backup.md | 4 ++-- site/content/docs/v1.13/api-types/restore.md | 4 ++-- 10 files changed, 16 insertions(+), 16 deletions(-) diff --git a/config/crd/v1/bases/velero.io_backups.yaml b/config/crd/v1/bases/velero.io_backups.yaml index 3c46f833f..cc4d6a587 100644 --- a/config/crd/v1/bases/velero.io_backups.yaml +++ b/config/crd/v1/bases/velero.io_backups.yaml @@ -315,7 +315,7 @@ spec: itemOperationTimeout: description: ItemOperationTimeout specifies the time used to wait for asynchronous BackupItemAction operations The default value is - 1 hour. + 4 hour. type: string labelSelector: description: LabelSelector is a metav1.LabelSelector to filter with diff --git a/config/crd/v1/bases/velero.io_restores.yaml b/config/crd/v1/bases/velero.io_restores.yaml index 74cf7e619..ba01b67b2 100644 --- a/config/crd/v1/bases/velero.io_restores.yaml +++ b/config/crd/v1/bases/velero.io_restores.yaml @@ -246,7 +246,7 @@ spec: type: array itemOperationTimeout: description: ItemOperationTimeout specifies the time used to wait - for RestoreItemAction operations The default value is 1 hour. + for RestoreItemAction operations The default value is 4 hour. type: string labelSelector: description: LabelSelector is a metav1.LabelSelector to filter with diff --git a/config/crd/v1/bases/velero.io_schedules.yaml b/config/crd/v1/bases/velero.io_schedules.yaml index ec217c0b9..7c4de96b5 100644 --- a/config/crd/v1/bases/velero.io_schedules.yaml +++ b/config/crd/v1/bases/velero.io_schedules.yaml @@ -361,7 +361,7 @@ spec: itemOperationTimeout: description: ItemOperationTimeout specifies the time used to wait for asynchronous BackupItemAction operations The default value - is 1 hour. + is 4 hour. type: string labelSelector: description: LabelSelector is a metav1.LabelSelector to filter diff --git a/config/crd/v1/crds/crds.go b/config/crd/v1/crds/crds.go index 93f9e8b5b..ace3e7db6 100644 --- a/config/crd/v1/crds/crds.go +++ b/config/crd/v1/crds/crds.go @@ -30,14 +30,14 @@ import ( var rawCRDs = [][]byte{ []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4VAo\xe46\x0f\xbdϯ \xf6;\xec\xe5\xb3g\xb7\xbd\x14\xbem\xd3\x16\b\x9a\x04A\x12\xe4Nۜ\x19mdI\x95\xa8I\xa7E\xff{A\xc9\xcexl'\xb3Y\xa0\xbaY\xa2\x1e\xc9G>ZEQ\xacЩG\xf2AYS\x01:E\x7f2\x19\xf9\n\xe5\xd3O\xa1Tv\xbd\xff\xbczR\xa6\xad\xe0\"\x06\xb6\xdd\x1d\x05\x1b}C\xbf\xd0F\x19\xc5ʚUG\x8c-2V+\x004\xc62\xcav\x90O\x80\xc6\x1a\xf6Vk\xf2ŖL\xf9\x14k\xaa\xa3\xd2-\xf9\x04>\xb8\xde\x7f*?\xffP~Z\x01\x18쨂\x1a\x9b\xa7\xe8<9\x1b\x14[\xaf(\x94{\xd2\xe4m\xa9\xec*8j\x04}\xebmt\x15\x1c\x0f\xf2\xed\xdes\x8e\xfa\xe7\x04t7\x00\x1dґV\x81\x7f_<\xbeR\x81\x93\x89\xd3ѣ^\n$\x1d\ae\xb6Q\xa3\x9f\x19\x88\x83\xd0XG\x15\xdcH,\x0e\x1bjW\x00}\xa6)\xb6\x02\xb0m\x13w\xa8o\xbd2L\xfe\xc2\xea\xd8\r\x9c\x15\xf05Xs\x8b\xbc\xab\xa0\x1c\xd8-\x1bO\x89\xd8\a\xd5Q`\xec\\\xb2\x1d\b\xfb\xb2\xa5\xfe\x9b\x0f\xe2\xbcE\xa69\x980W\x1ec}88:A9\x12\x01\xa3\xb3\x8c\x18\xd8+\xb3]\x1d\x8d\xf7\x9f3\x15͎:\xacz[\xeb\xc8|\xb9\xbd|\xfc\xf1\xfed\x1b\xc0y\xebȳ\x1aʓר\xfdF\xbb\x00-\x85\xc6+ǩ9>\n`\xb6\x82V\xfa\x8e\x02\xf0\x8e\x06N\xa9\xedc\x00\xbb\x01ީ\x00\x9e\x9c\xa7@&w\xe2\t0\x88\x11\x1a\xb0\xf5Wj\xb8\x84{\xf2\x02\x03ag\xa3n\xa5]\xf7\xe4\x19<5vk\xd4_/\xd8\x01\xd8&\xa7\x1a\x99\xfa\x1e9\xaeTC\x83\x1a\xf6\xa8#\xfd\x1fд\xd0\xe1\x01<\x89\x17\x88f\x84\x97LB\t\xd7\xd6\x13(\xb3\xb1\x15\xec\x98]\xa8\xd6\xeb\xad\xe2Av\x8d\xed\xbah\x14\x1f\xd6IA\xaa\x8el}X\xb7\xb4'\xbd\x0ej[\xa0ov\x8a\xa9\xe1\xe8i\x8dN\x15)t\x93\xa4Wv\xed\xff|/\xd4\xf0\xf1$\xd6Y-\xf3Jby\xa3\x02\xa2\x16P\x01\xb0\xbf\x9a\xb38\x12-[\xc2\xceݯ\xf7\x0f0\xb8NŘ\xb2\x9fx?^\f\xc7\x12\ba\xcal\xc8\xe7\"n\xbc\xed\x12&\x99\xd6Ye8}4Z\x91\x99\xd2\x1fb\xdd)\x96\xba\xff\x11)\xb0Ԫ\x84\x8b4\x8b\xa0&\x88N\xd4Жpi\xe0\x02;\xd2\x17\x18\xe8?/\x800\x1d\n!\xf6\xdbJ0\x1e\xa3S\xe3\xcc\xda\xe8`\x18\x81\xaf\xd4k:\xd6\xee\x1d5R>aP\xae\xaa\x8dj\x926`c=\xe0̾<\x81^\x96\xae\xac<\xfc\xee\xd9z\xdcҕ͘S\xa3\xc5\xd8&w\x86\xe0d\xb2d\x19Ӳ\xe1\f\x1b\x80w\xc8#\xfd2*\xf32\x06\x16\xf3y\xa3\b\xa9\x10(r6h\x1a\xfa-u\x94i\x0egr\xba^\xb8\")\xed\xec3\xd8\r\x93\x19\x83\xf6\xb1.dR\x13\xf8h\xde\x15\xec\xe90?\x13\xe6݉1(\xd3J\x1b\xf4\xd3T\x9c\f\xd4K]ɴ\xe0O\xff\x9b\xe3E&vsw\x05\x13\x92\x83\xce\x14+\r\x8e\xdd!F\x98&\x94|\xc1a\x11\xe5\xa9L̑\x1a\xa2\xa0T\xa0A\x18M\xcc\x11HFKS) rO~\xaev\xa0\x04\x18\xd05hB2^i\x03\x8ahC\r\x10j\b%\xa5d\xc2\x10&\x88a\x05\x90?\xbd\xbd\xdb\x12\xb9\xfb\x1d2\xa3\t\x159\xa1ZˌQ\x0399I^\x15\xe0\xda\xfeySC-\x95,A\x19\x16\xe8\xecJKxZ_{\xc3{i)\xe0j\x91\xdcJ\r\xb8ax*B\xee\x89f\xc7c\x8eL7\xc3E9\xea\x00&\xb6\x12\x15\x1e\xf9\r\xb9\ae\xc1\x10}\x94\x15ϭ\xb0\x9d@Y\x82e\xf2 \xd8?kؚ\x18\x89\x9drj\xc0\v@S\x980\xa0\x04\xe5\xe4Dy\x057H\x92\x82\x9e\x89\x02\xdb\v\xa9D\v\x1eV\xd1\x1b\xf2\xabT@\x98\xd8\xcb7\xe4hL\xa9\u07fczu`&L\x9aL\x16E%\x989\xbfB\xf9g\xbb\xcaH\xa5_\xe5p\x02\xfeJ\xb3Ú\xaa\xec\xc8\fd\x96\x91\xafh\xc9ֈ\xba\xc0\x89\xb3)\xf2\xff\x17\x04@\xbf\xec\xe0j\xceV\x18\xb5QL\x1cZ?\xa0\xd4Op\xc0N\x00'_\xae\xa9\x1bECh\xfb\xc9R\xe7\xf3\xfb\xfb\x87\xb6\xec1ݧ>ҽ%\x90\r\v,\xc1\x98\u0603rL\xdc+Y L\x10\xb9\x93>\x14]\xce@\xf4ɯ\xab]\xc1\x8c\xe5\xfb?*\xd0V\xc8\xe5\x86ܢ&!; U\x99[\xc9ܐ\xad \xb7\xb4\x00~K5<;\x03,\xa5\xf5\xda\x126\x8d\x05m%د\xec\xa8\xd6\xfa!\xe8\xb2\x11~9\x85p_B֙0\xb6\x15۳\f\xa7\x05\xd9K\xd5\xe8\v\xa7\xae6\x1d\x90\xf1)kK\xa6ٽ\xa0\xa5>J\xf3\xc0\n\x90\x95\xe9\xd7\xe8!t{\xbf\xed5\b\xc8x\xd4P\xadT\x1ar;Ϟ(3\x16\xbd\x01Lb\x01\x91/\xa8a\x02<\xd44\x95&\xa6R\x02g\xe9g\xa0\xf9\xf9A\xfe\xa6\x81\xe4\x15\nk\xa6\x00\x87|Cv\xb0\x97\n\"p\x15\xd8\xf6\xb62(e\t\xa3\x11%Y\x99\ry8\x82%#\xad\xb8\xf1r\xcf4y\xfd\x13)\x98\xa8\fl\x06\xd0F\x18\x8cD\xa1\x86\x16\xf2\x04j\x86^﨡\xbf\xdaz=2\xd9\xf6\x04\x01ؑ\xee<\xc9vg\xfbcdT\x9e\xabd\xbboAd\x9a\xacVD*\xb2rK\xe0\xea\x06A\xdbEլ\x99h\xf5\x11\x81\xf8\xc48\x0f\xfd.\x1b\xb9#\xa0\xe3\x9d~\x90\x1f\xb4\x13\xd29B\x8c4k\xd1\xe5\xe9\b\xe6\b\x8a\x942,>\x11\xbc\xf7\x8c\x03\xd1gm\xa0\xf0T\t*?\x10\x11\xa7\x03\xe7\x1e\x84\xb6D\xf58\x0f\xc7)*\xce\xe9\x8e\xc3\x1bbT5\xecΑa'%\a\xda_~\xfat\xf8\fڰl\x86\n\xab>\x19\\\xab\b\x11\x94\xff\x01\xc7\x16\x93\x88f\x96\x19\xfa\b\x84\x06j\xd8e\x91\xf3\x16\x11;\x14 \xff)\xc8;\xab\xb33\xabI\x87\xd8\x12\xaf\xb3\x19p\\'\x84$\\\x8a\x03(כ]\x0f\x83\xe4(\xb0\xb2\x95\x13\xab*\x15p\xab\xf3ɾ\xb2ZtHgB\xec,\x1e\x95\x01&\xb4\x01\x9aoV\xd7d\x10|\xcdx\x95C~댠{k\xbe\xe5\xc1h\x1dh\xc2\x1e\xa3\xdeO6\xf6+(g\x19\xda^\xde\xccZ\xa3\x85\x18cV\xb3\x90\x9eKpF\xaae\x9cǰY![\xd3\\\x83\xb1UV\x7fY\xddX~F\x80v{\xed\xf6\xa1\tUPS \xae\xf9\" \xa1(\xcdy\xc8=f\xa0\x88\x10lRM$\xb2\x8e*E\xcf#\x8c\xab-\xed\xcbX7ּ\xc7<\x11\xaa\xfd\xc1\xec\xeb\xf7\xbb\x90\x81\x11\x88L\x7f\xaf\f\\\xcc2\x8d\xde\"e²\xca:n\x1dNit\x86\"ñ4\xb3\xb6\"\x13\x0e\x1e\xfa9\rc\xbe\x17\xba,\x95\xe41ѭ%Ƌ\xa4\xf5\x10i\xd4*\xfa\x8e\x89r\x94\xf2q\x8e\x10\x7f\xb3u\x1a_\x83d\x18\x80 ;8\xd2\x13\x93\xca\x0f\xbd\xb1\x03\xe0+d\x95\x89\xceejH\xce\xf6{P\x16Ny\xa4\x1a\xb4s7\xc7\t2n>\x93\x96r\x88\xfe\xd8\x1bG\xc3H+\xa98\xf21ԭ!\xd0_\xd1B\xb1\x88Z\v\x17WΜ\x9dX^Q\x8e\x8b(\x15\x99\x1b\x0f\xad\xf1\x8a-\xc6\x13L\x1e\xe0\xec\x96耹\xe5D\xc7\x1d\x91\x02\xac\tZX\x1flX5\xb6ȸ26\xec\x1d\xb5v\x86t\"\xaa*\x0e\xdaw\xe5\f\xbbF\a܌\x82\xae9\xe2\xfcwNw\xc0\x89\x06\x0e\x99\x91*N\x8e9&\xbb\x92\xa2\xd7F\xa8\x18\xd1p]\x87\xa0\x19\xd8\x04H\x82NՑeGg\xa6Y\tB8$\x97\xa0q\x96Ӳ\xe4\x91\x15\xa0)\x93\x9c\xf7\x9dLM\xf4\xa6\xccL\xf9>\xbc\xd8\xe4oJ\x82nlʌ\x96\xecR\xb6\x16\ab\xe4\xe4\xb0\xffo\x126\xa8\xfd\v\x84v;hz]\xa1\xb5$e\xa0\xd1`B\xcb\xe5\x860\x13\xbe\xceA\xb4NN\xd3\xff\xbf0c\x96K\xfc\xb6\xdf\xf2\xaa\x12?ɕ9\x88\x96+u\xf7\xff\x82L\xc1\xc5\xe2ޯ\x15\xc9\f\xf9\xa5\xddꆰ}͐\xfc\x86\xec\x197\xa0z\x9c\xf9\xa6\xf9r\rb\xa4\xacw\xb6\x14\xd4d\xc7\xf7_\xad奛\xbd\x9cD\xba\xf4\x1b;\xfb5\xd8\xf3݅y\x06.\xc1\xa00SP\xb8`\xf3\x03R\xb3\xf9\x82\x16\xd5ۏ\xefbѬnI\x90\xbc\xc1@\xde\xf6\x90mw\xed\x8d\xf2\xd4axӧ\xf6o\xdc6\xc2\r\xa1\xe4\x11\xce\xceb\xa1\x82X\xe6P\xdbш\xa73$\x0e\xeeg\xa0\x90=\xc2\x19\xc1\xf8\r\x8a\xd9֩\xa2\xe0\xca#\x9cS\xaa\xf5\bhqb\xdao\xbcXJ\xda\x0fH\b\x8cg\xa7\x13\x8f\xe0fS\xd0E\xf3\x83#\xe9\x8a$\x94@\xfb\v\x86Y\xb3\xad\xb5Q\x87\x8c}\xa9\x1d\x8b\xec,8\xb22q\xa0v\x99\xc3P\x82\xdc\xd7\xdbM_(gyݑ\x93\xfb\xad\x18\xb7\x86\xbb\xe5\xa34[qC\xde\x7fe\xda\xef\xf8\xbd\x93\xa0?J\x83_\x9e\x85\x9c\x0e\xf1\v\x88\xe9\x1a\xe2\xf4\x12Nm[:\xb4\xf7\xad\x12\x84ە\xad\xf3\xf0j\xf60M\xb6\xc2\xfa-\x9e\x1e\xb8\v麛^\x1f\xba\xa5\xa84nL\t)\xd6.\xf4\x12\xeb\xc9\x11;\x11\xa4T\x1d\x8e\fQ\xab;\x1d\x89\xf5\xc4˃]I\\{\xb7\xaf\xcai\x06y\xd8W\xc1\xdd@j\xe0\xc02R\x80:L-\x1c\xedRZ\xfd\x9e\x86B\xa2\xd6ue\xa1\x84\xa5-\xed\xa1x\xd5\x1d\r~w\xcb\xda\xce܄Z\x81ٳUG6\x01ǫΏ\b\x97X\xb4?f\xa9K\xf3\x1c\xd34(\xbf[\xa0\xf1\x17\xf0b\xb8\xf6;\xc4\xdc\nYPܜ\xf8/\xbb̡@\xff7))S\ts\xf8-\xa6cp\xe8\xb4\xf5Q\xacv7\xb6\a\xa6\x89\xe5\xef\x89\xf2\xe1\xf6rdp\xd2\xea\x16\xe0n!\x97\xfb\x81\xc5rC\x9e\x8eR\xbb5\x157EfA2MV\x8fp\xf6\x9bqm=\xb0ڊ\x95[\xe0\x17\xab\x9b\xdaZ\x90\x82\x9f\xc9\nۮ\xbe\xc5\bJ\x94\xc4\xc4j_\u05cfu\xfaɺ\xa0\xe5\xdaK\xaf\x91\x05\xcbF\xdba\xbaL\xaa\x89m}\xd0`A؆u\x8e\x885\x8f\xa7F\x9b$\xbf\xa5ԑ\x9d\xef\x11T\xee\xa46.\"\xd91g\x97D\xbf\x88\x93=\x1f\xf5\"t\xef\xb2t\xa4\n\xf9\x17V]\xf6\x02\xb5\x96\xdbzZ3\xbb=\x03\x1fIs@\xadC\xb6jf\xbe\xd3\xc3+\xb7g\x81\x9d\xd0\f\x8d\x92Y\xb8\xa5\x92\x19\xe8\xe8nqS\x12\xb4\xfcLp\xb1\x0e,R\xe7\xf8\xb8\xe4\x86\xe9`f(醬%\xd2B\x17\xe0\xfd\xd7V\xd4\xd3*\r\xfb\xf7\x9c\xf0-ŋ\xe0\\/\n\xda\xcf\xe2IB\xf1ֵ\f\xd3\xc4\x03r.\x85:T\xa8\"\xd2-O/H\xdf\xc3\xf2^0\xb1\xc5\x0e\xc8뫛\x03\xb5r\x8d\xe5r\xc4J\x8f\xe4\xbemC\xf4\xfa\x83\x18I戕Rb\xc4_A\x87s\xc3\xf8\xb850\x13A\ni\xdaa\b\v\xb7\x94\xf9KM\xf6Li\xd3F4U(\xe2\xb9\"\xb1\xb2\xd4\xe3\x12\uf57a\xc8\xe1\xfa\xe4Z\xb6\x02`G\xf9\x14r\xa1F\x93'b\x057\x93\x80\xb0=a\x86\x80\xc8d%0lc\xa7:v\xe1X\xe0\x14t2\xc9\xd2\x14\x84- \xaa\"\x8d\x00k\x94:&&\xe3;\xed\xea\x1f(\x8b\xed@\x0f\xcbB\xb6\x99\xb1\x94\xb1X\xe9\xb0-䎵\x93\xda\n\xfa\x95\x15UAhaI\x9f\xea.\xed]\xc6Y\x87\xe3u\xde\x19\xc2\xc5e\xc4H;\xa9J\x0e&uF\xba\f3;M4ˡ^\x98\xbd\x14HA(\xd9S\xc6G\xd2]\x86e\x11m\x97\xf8(^Y\\\xcf\xf9H\xeb|\x8d\xa4H\b\xe0&\x1a\x99\xd3ںT\xe9\xa6❂4\xf3l.\x98\x1d̳R1\x89yzW\xb6м\x88Qq\xfea\xa2\r\xca\x0f\x13m\xa6\xfc0\xd1F\xcb\x0f\x13m\xbe\xfc0\xd1|\xf9a\xa2\x85\xf2\xc3D\xfba\xa2MU\x9b\xd2\xd6s\x18\xb9\xd3q#?\xceb\x91\xb0\xad=\x85\xe2\x04|\x9f\x85\xe1\xf3\xbcS33\xb7\xf1V\x91<\xfe\xe4\xdcp\xddZJ\xeaTM;A\x82x\xbb\xc3>3ɛߐ/\x1f:\xbd(_~;\xd9\xf8J\xf9\xf2\x1eþ\xd5}\xa5l\xf90\xfee\xd9\xf27>U\xa3\x00\x1a\xc2\xf3n/>\x1f\xeb\xb2\xd7\xdb\x00\xf0\x1f\x9c\x7f;\xc8\x0f\xbb\x8c\xf1Ϟm?\xc2\xfc\xc4\xc4\xf8\xd5_V\xdf\x1f\xa5\x17\xd3v\x94\x9a\x032E\x06\x15\x8e\xf9Z\xbf\xb2\x9d\xdc\xd5M\xa4\xfb>\x85s\xa94\xa6f\xccO\xd1k\xa8eZ\x04\xfb^'\xb3\x81\xe2S\xe9\u05ca\xb4\x13\x9d\xdbH\x93\xb93\x9d\x91\xf1`\x10@\x9fEvTR\xc8J\xfb\xb8\x81\x85\xfe\x16\xc3\x17~+\x14\xd3\xc0\x12\x15\xeckr\x94U$c{\x82v3\xf9{\xe3Y{~\x8f\x1a\f=\xbd\xdet\x7f1\xd2\xe7\xf0\x91'f\x8e\x11<\x9f\x8e pw]\x1c\xda\t\xf9a\xc2\xf9\x03\xe7}A\"R\x11\xc1\xf8\u0602U\x9f\xca\xef,M\x9fJ\x17$Z\xbc\xf2O\a8Ҳ\xfc.\xce\xed\xeb\xe6\xee\x8d\x18\x81K7\xb3ӏ0\xa4g\xefM\xa7\xdb-\xc9\xd9\xebg\xe4\x8d\x02\x9d\xcf\xd4K\x89M\xcdd\xe5]\x90\x8b\x97\x98\x87\xfd\xcd[\xef)\xd9v\x17\xe5\xd8ͦ*'f\xd6us\xe6\xa6A.ȧK\"\xce|\xee\xdc\xe2\x8c9\x9f\xa169\x8e\xe4<\xb9H\x06\xdc$\xe0\xd1츩\xbc\xb7\x99\xb8\xf70'.=\xdbm\x124f\xc2\xcd\xe7\xb8]/\x93\xfd\x1a^\xf6\xb8\xaa\x99\xcdS\x9b\xf5§\xf1\x9b\xcdD[\x92\x7f6K\xb1\vs\xcd\xea\\\xb2\x91~\x97f\x98u3\xc8F\x80\xa6䕍䍍@\x9c\xcc&K\xcd\x16\x1b\x81=\xb3\xecNJ\xc9\xe4\x8fK\xb2\xc4◨\x90\xd9Ր\xffQ\xf2w)\x19\xa4\xea\x18\x97s\x0eͧ^u\xcb\xf9`cM\x1b\xab1;\x95\x99\xe3rc\xb5\xa8\xb8a%\xc7\xed\xc5\x13ˣ>\xbb9¹\xbe\x18\xe2w\x89\xc75\xdde&\xe4\xd3\xe7Z\x987=\x93\x9bj\xf2\x04\x9c\x13\x1a\x13\xc5\xc1\xc83w\x0fP&\xd7`W\b;=\xfd\x95\x17\xfe\xba\xa0\x1b'\xefx\"5\xb6\x03c\x8ePX(\xe3מ\x8c\xaa\xf2is\xd2Y\xbe\xf8\xed\x1f\x15\xa83\xc1\xebYj\xfbb\xe6<\x94\x9b\x96\xda\xfaBAQxm\xe3n\x9f\xea\x99\xd9\xcd\xf4$o\x85[\xf0\xa2`{8\"\x1c\xab!x\xcdk\xab\f\xad\xd70R5\x1e\x88\x95u\xeb\xc8\xefs\x96j\xeaa\xa2\xe7u4\x96\xbb\x1a\xb3\x8b\xfc\xb3\xb8\x1b\x97;\x1c\x13 S\x0f\a\xa5m\x88\xcf\x1e\x06z.\xc7c\xce\xf5H\xb6\xb9\xd2\x0e\xfb<\xc7!\x9f\x05\x87{\x16\xb8 ˜\x90d2\xa5\x1c\xe2y\x16W\xe4\x19\x9d\x91\xe7pG.sHf@\xf6\x0e\xe7\xa4\x1c\xbbIJ\xf6H\xde\xefLI֘ߒ\x9c>N\x93p\x8c&a\xb3r\x0eӄ\xe32ˎ\xc9$\xd0\xf0\x99\\\x95grV\x9e\xc3]y^\x87e\xd6e\x99\x95\x9c\x99\x9f\x97\x1do\xb98x/U\x0ejr\xaf#U4'\x85\xb2\xe7_t\xfb\xecE\xfeÝr\xb6Vǔ\x8d\x05\xac\xebS\xef\x19\xf9\x99\t\xbf\x8fj\x85\xb0\xb5\xeew6`\x1aC$\x1e\xffo\xac<\x7fۨ۵\xd1PR\x85;\xac\xbb\xb3K\xad\xd0\x1b\xf2\x9ef\xc7\x1e\xf4cԯ\xd8KUPCV\xf5\x96\xd7+\a\xdc\xfe\xbd\xda\x10\xf2A֛\xf6\xed\x9bd4+J~\xb6~C\x04\xe6\xaa\r\xe22\x81\x88\n_\xe8\xffNr\x96E,\xad\xe8\xe5B\xae\xf2\xe0J\b\xbc\xf2(ko}\x97\xb6b\xdc\xd0B\xa3\xac{\xbd\xe2^r.\x9f\x16\xba\xe3\xb4d\xff\x81\xb74\xcf\xc7p\xde\xdem\xb1j\x90\x14\xbcݹ\xce\x10\xaa\x91ށ]1\x9b\xe1\x8c\xcd\xf8\xed\xbe\x031\x92iW\xff\x89\xd2Z\xaf\xd8l\xec\xd6%\x97\xf5g5\xcd\xdd\xd6a\xb7Aa\xa1\xe2L$\xe6z\x98#S\xf9\xba\xa4ʜ]^\xc1M\x8d\xc3x\x1c'\xac\x9bSі\xd1\xe5ex\xddo\x94\xb6\xe1\xd6_\xdc\xcc;\x97ݭ\xd0>E/\xc1c\xfc(\xdf\xec!\xbe+\xe21n\x82\xac\x91R\x91\xcfѤ\xa4\xabE\xb1\xb4\xbf\xda\xf6Wy\x82w\xd1hV\x87<\xf7\xbd\xea\x91t\xa2\x00\xd1]\xee:u;(^\xcey\x99.\x8a\xe7\a\x85\xae\xfd\xf5\x9d\x89c\xf1\xb5#C\t7\x97\x06\xb8:\x1e\xb5\xb1\xd3\xeb\xee\v\xbaV\xb5\n\xf3Ǝw\x9eB\xe8\xaa\x7f\xc1\xdd_\xaf\x9f#\xa5\x8dT\xf4\x00\xbfHw\xf5\xf2\x1c\r\xba\xb5;\xf7n{\x93'\xe4,\x86\xd9\x10s\x05\xfc%\xd0=`M*\xf2\xe0\x1a\\\x8b\xe5\xc2[}\x8d\xe13\x83yx\xf8\xc5\r\xc0\xb0\x026\xef*\xb7\x97o\xb5\x9d\x06K\xcd00\xd7hg\xff{\x8c\xac\x17\x04\xef\x93m\U00067177\x02Lwƴ\xb7E\xd8W%\x974\au+Ş\x1df\x06\xf2[\xa7ro\x9d\xcc\xf0\xa3\x1f\\\xbd\xfa\x04\xf8W\xdeu\xb7f\f\xe7\xc0?0\x0eڡ\x95\xa0D\uf1adj\x9dZ\x15;g\xa8\xed\xed\x8fu\a#\xab\x8f\x1b\x16\xc6|KP\xd60r\xd1\xe1J\a\xb1\x1c\x1fx\xc3\x11&\f\x1c\"\xf1\xe6\t-z\xea\\\xfc\x1dDzN\xa5|\x89\xb7j\xc5\x03[\x93ʙ\x8aQ\x8d2\x06\xa7\xf5\xf6\x01F\xca\xf1x\xf9uo\xc9\x1c3\xfe\xc7n\x87\xc7\x1b\xd1\xe7\xef\x87w\x17\xa7\xfb\xd7 \xbc W\n\xaf\xb8\xf4\x97\xaa㕐\x17]\x11\xbf\xab\xf3x\xea,!\xfd\xd6\x18(\xca\xe8\x05\xd2\x11\xf4F\xda\xd6\x06\x894\x947\xa2\x1b[\x02\xea&\x98a4\x99Z\xe4\xa6\xec\x04㦄66\xd6[\x9f\x93~\xc9X\xeb\xb6\xe9c\xd5U\x96\x81\xd6\xfb\x8a\xf3s\x9d\x0f\xbfd\xe01k\xe0J\xa4\xf8@\x19\xbf\x88\x0e\xae\xe1\b\x11\xdc\xd8F\u05fd$6\xfb$\\\x10y\x98\xbc\x83\xa5\xdb\x16<%\xb2\x8c\x0e\x9e\x05>7N\x1bZ\xcc\xddt\x7f;l\x81ϐ\xa8\xbc\x95MW_\xd7\xfeDu\xc3昚m\xc0\xb9\x96\xe82Xh\x90\x138\x81 v\x9dr\xf4\rO\xe5\xf4\xdbD\xa0\xb6\xa1\xf8c\x14N\xd7\a\xcd\x1f<\x7f\xff\xbc\xca\x03\x9a[\xea\x04\ua95e\x80Y_\xc0\x1f!\xc2P2\x9d+\xfe\xc6ڲ\xb0\x8e\x02M2բ\xba6Ӭ\xab瓕\xd6\xed\xfdv\xac\xe5\xa8\x04\x87\n1\xfe\r\x1e\xba\xf8F%5\x1cY\xaa\x8a\x1a\x8elNAu\xd4Qdp\x8d\x82\xba\xfa0q\xae\xceސ\x8d\x95\x9c\x05\x80\xc7\xc1\xc2\xf3\a\xee\xb4\xeb\xfa\xfd%\xc7mw\xaf\"u\x89\xca\xf8ސa\n\x9a\a\xbc\x06\bI\xecx\x91\xa7\xe1\xa8\x10}\xeaj\x88i\xbbn\x98`^\xaf\xfa(\xa4\x7f\xf9\xea\xc6\xfb\xae\xf1\xe0JA\x7f\x97\xea\x86\x14L\xd8\x7f\xa8\xc8\xdd\x06Ph\xbc\b\xff\xa3\x94\x8f\xf7\x11\xabr\x80\xfc\xdf\xea\x8aMt\x9e\t\x876\x1e\xad\xdb\xc9\xca\xef\xdb\xd6\x16f|/\x0f\xaf9\xbf\xb2\xe7\x840'\x14zt8\v\xf4\xb8\xeb`\xc4m\xba\x0f\xaf/q~\xbe\xe9C\xee\xbd\xd0\xd6\xc0\x9e\x82\x88\x92\xeb\x17\xf1\xf6\xb5P~\xaf\xa4\a\xc4U\x9f\xb8\xe3\x9e\xf4\xec\xc8K\xbc7O\xe31\x93/N\xe0i;\xcf!߲\xd4FP\xf7\xf6\x9b\x9b\xd5\xd7u<\xf1\xc2\xff\x19ɿ\xb3u\xea\xb3\xe2-7\n\xc2z0\x16X\x8a\x9f\x0f^\x93\x8f0\x8c\x83\xb8#\xbf\x90\xe3\x8e\x7f\xecy;[e+\xee\x94<(\xd0C\xc1Y\x93\xbfSf\x988|\x90\xea\x8eW\a&\x1as{Q\xe5;\xaa\f\xb3\xa2\xec\xf0\x89!\xca\x04\xe5\xec\x9f1\xe5\xd4\xfeq\x1ePmmD~K@c\xec\x87w`-\xcdQo;\xaa\aKO\xd79Y\xf0\xd5\xe6t`X\xfb\x1b\xdb!\x16\xee\xf1\xc06\xe4\xa34\x10\x12_X\x17\xa6\xb5\x96@\x9b5\xec\xf7R\x19\xb7!\xba^\x13\xb6\xf7\xde{\x04\xaeU\x1c\x18\xc4q\xef\xe3\x11f\x9aL\xe7f\xb9\xc1@\xaa\xc2U\x13/\xf4.\xe8\xd9mw\xd0,\xab\xacE\xf4J\x1b\xca#\x06\xc97)j\f\x93\xd8\xf9\x02\xf9o)\xbb@\xdbv\xfdaX\v\xc19\xca\xe1U\x00\xce\x18\x8b\x9a\xa6\x04\xc3\xe0 ȓb\xc6X\x03\xa8\x9d\xd9H\x8c5y8'\xda\x1a\x01\x17ŷ\x88Sp\xdb\xf1L\x8bn\xfc\xb4\xae<\xa6\x1f\xfd\xe0\xf09\xb8\x1d\x92`4n\xe73J|[\xcb\xca\xecH\xc5\xc1\n\x95\x92\xd5\xe1\x18\xe4rĔ\x1d\x81\x9bW\x80\xb1@\xd4\x10:d\x95\x99J\x89\xd6\xces}\xbc\xb5A\x97f\x8f\xa3\x98\xfa̙\xf0F\xeb+\xff\xce\xc3z\xafd\xb1\xf6\xbc\xc0d\xb0\x1b\xbf\x1b\xac\x98\xb4ք9FIN\xdc\x13p\xfeBu\x14\x83\xb2\x04A\xa8\xf6\xf8$܃s\xf1\xea\xa1\rU&5\fpߩ<\x13\x01@\xc8q|\xef\xfdn\xb7\xbb\x0f\xe8ֿ\x80X\x03\xbe!\x9a\x89\xf0<\xac\xdbKw\xa2\xa0\x89\x14\xf8\x8a\x9cT\xf1<\xbf\x81K\xdfq\xe0\xbb\xe8\xff\xb1\xbe\xfb\xa9^\x13ߧ8\x81_z\xd5{g>\xf1-\xc0\xba\x8aw\xdc\"\xf4\xf8\x13ۻ\xd4\xc3\xccb\xfd\xe7\xff\xf5\xb3\x9c\xa7$'\xe3\xe5\xa4\x7f\x81\xaeC\xed(̼\xfcw\xc7\xc1Z>\x1a\xa0뺼\\䣞.\x8b\xba\\3\xe4\x12^.\xbeN \xe2tY\xb0\xe5\xd9\"-\xd7\x1d\xdd\x13\xc5\xd7R\xe7\xe6\xd8\xdf}\xb5H\xa8\xc5C\x88\x04[\"è\xc3/\xb3\xc1\x96V\xac%\xe08\xf2\xb8Y/\xfer\xa5hKt\x1d\x18|D\x05\x9a\xb7\xe6\xb6\xef\xc9\x7f\xf9\x9f\x00\x00\x00\xff\xffqN\x18=X}\x00\x00"), + []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xec=M\x93\x1b+\x92w\xff\nB{\xf0\xccDK\x1e\xef\xeea\xc37O\xdb\xdeQ\xbc\xf7\xec\x0ew?\xcfe/\xa8*%\xf1\x9a\x82\x1a\xa0\xd4\xd6l\xec\x7f\xdf \x81\xfa\xa4\xaa(Y\xfdֳa.v\x97 I2\x93$3I`\xbd^\xbf\xa0%\xfb\x02J3)\xde\x10Z2\xf8j@ؿ\xf4\xe6\xf1?\xf4\x86\xc9W\xa7\xd7/\x1e\x99\xc8ߐ\xdbJ\x1bY|\x06-+\x95\xc1;\xd83\xc1\f\x93\xe2E\x01\x86\xe6\xd4\xd07/\b\xa1BHC\xedgm\xff$$\x93\xc2(\xc99\xa8\xf5\x01\xc4\xe6\xb1\xda\xc1\xaeb<\a\x85\xc0Cק?o^\xff\xeb\xe6\xcf/\b\x11\xb4\x807dG\xb3Ǫԛ\x13pPr\xc3\xe4\v]BfA\x1e\x94\xac\xca7\xa4\xf9\xc15\xf1\xdd9T\xff\x82\xad\xf1\x03g\xda\xfc\xd4\xfa\xf83\xd3\x06\x7f(y\xa5(\xaf{\xc2o\x9a\x89Cũ\n__\x10\xa23Y\xc2\x1b\xf2\xd1vQ\xd2\f\xf2\x17\x84x\xac\xb1˵G\xf8\xf4\xdaAȎPP\x87\v!\xb2\x04\xf1\xf6n\xfb\xe5\xdf\xee;\x9f\t\xc9Ag\x8a\x95\x06\xc7\xee\x10#L\x13J\xbeఈ\xf2T&\xe6H\rQP*\xd0 \x8c&\xe6\b$\xa3\xa5\xa9\x14\x10\xb9'?U;P\x02\f\xe8\x1a4!\x19\xaf\xb4\x01E\xb4\xa1\x06\b5\x84\x92R2a\b\x13İ\x02\xc8\x1f\xde\xdem\x89\xdc\xfd\x06\x99ф\x8a\x9cP\xadeƨ\x81\x9c\x9c$\xaf\npm\xff\xb8\xa9\xa1\x96J\x96\xa0\f\vtv\xa5%<\xad\xaf\xbdὴ\x14p\xb5Hn\xa5\x06\xdc0<\x15!\xf7D\xb3\xe31G\xa6\x9b\xe1\xa2\x1cu\x00\x13[\x89\n\x8f\xfc\x86܃\xb2`\x88>ʊ\xe7V\xd8N\xa0,\xc12y\x10\xec\x1f5lM\x8c\xc4N95\xe0\x05\xa0)L\x18P\x82rr\xa2\xbc\x82\x1b$IA\xcfD\x81\xed\x85T\xa2\x05\x0f\xab\xe8\r\xf9E* L\xec\xe5\x1br4\xa6\xd4o^\xbd:0\x13&M&\x8b\xa2\x12̜_\xa1\xfc\xb3]e\xa4үr8\x01\x7f\xa5\xd9aMUvd\x062\xcb\xc8W\xb4dkD]\xe0\xc4\xd9\x14\xf9\xbf\x04\x01\xd0/;\xb8\x9a\xb3\x15Fm\x14\x13\x87\xd6\x0f(\xf5\x13\x1c\xb0\x13\xc0ɗk\xeaF\xd1\x10\xda~\xb2\xd4\xf9\xfc\xfe\xfe\xa1-{L\xf7\xa9\x8fto\td\xc3\x02K0&\xf6\xa0\x1c\x13\xf7J\x16\b\x13D\xee\xa4\x0fE\x973\x10}\xf2\xebjW0c\xf9\xfe\xf7\n\xb4\x15r\xb9!\xb7\xa8I\xc8\x0eHU\xe6V27d+\xc8--\x80\xdfR\r\xcf\xce\x00Ki\xbd\xb6\x84McA[\t\xf6+;\xaa\xb5~\b\xbal\x84_N!ܗ\x90u&\x8cm\xc5\xf6,\xc3iA\xf6R5\xfa©\xabM\ad|\xcaڒiv/h\xa9\x8f\xd2<\xb0\x02de\xfa5z\b\xdd\xdeo{\r\x022\x1e5T+\x95\x86\xdcγ'ʌEo\x00\x93X@\xe4\vj\x98\x00\x0f5M\xa5\x89\xa9\x94\xc0Y\xfa\x19h~~\x90\xbfj y\x85\u009a)\xc0!ߐ\x1d쥂\b\\\x05\xb6\xbd\xad\fJY\xc2hDIVfC\x1e\x8e`\xc9H+n\xbc\xdc3M^\xff\x99\x14LT\x066\x03h#\fF\xa2PC\vy\x025C\xafw\xd4\xd0_l\xbd\x1e\x99l{\x82\x00\xecHw\x9ed\xbb\xb3\xfd12*\xcfU\xb2ݷ 2MV+\"\x15Y\xb9%pu\x83\xa0\xed\xa2j\xd6L\xb4\xfa\x88@|b\x9c\x87~\x97\x8d\xdc\x11\xd0\xf1N?\xc8\x0f\xda\t\xe9\x1c!F\x9a\xb5\xe8\xf2t\x04s\x04EJ\x19\x16\x9f\b\xde{Ɓ\xe8\xb36Px\xaa\x04\x95\x1f\x88\x88Ӂs\x0fB[\xa2z\x9c\x87\xe3\x14\x15\xe7t\xc7\xe1\r1\xaa\x1av\xe7Ȱ\x93\x92\x03\xed/?}:|\x06mX6C\x85U\x9f\f\xaeU\x84\b\xca\xff\x80c\x8bID3\xcb\f}\x04B\x035\xec\xb2\xc8y\x8b\x88\x1d\n\x90\xff\x12\xe4\x9d\xd5ٙդCl\x89\xd7\xd9\f8\xae\x13B\x12.\xc5\x01\x94\xebͮ\x87Ar\x14X\xd9ʉU\x95\n\xb8\xd5\xf9d_Y-:\xa43!v\x16\x8f\xca\x00\x13\xda\x00\xcd7\xabk2\b\xbef\xbc\xca!\xbfuFн5\xdf\xf2`\xb4\x0e4a\x8fQ\xef'\x1b\xfb\x15\x94\xb3\fm/of\xad\xd1B\x8c1\xabYH\xcf%8#\xd52\xcecج\x90\xadi\xae\xc1\xd8*\xab?\xadn,?#@\xbb\xbdv\xfbЄ*\xa8)\x10\xd7|\x11\x90P\x94\xe6<\xe4\x1e3PD\b6\xa9&\x12YG\x95\xa2\xe7\x11\xc6Ֆ\xf6e\xac\x1bk\xdec\x9e\b\xd5~g\xf6\xf5\xfb]\xc8\xc0\bD\xa6\xbfW\x06.f\x99Fo\x912aYe\x1d\xb7\x0e\xa74:C\x91\xe1X\x9aY[\x91\t\a\x0f\xfd\x9c\x861\xdf\v]\x96J\xf2\x98\xe8\xd6\x12\xe3E\xd2z\x884j\x15}\xc7D9J\xf98G\x88\xbf\xda:\x8d\xafA2\f@\x90\x1d\x1c\xe9\x89I\xe5\x87\xde\xd8\x01\xf0\x15\xb2\xcaD\xe725$g\xfb=(\v\xa7h\xb0 l\xc3:GĚ\xc7S\xa3M\x92\xdfR\xea\xc8\xce\xf7\b*wR\x1b\x17\x91옳K\xa2_\xc4ɞ\x8fz\x11\xbawY:R\x85\xfc\v\xab.{\x81Z\xcbm=\xad\x99ݞ\x81\x8f\xa49\xa0\xd6![53\xdf\xe9\xe1\x95۳\xc0Nh\x86F\xc9,\xdcR\xc9\ftt\xb7\xb8)\tZ~&\xb8X\a\x16\xa9s|\\r\xc3t03\x94tC\xd6\x12i\xa1\v\xf0\xfek+\xeai\x95\x86\xfd{N\xf8\x96\xe2Ep\xae\x17\x05\xedg\xf1$\xa1x\xebZ\x86i\xe2\x019\x97B\x1d*T\x11閧\x17\xa4\xefay/\x98\xd8b\a\xe4\xf5\xd5́Z\xb9\xc6r9b\xa5Gr߶!z\xfdA\x8c$s\xc4J)1⯠ùa|\xdc\x1a\x98\x89 \x854\xed0\x84\x85[\xca\xfc\xa5&{\xa6\xb4i#\x9a*\x14\xf1\\\x91XY\xeaq\x89\xf7J]\xe4p}r-[\x01\xb0\xa3|\n\xb9P\xa3\xc9\x13\xb1\x82\x9bI@؞0C@d\xb2\x12\x18\xb6\xb1S\x1d\xbbp,p\n:\x99di\n\xc2\x16\x10U\x91F\x805J\x1d\x13\x93\xf1\x9dv\xf5\x0f\x94\xc5v\xa0\x87e!\xdb\xccX\xcaX\xact\xd8\x16r\xc7\xdaIm\x05\xfdʊ\xaa \xb4\xb0\xa4Ou\x97\xf6.\xe3\xac\xc3\xf1:\xef\f\xe1\xe22b\xa4\x9dT%\a\x93:#]\x86\x99\x9d&\x9a\xe5P/\xcc^\n\xa4 \x94\xec)\xe3#\xe9.ò\x88\xb6K|\x14\xaf,\xae\xe7|\xa4u\xbeFR$\x04p\x13\x8d\xccim]\xaatS\xf1NA\x9ay6\x17\xcc\x0e\xe6Y\xa9\x98\xc4<\xbd+[h^Ĩ8\xff0\xd1\x06凉6S~\x98h\xa3凉6_~\x98h\xbe\xfc0\xd1B\xf9a\xa2\xfd0Ѧ\xaaMi\xeb9\x8c\xdc鸑\x1fg\xb1H\xd8֞Bq\x02\xbe\xcf\xc2\xf0yީ\x99\x99\xdbx\xabH\x1e\x7frn\xb8n-%u\xaa\xa6\x9d A\xbc\xdda\x9f\x99\xe4\xcdoȗ\x0f\x9d^\x94/\xbf\x9dl|\xa5|y\x8fa\xdf\xea\xbeR\xb6|\x18\xff\xb2l\xf9\x1b\x9f\xaaQ\x00\r\xe1y\xb7\x17\x9f\x8fu\xd9\xebm\x00\xf8wο\x1d\xe4\x87]\xc6\xf8g϶\x1fa~bb\xfc\xeaO\xab\xef\x8fҋi;J\xcd\x01\x99\"\x83\n\xc7|\xad_\xd9N\xee\xea&\xd2}\x9f¹T\x1aS3\xe6\xa7\xe85\xd42-\x82}\xaf\x93\xd9@\xf1\xa9\xf4kEډ\xcem\xa4\xc9ܙ\xce\xc8x0\b\xa0\xcf\";*)d\xa5}\xdc\xc0B\x7f\x8b\xe1\v\xbf\x15\x8ai`\x89\n\xf6\xdf\xc9QV\x91\x8c\xed\t\xda\xcd\xe4\xef\x8dg\xed\xf9=j0\xf4\xf4z\xd3\xfd\xc5H\x9f\xc3G\x9e\x989F\xf0|:\x82\xc0\xdduqh'\xe4\x87\t\xe7\x0f\x9c\xf7\x05\x89HE\x04\xe3c\vV}*\xbf\xb34}*]\x90h\xf1\xca?\x1d\xe0H\xcb\xf2\xbb8\xb7\xaf\x9b\xbb7b\x04.\xdd\xccN?\u0090\x9e\xbd7\x9dn\xb7$g\xaf\x9f\x917\nt>S/%65\x93\x95wA.^b\x1e\xf67o\xbd\xa7d\xdb]\x94c7\x9b\xaa\x9c\x98Y\xd7͙\x9b\x06\xb9 \x9f.\x898\xf3\xb9s\x8b3\xe6|\x86\xda\xe48\x92\xf3\xe4\"\x19p\x93\x80G\xb3\xe3\xa6\xf2\xdef\xe2\xdeÜ\xb8\xf4l\xb7IИ\t7\x9f\xe3v\xbdL\xf6kx\xd9\xe3\xaaf6Om\xd6\v\x9f\xc6o6\x13mI\xfe\xd9,\xc5.\xcc5\xabs\xc9F\xfa]\x9aa\xd6\xcd \x1b\x01\x9a\x92W6\x9276\x02q2\x9b,5[l\x04\xf6̲;)%\x93?.\xc9\x12\x8b_\xa2BfWC\xfe{\xc9ߥd\x90\xaac\\\xce94\x9fz\xd5-烍5m\xac\xc6\xecTf\x8eˍբ↕\x1c\xb7\x17O,\x8f\xfa\xec\xe6\b\xe7\xfab\x88\xdf$\x1e\xd7t\x97\x99\x90O\x9fka\xde\xf4Ln\xaa\xc9\x13pNhL\x14\a#\xcf\xdc=@\x99\\\x83]!\xec\xf4\xf4W^\xf8\xeb\x82n\x9c\xbc\xe3\x89\xd4\xd8\x0e\x8c9Ba\xa1\x8c_{2\xaaʧ\xcdIg\xf9ⷿW\xa0\xce\x04\xafg\xa9틙\xf3PnZj\xeb\v\x05Eᵍ\xbb}\xaagf7ӓ\xbc\x15n\xc1\x8b\x82\xed\xe1\x88p\xac\x86\xe05\xaf\xad2\xb4^\xc3H\xd5x V֭#\xbf\xcfY\xaa\xa9\x87\x89\x9e\xd7\xd1X\xeej\xcc.\xf2\xcf\xe2n\\\xeepL\x80L=\x1c\x94\xb6!>{\x18\xe8\xb9\x1c\x8f9\xd7#\xd9\xe6J;\xec\xf3\x1c\x87|\x16\x1c\xeeY\xe0\x82,sB\x92ɔr\x88\xe7Y\\\x91gtF\x9e\xc3\x1d\xb9\xcc!\x99\x01\xd9;\x9c\x93r\xec&)\xd9#y\xbf3%Yc~Kr\xfa8M\xc21\x9a\x84\xcd\xca9L\x13\x8e\xcb,;&\x93@\xc3grU\x9e\xc9Yy\x0ew\xe5y\x1d\x96Y\x97eVrf~^v\xbc\xe5\xe2\xe0\xbdT9\xa8ɽ\x8eTќ\x14ʞ\x7f\xd1\xed\xb3\x17\xf9\x0fw\xca\xd9Z\x1dS6\x16\xb0\xaeO\xbdg\xe4'&\xfc>\xaa\x15\xc2ֺ\xdfـi\f\x91x\xfc\xbf\xb1\xf2\xfcm\xa3n\xd7FCI\x15\xee\xb0\xee\xce.\xb5Bo\xc8{\x9a\x1d{ЏQ\xbfb/UA\rY\xd5[^\xaf\x1cp\xfb\xf7jC\xc8\aYoڷo\x92Ѭ(\xf9\xd9\xfa\r\x11\x98\xab6\x88\xcb\x04\"*|\xa1\xff;\xc9Y\x16\xb1\xb4\xa2\x97\v\xb9ʃ+!\xf0ʣ\xac\xbd\xf5]ڊqC\v\x8d\xb2\xee\xf5\x8a{ɹ|Z\xe8\x8eӒ\xfd'\xde\xd2<\x1f\xc3y{\xb7ŪAR\xf0v\xe7:C\xa8Fz\av\xc5l\x8636\xe3\xb7\xfb\x0e\xc4H\xa6]\xfd'Jk\xbdb\xb3\xb1[\x97\\֟\xd54w[\x87\xdd\x06\x85\x85\x8a3\x91\x98\xeba\x8eL\xe5\xeb\x92*svy\x0575\x0e\xe3q\x9c\xb0nNE[F\x97\x97\xe1u\xbfQچ[\x7fq3\xef\\v\xb7B\xfb\x14\xbd\x04\x8f\xf1\xa3|\xb3\x87\xf8\xae\x88Ǹ\t\xb2FJE>G\x93\x92\xae\x16\xc5\xd2\xfej\xdb_\xe4\t\xdeE\xa3Y\x1d\xf2\xdc\xf7\xaaG҉\x02Dw\xb9\xeb\xd4\xed\xa0x9\xe7e\xba(\x9e\x1f\x14\xba\xf6\xd7w&\x8e\xc5\u05ce\f%\xdc\\\x1a\xe0\xeax\xd4\xc6N\xaf\xbb/\xe8Z\xd5*\xcc\x1b;\xdey\n\xa1\xab\xfe\x05w\x7f\xb9~\x8e\x946R\xd1\x03\xfc,\xdd\xd5\xcbs4\xe8\xd6\xeeܻ\xedM\x9e\x90\xb3\x18fC\xcc\x15\xf0\x97@\xf7\x805\xa9ȃkp-\x96\vo\xf55\x86\xcf\f\xe6\xe1\xe1g7\x00\xc3\nؼ\xab\xdc^\xbe\xd5v\x1a,5\xc3\xc0\\\xa3\x9d\xfd\xef1\xb2^\x10\xbcO\xb6ş\x16\xde\n0\xdd\x19\xd3\xde\x16a_\x95\\\xd2\x1cԭ\x14{v\x98\x19ȯ\x9dʽu2Ï~p\xf5\xea\x13\xe0_y\xd7ݚ1\x9c\x03\xff\xc08h\x87V\x82\x12\xbd\x1b\xb6\xaaujU육\xb6\xb7?\xd6\x1d\x8c\xac>nX\x18\xf3-AY\xc3\xc8E\x87+\x1d\xc4r|\xe0\rG\x980p\x88ě'\xb4\xe8\xa9s\xf1w\x10\xe99\x95\xf2%ު\x15\x0flM*g*F5\xca\x18\x9c\xd6\xdb\a\x18)\xc7\xe3\xe5\u05fd%s\xcc\xf8\x1f\xbb\x1d\x1eoD\x9f\xbf\x1f\xde]\x9c\xee_\x83\xf0\x82\\)\xbc\xe2\xd2_\xaa\x8eWB^tE\xfc\xae\xce㩳\x84\xf4[c\xa0(\xa3\x17HG\xd0\x1bi[\x1b$\xd2Pވnl\t\xa8\x9b`\x86\xd1dj\x91\x9b\xb2\x13\x8c\x9b\x12\xda\xd8Xo}N\xfa%c\xadۦ\x8fUWY\x06Z\xef+\xce\xcfu>\xfc\x92\x81Ǭ\x81+\x91\xe2\x03e\xfc\":\xb8\x86#Dpc\x1b]\xf7\x92\xd8\xec\x93pA\xe4a\xf2\x0e\x96n[\xf0\x94\xc82:x\x16\xf8\xdc8mh1w\xd3\xfd\xed\xb0\x05>C\xa2\xf2V6]}]\xfb\x13\xd5\r\x9bcj\xb6\x01\xe7Z\xa2\xcb`\xa1AN\xe0\x04\x82\xd8u\xca\xd17<\x95\xd3o\x13\x81چ\xe2\x8fQ8]\x1f4\x7f\xf0\xfc\xfd\xf3*\x0fhn\xa9\x13\xa8\x97z\x02f}\x01\x7f\x84\bC\xc9t\xae\xf8\x1bk\xcb\xc2:\n4\xc9T\x8b\xea\xdaL\xb3\xae\x9eOVZ\xb7\xf7۱\x96\xa3\x12\x1c*\xc4\xf87x\xe8\xe2\x1b\x95\xd4pd\xa9*j8\xb29\x05\xd5QG\x91\xc15\n\xea\xea\xc3Ĺ:{C6Vr\x16\x00\x1e\a\v\xcf\x1f\xb8\xf3`\x05hM\x0f\xe1j\xec'k0\x1f@\x00\x06\x8f\"\xa3\xf1\xe1\xf8\xe6\xfcP\xf7bh\xb7oH3SQ\xdfA\xc8\xcel\xd5z\x19S\xc0\\\x1e\xdc\xc3\x15,\x03ճC\xfbЮ\xeb\xf7\x97\x1c\xb7ݽ\x8a\xd4%*\xe3{C\x86)h\x1e\xf0\x1a $\xb1\xe3E\x9e\x86\xa3B\xf4\xa9\xab!\xa6\xed\xbaa\x82y\xbd꣐\xfe\xe5\xab\x1b\xef\xbbƃ+\x05\xfdM\xaa\x1bR0a\xff\xa1\"w\x1b@\xa1\xf1\"\xfc\x8fR>\xdeG\xac\xca\x01\xf2\x7f\xad+6\xd1y&\x1c\xdax\xb4n'+\xbfo[[\x98\xf1\xbd<\xbc\xe6\xfcʞ\x13\u009cP\xe8\xd1\xe1,\xd0㮃\x11\xb7\xe9>\xbc\xbe\xc4\xf9\xf9\xa6\x0f\xb9\xf7B[\x03{\n\"J\xae_\xc4\xdb\xd7B\xf9\xbd\x92\x1e\x10W}\xe2\x8e{ҳ#/\xf1\xde<\x8d\xc7L\xbe8\x81\xa7\xed<\x87|\xcbR\x1bA\xdd\xdbonV_\xd7\xf1\xc4\v\xffg$\xff\xce֩ϊ\xb7\xdc(\b\xeb\xc1X`)~>xM>\xc20\x0e\xe2\x8e\xfcB\x8e;\xfe\xb1\xe7\xedl\x95\xad\xb8S\xf2\xa0@\x0f\x05gM\xfeF\x99a\xe2\xf0A\xaa;^\x1d\x98h\xcc\xedE\x95\xef\xa82̊\xb2\xc3'\x86(\x13\x94\xb3\x7fĔS\xfb\xc7y@\xb5\xb5\x11\xf9-\x01\x8d\xb1\x1fށ\xb54G\xbd\xed\xa8\x1e,=]\xe7d\xc1W\x9bӁa\xedol\x87X\xb8\xc7\x03ې\x8f\xd2@H|a]\x98\xd6Z\x02mְ\xdfKe܆\xe8zM\xd8\xde{\xef\x11\xb8Vq`\x10ǽ\x8fG\x98i2\x9d\x9b\xe5\x06\x03\xa9\nWM\xbcл\xa0g\xb7\xddA\xb3\xac\xb2\x16\xd1+m(\x8f\x18$ߤ\xa81Lb\xe7\v俦\xec\x02m\xdb\xf5\x87a-\x04\xe7(\x87W\x018c,j\x9a\x12\f\x83\x83 O\x8a\x19c\r\xa0vf#1\xd6\xe4\xe1\x9chk\x04\\\x14\xdf\"N\xc1m\xc73-\xba\xf1Ӻ\xf2\x98~\xf4\x83\xc3\xe7\xe0vH\x82Ѹ\x9d\xcf(\xf1m-+\xb3#\x15\a+TJV\x87c\x90\xcb\x11Sv\x04n^\x01\xc6\x02QC\xe8\x90Uf*%Z;\xcf\xf5\xf1\xd6\x06]\x9a=\x8eb\xea3g\xc2\x1b\xad\xaf\xfc;\x0f뽒\xc5\xda\xf3\x02\x93\xc1n\xfcn\xb0b\xd2Z\x13\xe6\x18%9qO\xc0\xf9\v\xd5Q\f\xca\x12\x04\xa1\xda\xe3\x93p\x0f\xceū\x876T\x99\xd40\xc0}\xa7\xf2L\x04\x00!\xc7\xf1\xbd\xf7\xbb\xdd\xee>\xa0[\xff\x02b\r\xf8\x86h&\xc2\xf3\xb0n/݉\x82&R\xe0+rR\xc5\xf3\xfc\x06.}ǁ\xef\xa2\xff\xfb\xfa\xee\xa7zM|\x9f\xe2\x04~\xe9U\xef\x9d\xf9ķ\x00\xeb*\xdeq\x8b\xd0\xe3\x0fl\xefR\x0f3\x8b\xf5\x1f\xff\xcf\xcfr\x9e\x92\x9c\x8c\x97\x93\xfe\x05\xba\x0e\xb5\xa30\xf3\xf2\xdf\x1d\ak\xf9h\x80\xae\xeb\xf2r\x91\x8fz\xba,\xear͐Kx\xb9\xf8:\x81\x88\xd3e\xc1\x96g\x8b\xb4\\wtO\x14_K\x9d\x9bc\x7f\xf3\xd5\"\xa1\x16\x0f!\x12l\x89\f\xa3\x0e\xbf\xcc\x06[Z\xb1\x96\x80\xe3\xc8\xe3f\xbd\xf8˕\xa2-\xd1u`\xf0\x11\x15hޚ۾'\xff\xe5\x7f\x03\x00\x00\xff\xff\x1f\xff\x879X}\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xc4YKs\xe3\xb8\x11\xbe\xebWt\xed\x1e|YQ3\x9bKJ\x97\x94FN\xaa\xa6\xe2\x89]#ǹ\xe4\xb0\x10Д\xb0\x06\x01\x06\x0fi\x94T\xfe{\xaa\x01\xf0!\x92\xb2\xe4\xa9$\x8b\x8bM\xb2\xd1\xe8\xfe\xfa\r\xcd\xe7\xf3\x19\xab\xe5\vZ'\x8d^\x02\xab%~\xf3\xa8\xe9\xc9\x15\xaf\xbfw\x854\x8b\xc3\xc7٫\xd4b\t\xeb༩\xbe\xa23\xc1r\xbc\xc7Rj\xe9\xa5ѳ\n=\x13̳\xe5\f\x80im<\xa3\u05ce\x1e\x01\xb8\xd1\xde\x1a\xa5\xd0\xcew\xa8\x8bװ\xc5m\x90J\xa0\x8d̛\xa3\x0f\x1f\x8a\x8f?\x17\x1ff\x00\x9aU\xb8\x84-㯡v\xdeX\xb6CexbY\x1cP\xa15\x8543W#\xa7\x13vքz\t݇\xc4!\x9f\x9e$\xff\x14\x99m\x12\xb3\x87\xcc,~W\xd2\xf9?_\xa6y\x90\xceG\xbaZ\x05\xcb\xd4%\xb1\"\x89\xdb\x1b\xeb\xff\xd2\x1d=\x87\xadS\xe9\x8bԻ\xa0\x98\xbd\xb0}\x06ษq\tqw\xcd8\x8a\x19@\x86&r\x9b\x03\x13\"\x82\xcdԓ\x95ڣ]\x1b\x15*ݞ%\xd0q+k\x1f\xc1L\xba@V\x06\x1am\xc0y\xe6\x83\x03\x17\xf8\x1e\x98\x83ՁIŶ\n\x17\x7fլ\xf9?\xf2\x03\xf8\xd5\x19\xfd\xc4\xfc~\tE\xdaU\xd4{暯\xc9FO\xbd7\xfeD\n8o\xa5\xdeM\x89\xf4\xc0\x9c\x7faJ\x8a(ɳ\xac\x10\xa4\x03\xbfGP\xccy\xf0\xf4\x82\x9e\x12B@\x10!4\b\xc1\x91\xb9|\x0e\xc0!q\x89\x18MK\xaaFg\x9d\x89M\xa2\xc0ˀK\x92\x9f\xded\xe9{l\x1b\xff.\xb8Ŗ\xa5\xf3\xac\xaa\xcf\xf8\xaevx\x89\xd9\x19\x14\xf7X\xb2\xa0|_U\xb2\x92\xea\xfb\xe5\xb9Z5\xf2B\xa4]g'ޟ\xbdK\xa7n\x8dQ\xc8\x12\x97Du\xf8\x98\xbc\x90\xef\xb1b\xcbLljԫ\xa7\xcf/\xbfۜ\xbd\x86)G\x1a\x04\x05\x19\x8e\xf5l\xb3G\x8b\xf0\x12\xe3/\xd9\xcde\xd5Z\x9e\x00f\xfb+r\xdf\x19\xb1\xb6\xa6F\xebe\x13,i\xf5rQ\xef\xed@\xa6;\x12;Q\x81\xa0$\x84ɏr\xbc\xa0Ț\x82)\xc1\xef\xa5\x03\x8b\xb5E\x87\xda\xf7\xe1m\x05+\x81\xe9,^\x01\x1b\xb4Ćb9(A\xb9\xeb\x80փEnvZ\xfe\xb3\xe5\xed\xc0\x9b\xec\xbc\x1e\x9d\x1f\xf0\x8c\xf1\xa9\x99\"W\r\xf8\x130-\xa0b'\xb0H\xa7@\xd0=~\x91\xc4\x15\xf0\x85\xfc]\xea\xd2,a\xef}햋\xc5N\xfa&\asSUAK\x7fZ\xc4t*\xb7\xc1\x1b\xeb\x16\x02\x0f\xa8\x16N\xee\xe6\xcc\xf2\xbd\xf4\xc8}\xb0\xb8`\xb5\x9cG\xd1uJ\x9a\x95\xf8\xd1\xe6\xac\xed\xee\xced\x1dEmZ1k\xbea\x01ʘ\xc9\v\xd2֤E\a4\xbd\"t\xbe\xfeq\xf3\f\xcd\xd1\xd1\x18C\xf4#\xee\xddFי\x80\x00\x93\xbaD\x9b\x8cXZSE\x9e\xa8Em\xa4\xf6\xf1\x81+\x89z\b\xbf\v\xdbJz\xb2\xfb?\x02:O\xb6*`\x1d\v\x13l\x11B\x1d㾀\xcf\x1a֬B\xb5f\x0e\xff\xe7\x06 \xa4ݜ\x80\xbd\xcd\x04\xfd\x9a:$N\xa8\xf5>4\xb5\xf0\x82\xbd&\xa3xS#?\x8b\x1f\x81NZ\xf2p\xcf<Ƹ\x18\xe0\x9aC\xfcr1m\xd6tp\xd3b\x9c\xa3s_\x8c\xc0ᗁȫ\x96\xf0L\xc6\x1am%],\x8bP\x1a;\xac\x18\xac\xcd\xc0\xfd\xd5d\xaab\xf4\ru\xa8Ƃ\xcc\xe1+2\xf1\xa8\xd5\xe9§\xbfY\xe9\xc7\a]0$\xad$\xe2\xe6\xa4\xf9\x13Zi\xc4\x15\xe5?\r\xc8[\b\xf6\xe6\betk\xedՉr\x90;i>ζ\xcdZ=}n2o\n\xa0\x1co\x19\xab\x02V9rM\t\x1f@HG\r\x80\x8bL\xc7`\xe9\xa0b\x83\xb0\x04oû\xd4\xe7F\x97r7V\xba\xdf\xd3\\\xf2\x98+\xac\aȭ\xe3I\x94\x9a\xc8;jk\x0eR\xa0\x9dS|\xc8R\xf2,I\xb0\xa9r\x95\x12\x95pcM/DYTŢ\xa0\xa8f\xea\x8a\r\xd7-a쀙\xd4Ƀ;\x061\xd9\xd8*\x97T\xedQ\x8b\xb6\x1b9\x93\xc6Ĭ\xe5P\xc0Q\xfa}J\x87j*\xee\xe0\xcdأ\xf5\x8a\xa7\xa9\xd7\x03ٟ\xf7H\x94\xa9\x80\"8\xe4\x16}\xf46T\xe4>\xe4J\x05\xc0\x97\xe0bB\x1d\xe6\x89f\xc5F\xad\xd9\xfd\x8a\xa71\xd0p\u0378\xb9\x85\xb9.\xf2\x1d\xb5\u038d\xc0\x16K\xb4\xa8\xfddR\xa7\x01\xc4j\xf4\x18\xf3\xba0\xdcQJ\xe7X{\xb70\a\xb4\a\x89\xc7\xc5\xd1\xd8W\xa9ws\x02|\x9e#h\x11NJŏ\xf1\xcf\x05\x95\x9f\x1f\xef\x1f\x97\xb0\x12\x02\x8cߣ%\xab\x95A5\x8e\xd6\xebo~\x8a5\xf6'\bR\xfc\xe1\xee{p1u\x8a\x9c\x1b\xb0\xd9D\xef?Q\xa3\x16\x85\"\x886\xc9*\xc6\x02UJ2v\x95\xad\x99r͔#Nu\x98\xfdE\x89\x89*\xc8TF}\xc5q2}#\xcc\x00\xbe\xcd;C\xcd+V\xcf\x135\xf3\xa6\x92|6\xd46\xb6\xc1W\"\xb2i\xbb\xa5\x16\x92S\xdbv\x1eI\xcd8\"κ\xf3\t\x18\x86\xfd\xfa\xa5\xfc1\rSR7W\xcf+\x12?\xf6i\xbb!.%\xb3\\\x11\x1dzj\xb7\x1ch\xa4\x8a\xc9\xec\x18\xe7\x98B\xb8њb\xd7\x1b`mb\xbcsÊ\xf0\xce|\xb2\r\xfc\x15'\x80\x1f\xa9\xf2)\x126\x18\xa7m$Kp\x18S\xf551\xe0zDp\xb6F{\x8b,\xeb\x15\x11\xb6E\x95\xc1z\x05۠\x85\xc2F\xa2\xe3\x1e5\xcd\x13\xb2\x14?\xfcf3\x93b\xce\xd3\b\x84\xe2+\x1e\xe4\xf8Nh\x8c\xee\xc3hG\x13\xf8m8\xd0\xc3/\xcdh\xbd\xb0\x99\xec\x97\t0J\xa9\xa8s\x9c\xc8\x13]\xc70\xbe\xbd\xfc\xb4y\xb8s\xb1\xe1G\xed\xa7\x9a\xc4#Z\x8c\xf3\x15\n\xea\xf9M\xbe\xc5\bΣ\x9dp\x80\xd6z\xd1栌\xde\r\x02'\xad|\xa7A\xfd\\r(cA\xa0\xa7Ҥw\xc0\xf7Lﰻ\xb3\xca\xf2\xbf-)\xb9\xcf\xc0g:\x0f\x91\xfa\x92{\xdcd\xd1g9\xd5ԏ\xee\x8b;\xe2\xe9\xbb\xe2F\xfaƲ\x17\x87\xa2+\xb8\x8f\xe8\x9b*M\xa0\xce}w\x7fܭ\xef\x1f\x86Ǘ\xd37 \xf1ޛ\xf37nA\xe0\xc8\\w\x87\xfe\xdb\xe1PQ\xb7z\xb5\x05\xfe\x92\xa8\xd2ec\xde\x02lk\x82\x7f+2\xef\xa6\x1c:\xff8\xf0\x1e\x19\xe3O\x1eך\f\xa2i,\u0083\xa5\xc1\xb3\xbbC\x8bIa\xaa\xb6\xdc~\x19\xb5\x1a\xfc2\xd3\xff6\xfe\xdd\xe6\x06\xbd&k\xed\xe8e\xaa\x97=\xbbf\x90\xfbo¶\xbdW^¿\xfe=\xfbO\x00\x00\x00\xff\xff\x80.\x12\xd3P\x1c\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4\x96M\x93\xdb6\x0f\xc7\xef\xfe\x14\x98y\x0e\xb9<\x92\xb3\xed\xa5\xa3[\xb3\xc9a\xa7mƳ\x9bɝ&a\x8bY\x8ad\x01\xd0[\xb7\xd3\xef\xde!)\xf9E\xb67\xdbCy\x13\t\x02\x7f\xfe@\x80j\x9af\xa1\xa2\xfd\x8a\xc46\xf8\x0eT\xb4\xf8\x87\xa0\xcf_\xdc>\xffĭ\r\xcb\xdd\xdd\xe2\xd9z\xd3\xc1}b\t\xc3#rH\xa4\xf1#n\xac\xb7b\x83_\f(\xca(Q\xdd\x02@y\x1fD\xe5iΟ\x00:x\xa1\xe0\x1cR\xb3E\xdf>\xa75\xae\x93u\x06\xa98\x9fB\xef\u07b7w?\xb4\xef\x17\x00^\r\u0601A\x87\x82k\xa5\x9fS$\xfc=!\v\xb7;tH\xa1\xb5a\xc1\x11u\xf6\xbf\xa5\x90b\aDž\xba\x7f\x8c]u\x7f,\xae>\x14W\x8f\xd5UYu\x96\xe5\x97[\x16\xbf\xda\xd1*\xbaD\xca]\x17T\f\xd8\xfamr\x8a\xae\x9a,\x00X\x87\x88\x1d|β\xa2\xd2h\x16\x00㱋\xcc\x06\x941\x05\xa4r+\xb2^\x90\xee\x83K\xc3\x04\xb0\x01\x83\xac\xc9F)\xa0\xbe\xf4X\x8e\ba\x03\xd2#\xd4p \x01\xd68*0e\x1f\xc07\x0e~\xa5\xa4\xef\xa0ͼ\xdaj\x9a\x85\x8c\x06\x15\xf5\x87\xf9\xb4\xec\xb3`\x16\xb2~{K\x02\x8b\x92ē\x88\x12\xd7\x06\x0ft\xc2\xf7\\@\xb1oc\xaf\xf8<\xfaSY\xb8\x15\xb9\xda\xec\xee*i\xdd㠺\xd16D\xf4?\xaf\x1e\xbe\xfe\xf8t6\r\xe7Z\xaf\xa4\x16,\x83\x9a\x94fp\x95\x1a\x04\x8f\x10\b\x86@\x13Un\x0fN#\x85\x88$v\xbaZu\x9c\x14\xcf\xc9\xecL»\xac\xb2Z\x81\xc9U\x83\\\xa0\x8d\x97\x00\xcdx\xb0\n\xd32\x10FBF_\xeb\xe8\xcc1d#\xe5!\xac\xbf\xa1\x96\x16\x9e\x90\xb2\x1b\xe0>$gr\xb1\xed\x90\x04\bu\xd8z\xfb\xe7\xc17\xe7s\xe6\xa0N\xc91?\xd3(\x97\xce+\a;\xe5\x12\xfe\x1f\x9470\xa8=\x10\xe6(\x90\xfc\x89\xbfb\xc2-\xfc\x961Y\xbf\t\x1d\xf4\"\x91\xbb\xe5rkej\x1a:\fC\xf2V\xf6\xcbR\xffv\x9d$\x10/\r\xee\xd0-\xd9n\x1bE\xba\xb7\x82Z\x12\xe1RE\xdb\x14\xe9\xbe4\x8ev0\xff\xa3\xb1\xcd\xf0\xbb3\xad\x17\x17\xa4\x8eR\xe8\xafd \x97yM{\xddZOq\x04\x9d\xa72\x9d\xc7OO_`\n]\x921\xa7_\xb8\x1f7\xf21\x05\x19\x98\xf5\x1b\xa4\x9a\xc4\r\x85\xa1\xf8Dob\xb0^ʇv\x16\xfd\x1c?\xa7\xf5`\x85\xa7+\x99s\xd5\xc2}餹\xa8S4Jд\xf0\xe0\xe1^\r\xe8\xee\x15\xe3\x7f\x9e\x80L\x9a\x9b\f\xf6m)8}\x04\xe6ƕ\xda\xc9\xc2Ծo\xe4\xebJ\xd1>E\xd49\x83\x19b\xdem7V\x97\xf2\x80M x\xe9\xad\ue9e2\x9d\xd1=\x14x{\xb6p\xbd\xa0\xf38\xb6\xc9\xf9\xca\xcd\xc3Cɝ%\x9c\xdd\xc2\x06.z\xee\xeb\\J3\xfc\x97dj'\x1e\xd9\xe8D\x84^N\xfa\xb3\xba\xb6\xe9\xad,\x90(\xd0\xc5\xecLԧbT^ze=\x83\xf2\xfbq#H\xaf\x04^\x90r\x19\xe8\x90r\x9fA\x03&]\xf0\x1b\xb1\x9c\xbe%\x91\x82F\xe6\xf6\xc2\xce\n\x0eW4\xbd\x92\x9d<|rN\xad\x1dv \x94\xf0Ff\x15\x91\xda\xcf\xd6ʛ\xf5\x1d\x04\xabls-\a\x87w\xfa\xbbI(\xb8}\x1a.#5\xf0\x19_\xae\xcc>\xf8\x15\x85-!ϯ|^\\Uz\x87\x9f\x817P\xbaz)/&9\xf7;sB\x91%\x90ڞr\xe5\xb4>\xf4\xef\x0e\xfe\xfa{\xf1O\x00\x00\x00\xff\xff\x045\f\xc6i\n\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4VM\x93\xdb6\f\xbd\xfbW`\xa6\x87\xb43\x91\x9c\xb4\x97\x8eo\xad\x93\xc3N\xd24\xb3N\xf7NS\xb0\xc4.E\xb2\x04\xe8\xcd\xf6\xd7w@J\xfe\x94\xbd\xdeCu\x13\t\x82\x8f\x0f\x0f\x8f\xac\xaaj\xa6\x82y\xc0Hƻ\x05\xa8`\xf0;\xa3\x93?\xaa\x1f\x7f\xa5\xda\xf8\xf9\xf6\xfd\xecѸf\x01\xcbD\xec\xfb{$\x9f\xa2\xc6\x0f\xb81ΰ\xf1n\xd6#\xabF\xb1Z\xcc\x00\x94s\x9e\x95\f\x93\xfc\x02h\xef8zk1V-\xba\xfa1\xadq\x9d\x8cm0\xe6\xe4\xe3\xd6\xdbw\xf5\xfb\x9f\xebw3\x00\xa7z\\@㟜\xf5\xaa\x89\xf8OBb\xaa\xb7h1\xfa\xda\xf8\x19\x05Ԓ\xbb\x8d>\x85\x05\xec'\xca\xdaa߂\xf9Ð澤\xc93\xd6\x10\x7f\x9a\x9a\xfdl\x86\x88`ST\xf6\x1cD\x9e$\xe3\xdadU<\x9b\x9e\x01\x90\xf6\x01\x17\xf0E`\x04\xa5\xb1\x99\x01\fG̰\xaa\xe1t\xdb\xf7%\x95\xee\xb0W\x05/\x80\x0f\xe8~\xfbz\xf7\xf0\xcb\xeah\x18\xa0A\xd2\xd1\x04\xceD\x9d`\x06C\xa0`@\x00\xecw\xa0@9P\x91\xcdFi\x86M\xf4=\xac\x95~La\x97\x15\xc0\xaf\xffF\xcd@\xec\xa3j\xf1-P\xd2\x1d(\xc9WB\xc1\xfa\x166\xc6b\xbd[\x14\xa2\x0f\x18ٌ,\x97\xef@C\a\xa3'\xc0\xdf\xc8\xd9J\x144\"\x1e$\xe0\x0eG~\xb0\x19\xe8\x00\xbf\x01\xee\fA\xc4\x10\x91\xd0\x159\x1d%\x06\tRn8A\r+\x8c\x92\x06\xa8\xf3\xc96\xa2\xb9-F\x86\x88ڷ\xce\xfc\xbb\xcbM\u0090lj\x15\x8fr\xd8\x7f\xc61F\xa7,l\x95M\xf8\x16\x94k\xa0W\xcf\x101\xf3\x94\xdcA\xbe\x1cB5\xfc\xe1#\x82q\x1b\xbf\x80\x8e9\xd0b>o\r\x8f\xbd\xa3}\xdf'g\xf8y\x9e\xdb\xc0\xac\x13\xfbH\xf3\x06\xb7h\xe7d\xdaJE\xdd\x19F\xcd)\xe2\\\x05Se\xe8.\xf7O\xdd7?ġ\xdb\xe8\xcd\x11V~\x16\x99\x11G\xe3ڃ\x89\xac\xf9+\x15\x10\xd5\x17\xc1\x94\xa5\xe5\x14{\xa2eHع\xff\xb8\xfa\x06\xe3ֹ\x18\xa7\xec\x17\xe5\xec\x16Ҿ\x04B\x98q\x1b\x8c\xa5\x88Yy\x92\x13]\x13\xbcq\x9c\x7f\xb45\xe8N駴\xee\r\xd3(f\xa9U\r\xcbl(\xb0FH\xa1Q\x8cM\rw\x0e\x96\xaaG\xbbT\x84\xff{\x01\x84i\xaa\x84\xd8\xdbJp腧\xc1\x85\xb5\x83\x89\xd1\xc9.\xd4\xeb\xa4\xd5W\x01\xb5TO\b\x94\x95fctn\r\xd8\xf8\bj\xdf\xf9\x03\x81\xf5Q\xe6\xe9\xce\xcd\xe0Tl\x91OGO\xb0|\xcbA\xb2\xfdS\xa7\x8e\x8d\xe6G\xac\xdbZ\xbc\x82\x06 \xc5=~\xaa\xcf2^\xc6\x00\x93\xea\x9dD2\x8aXh\x10^\xc5\nĤ\x0e1\x9do-\x1f\xba\xd4OoP\xc1\xef\x19\xf3g\xdf^\x9d_z\xc7\"\xf7\xabA\x0fަ\x1eWN\x05\xea\xfc\v\xb1w\x8c\xfd\x9f\x01c\xb91\xaf\x86\x8e\x17\xef\ue5ba\x12\x98\xec\xc5}\xefQ\xfc\x1e/\x9ft\b\xb8)\xcb\r\x98\x86ț\x0e\xba\\ݽ\x86\xc2\v\xe1\xaf(ҝ\xdb\xf8\xe9\xb8\v\xed=~\xf9\x1a\x7fY\xab\xf2\x10\x18\xb5*K\xca݆\xf0)\xad1:d\xa4\xbd\xcd>\x19\xee&3\x02}S\xe6\x84H\xf9\x01\xa4\xd5\xe9\xd3K\xbe5B\x83\x16\x19\x1bX?\x97\x1b\xe9\x99\x18\xfbs\xdc\x1b\x1f{\xc5\v\x90˻b3!#\x97\xacUk\x8b\v\xe0\x98.\xa9l\xf2\xe0\xa1S4цGg\xfe*1S\xc2\xd85\xe3Ue\xc0\xc5{\xa3\x82/\xf841\xfa5z\x8dDx\xdeF\x17O2\xd9\x04g\x83$/\xac急\xe1\xe1>\x8c\xfc\x17\x00\x00\xff\xff\t\x15i;\xcd\r\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xc4YKs\x1b7\xf2\xbf\xebSt9\a\xfdSe\x0ec\xff\xb7\xb6\xb6x\xb3\xe5͖v\x13Yeʾ\xa4r\x00\a=3\x88f\x00\x04\xc0P\xe2\xa6\xf2ݷ\x1a\x0fr\x1e )\xa9\xd6\u07b9H\x04\x1a\x8d\x1f\x1a\xfd\xc6b\xb1\xb8`Z|Ac\x85\x92+`Z\xe0\xa3CI\xbflq\xff7[\b\xb5ܾ\xb9\xb8\x17\x92\xafષNu\x9fЪޔ\xf8\x01+!\x85\x13J^t\xe8\x18g\x8e\xad.\x00\x98\x94\xca1\x1a\xb6\xf4\x13\xa0T\xd2\x19նh\x165\xca\xe2\xbe\xdf\xe0\xa6\x17-G㙧\xad\xb7?\x14o\xde\x16?\\\x00H\xd6\xe1\n\xb4\xe2[\xd5\xf6\x1dnXy\xdfk[l\xb1E\xa3\n\xa1.\xacƒx\xd7F\xf5z\x05\x87\x89\xb06\xee\x1b0\xdf*\xfeųy\xef\xd9\xf8\x99VX\xf7\xaf\xdc\xecO\xc2:O\xa1\xdbްv\x0e\xc2OZ!\xeb\xbeef6}\x01`K\xa5q\x057\x04C\xb3\x12\xf9\x05@<\xa2\x87\xb5\x00ƹ\x17\x1ako\x8d\x90\x0e\xcd\x15qH\xc2Z\x00G[\x1a\xa1\x9d\x17ʭ\xe2\x10\x00B@\b\xd61\xd7[\xb0}\xd9\x00\xb3p\x83\x0f\xcbkykTm\xd0\x06x\x00\xbfY%o\x99kVP\x04\xf2B7\xccb\x9c\r\xe2]\xfb\x898\xe4v\x04\xda:#d\x9d\x83q':\x84\x87\x06%\xb8FX\b\xa7\x85\af\t\x8eq\xfe\x94\xf9\x8d\xfd<-\xb7\x8euz\x84\xe0\xca ;,\r\x108s\x98\x03\xb0\x97'\xa8\n\\\x83$y\xafXLH!k?\x14n\x02\x9c\x82\rz\x88ȡ\xd7\x19d\x1a\xcbB+^\xc8\xc4t\x04\xebf2zN6D\xff\xdfF5\x02t\xab\xf8\v\xa0\x98\x9d\xb8\x01\ng ,\xb0\xb84\x9c\xe2 \xe8\xe4\x8e>\xfd}}\aik\x7f\x19S\xe9{\xb9\x1f\x16\xda\xc3\x15\x90\xc0\x84\xacȬ\xe9\x12+\xa3:\xcf\x13%\xd7JH\xe7\x7f\x94\xad@9\x15\xbf\xed7\x9dpt\xef\xbf\xf7h\x1d\xddU\x01W>S \xf7\xd4k\xd2\\^\xc0\xb5\x84+\xd6a{\xc5,~\xf5\v I\xdb\x05\t\xf6iW0Lr\xa6\xc4Aj\x83\x89\x94\xa2\x1c\xb9\xafIޱ\xd6X\xd2\xed\x91\x00i\xa5\xa8D\xf4P\x952\xc0\xa6\xe4ňq\xdep\xe9\xcbz\xa7)\xd1\x04\xd9\xfbܚ\x84M\x0e|jr\x98\x81r\xc6\x14\xa0\x9dz\xd9\xfd\x1a\x83ZY\xe1\x94\xd9\x11\xe3\xe0`\x8b\x19\x87#\xd7@\x9fT\x1cϜ\xe3Fq\xcc\xc1\xa6\xa5\xe0\x1a\x16\xb4\x95\xf2+\xf2G\xbd\x94\xf3]\xe8S\xf2Y\xc0\xb4\xe2gp\xc5\x1d\x19\x18\xacР,19\xaeS\xc9C\x06\xd90\xac\xcf1\x1eW\n8\xe1ճ\x88\xdf\xdd^'O\x9e\x84\x18\xb1\xbb\xf9\xbeg\xe4C_%\xb0\xe5>Н\xdf\xfb\xf2\xba\n\x9by\x9f\xe6\x140\xd0\x02C\x1a\xb8\x0f\x12 \xa4u\xc88\xa8*ˑj\x12 \xc37\x18W\xbc\x0e\x1e,\xba\xcaCh!\xd9\x03#\xdf)8\xfcs\xfd\xf1f\xf9\x8f\x9c\xe8\xf7\xa7\x00V\x96h}\x16\xec\xb0C\xe9^\xef\x13s\x8eV\x18\xe4\x94fc\xd11)*\xb4\xae\x88{\xa0\xb1\xbf\xbc\xfd5/=\x80\x1f\x95\x01|d\x9dn\xf15\x88 \xf1\xbd[NJ#l\x10Ǟ#<\b\u05c8i0\xddK\x80\xd4+\x1e\xfb\xc1\x1fױ{\x04\x15\x8f\xdb#\xb4\xe2\x1eW\xf0ʧ5\a\x98\x7f\x90\xed\xfc\xf9\xea\b\xd7\xff\v\xa6\xfd\x8a\x88^\x05p\xfb8<4\xba\x03\xc8`yF\xd45\x1e\xb2\xaa\xe9\xe7\x83\n\xb9\xea\xefA\x19\x92\x80T\x03\x16\x9e1\xdd^p\x94\xc8g\xa0\x7fy\xfb\xebQ\xc4cy\x81\x90\x1c\x1f\xe1-\x88X\xdahſ/\xe0\xcek\xc7N:\xf6H;\x95\x8d\xb2xL\xb2J\xb6\xbb\x90\xe7n\x11\xac\xa2B\t\xdbv\x11\xf2 \x0e\x0flGRH\x17G\xfa\xc6@3\xe3Njk\xca~\xee>~\xf8\xb8\n\xc8H\xa1j\xef\x89)jV\x82\xb2\x19JcB,\xf6\xda8\v\xe6\xe9\xb3}P\x1f\xa7\xa0l\x98\xac1\x9c\x17\xa1\xea):\x16\x97/\xb1\xe3yJ\x92\xbeLj2u\x1c\xff\xb3\xe0\xfe\xc4\xc3\xf9\f\xfa\t\x87\x1bV\x19'\x0fw\xdfo\xd0Ht\xe8\xcf\xc7Ui\xe9h%jg\x97j\x8bf+\xf0a\xf9\xa0̽\x90\xf5\x82Ts\x11t\xc0.}\x99\xba\xfc\xce\xffy\xf1Y|E\xfb\xd4\x03\x8d*\xed\xafy*\xda\xc7._t\xa8\x94\xc3>=\x8e]\xaecf5]Kf\xf1Ј\xb2I\xc5I\xf4\xb1G\x8cIP&̃kfr\xf7\xd5U\x99\x04\xda\x1bB\xb4[\xc4^ڂIN\xff[a\x1d\x8d\xbfH\x82\xbdx\x92\xf9~\xbe\xfe\xf0m\x14\xbc\x17/\xb2\xd5#\tx\xf8\x1e\x17\aX\x8b\x8e\xe9E\xa0fNu\xa2\x9cPSVz\xcdI\xf0\x95@s&\x8d\xfb4\"N\x89f&\xbf\xdd\xd3<+\x8ft\xac\xce$n\xc3\xd6\xe1\xa9\xf4\ue93cƍ\x1bV[`\x06\x81A\xc74\xdd\xf3=\xee\x16!!\xd0LP4\xa7\x80\xbd\xef\x8a\x00Ӻ\x15\xd9\xc0\x1d\xc3~LY\xa3$\xa8,g\xb5=v\xf6쭥.\xd0\x1a\x1d\x95\xad\xdfD\x0e\x9f'{>Y&\xf9\xc4z$\x8a\xaa\x90\xbb\xaa\xbdoy\x96\xa9\x8e\x9f\xb4\xcfA\x1d\x11\x9f\x89B\xf11=\x17\x83֨\x99!K\xf7\xaf.W\xd3g\xc1\xd7`\x85o%Sf\x1aR\xd5\xd0\xe8\xb1\x14\x9c(\xb5R\x063.\x13\xe6ae\x14D\xc6\xf0\xbfe\xfc\xc8\xea\xc9l\xd0#\xe7\x03\xde\xf19b8\xd2o\xf6Om+\xf8\xe3ϋ\xff\x04\x00\x00\xff\xff{ŋW\xf4\"\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xc4YKs#\xb7\x11\xbe\xf3Wt\xad\x0f\x8a\xabv\x86\xdeM*\x95\xe2mW\x8aSJl\xadj\xa9\u074b\xcb\apМ\x819\x03\xc0x\x90b\\\xfe\xef\xa9\x06\x06\xe4\xbcHJ\xaaȞˮ\x80F\xe3Ç~\xa1\x99eٌi\xf1\x15\x8d\x15J.\x80i\x81\x8f\x0e%\xfde\xf3\xcd?l.\xd4|\xfbn\xb6\x11\x92/\xe0\xda[\xa7\x9a\xcfh\x957\x05\xde\xe0ZHᄒ\xb3\x06\x1d\xe3̱\xc5\f\x80I\xa9\x1c\xa3aK\x7f\x02\x14J:\xa3\xea\x1aMV\xa2\xcc7~\x85+/j\x8e&(O[o\xbf\xcb߽Ͽ\x9b\x01H\xd6\xe0\x02\xb4\xe2[U\xfb\x06\rZ\xa7\f\xda|\x8b5\x1a\x95\v5\xb3\x1a\vR^\x1a\xe5\xf5\x02\x8e\x13qq\xbbq\x04}\xaf\xf8נ\xe7s\xd4\x13\xa6ja\xdd\x7f&\xa7\x7f\x10\xd6\x05\x11]{\xc3\xea\t\x1ca\xd6\nY\xfa\x9a\x99\xf1\xfc\f\xc0\x16J\xe3\x02\xee\b\x8af\x05\xf2\x19@{\xce\x00-\x03\xc6y`\x8e\xd5\xf7FH\x87\xe6\x9aT$\xc62\xe0h\v#\xb4\v\xcc\x1c\xf4\x80Z\x83\xab\x90\xb6\f\xac2!\x85,\xc3P\x84\x00N\xc1\n\xa1E\u00832\x80_\xac\x92\xf7\xccU\vȉ\xb8\\+\x9eˤ\xb3\x95\x89\x9c\xdf\rFݞ\xcea\x9d\x11\xb2<\x85\xec\xff\f\xaa\x87\xe7^\xf1'\"y\xa80\xc8$4^\u05caq4\xb4y\xc5$\xaf\x11\xc8@\xc1\x19&\xed\x1a\xcd\t\x14i\xd9\xc3^\xf7\x91|I\xfa:3\xcfa\xe79TD\xd9\xde\xf6_\xbbC\x97\xf6\xbdW\xbc]\x00\xadQ\x83u\xccy\v\xd6\x17\x150\vw\xb8\x9b\xdf\xca{\xa3J\x83\xd6N\xc0\b\u2e6e\x98\xed\xe3X\x86\x89\xd7űV\xa6an\x01B\xba\xbf\xff\xed4\xb6vQ\xee\x94c\xf5ǽC\xdbC\xfa0\x1c\x8eh\xc9\xd9\xca\xf6\xfa\xff\x14\xb8+\x82t\xa3d\x9f\u05cf\x83\xd1)\xb0\x1d\xa5)\xde\xe6\x85\xc1\x10j\x1fD\x83ֱF\xf7\xb4~(\xfb\xfa8sq No\xdf\xc5PVTذE+\xa94\xca\x0f\xf7\xb7_\xff\xba\xec\r\x03h\xa34\x1a'Rt\x8d_'ytF\xa1\xcf\xec\x15)\x8cR\xc0)k\xa0\x8dN\x11ǐ\xb7\x18\xa2\xb3\b\v\x06\xb5A\x8b2摞b !&A\xad~\xc1\xc2\xe5\xb0DCj\xc0V\xca\xd7!\x02m\xd180X\xa8R\x8a\xff\x1et[\xf2=ڴf\x0e\xdb\x10\x7f\xfcB\f\x96\xac\x86-\xab=\xbe\x05&94l\x0f\x06i\x17\xf0\xb2\xa3/\x88\xd8\x1c~$\v\x11r\xad\x16P9\xa7\xedb>/\x85KI\xb3PM\xe3\xa5p\xfby\xc8\x7fb\xe5\x9d2v\xceq\x8b\xf5܊2c\xa6\xa8\x84\xc3\xc2y\x83s\xa6E\x16\xa0ː8\xf3\x86\x7fc\xda4k\xafzXGN\x17\xbf\x90\xeb\xce\xdc\x00%;\x10\x16X\xbb4\x9e\xe2Ht\nٟ\xff\xb9|\x80\xb4u\xb8\x8c!\xfb\x81\xf7\xe3B{\xbc\x02\"L\xc85\x05]\xbaĵQMЉ\x92k%\xa4\v\x7f\x14\xb5@9\xa4\xdf\xfaU#\x1c\xdd\xfb\xaf\x1e\xad\xa3\xbb\xca\xe1:T\x12\x14/\xbd&\xcb\xe59\xdcJ\xb8f\r\xd6\xd7\xcc\xe2\xab_\x001m3\"\xf6iW\xd0-\x82\x86\u0091\xb5\xceD\xaa`N\xdcװ*Yj,\xe8\xfa\x88AZ*֢\b\xbeA\xe1\a\xd8H>艹v]\xfaV\xac\xd8x\xbdtʰ\x12\x7fPQ\xe7Ph\x80\xed\xe3Ԛ\x04Nvr^T\x0e6J\x8e\x94\x02\xd4i\xf1\xaeB\x83\xdd5\x06\xb5\xb2\xc2)\xb3'\xc51[\xe6#\r'.\"\x1cY\xf1\vǠp\x1f\x1c\xc2\xe0\x1a\r\xca\x02S\x848W\xc9L\x9c\xa2\x93\xd0\xc7\x10OS\x0fg\xa2\xe7$\xe0\x0f\xf7\xb7)b&\x86[\xe8n\xbc\xef\x05z\xe8[\v\xacyH(\x97\xf7\xbe\xba]\xc7\xcdB\xecp\n\x18h\x81\xb1\"=\x04c\x10\xd2:d\x1c\xd4zR#\xbd\r\x80\x1c\xcc`\xbb\xe2m\x8c\x14mH:\x86p\xa2\x1e\x18\xc5(\xc1\xe1\xdf\xcbOw\xf3\x7fM1\x7f8\x05\xb0\xa2@kC\xbe\xc6\x06\xa5{{\xc8\xd9\x1c\xad0ȩp\xc1\xbcaR\xacѺ\xbc\xdd\x03\x8d\xfd\xe9\xfd\xcf\xd3\xec\x01|\xaf\f\xe0#kt\x8doAD\xc6\x0f\xe1/ٌ\xb0\x91\x8e\x83F\xd8\tW\x89a\xd2:0@\xd6\xd5\x1e{\x17\x8e\xeb\xd8\x06A\xb5\xc7\xf5\b\xb5\xd8\xe0\x02ބJ\xf0\b\xf37r\xac\xdfߜ\xd0\xfa\x97\xe8@oH\xe8M\x04w\xc8w]\x8f<\x82t\x15s\xe0\x8c(K<\x16\xa2\xc3/\x04o\n\x89߂2ĀT\x1d\x15A1\xdd^\x8cG\xc8G\xa0\x7fz\xff\xf3I\xc4}\xbe@H\x8e\x8f\xf0\x1e\x84\x8c\xdchſ\xcd\xe1!X\xc7^:\xf6H;\x15\x95\xb2x\x8aY%\xeb}\xac\xf6\xb7\bV5\b;\xac\xeb,\xd6\x1b\x1cvlO,\xa4\x8b#{c\xa0\x99qg\xad5U\x19\x0f\x9fn>-\"22\xa82\xc4;\xcaNkAU\x03\x95\v1\xe7\x05k\x1c%\xcd\xf4Y\x1f\xcd\xc7)(*&K\x8c\xe7EX{\xcaB\xf9\xd5K\xfcx\x9c\xfa\xd37Q\x02\f\x03ǟ\x96D\x9fx\xb8P\xa9>\xe1pݷ\xd6\xd9\xc3m\xfc\n\x8dD\x87\xe1|\\\x15\x96\x8eV\xa0vv\xae\xb6h\xb6\x02w\xf3\x9d2\x1b!ˌL3\x8b6`\xe7\xe1\xc9<\xff&\xfc\xf3Ⳅ\xd7\xf5S\x0f\xd4{\xf4\xbf\xe6\xa9h\x1f;\x7fѡR\xad\xf8\xf4\x8bE\x96f\x82*$*\x82b\xd59]\xd73\xadk*l\xdaR\xea\xd0T,\x94\\\x8bқ\xf0z\x19\x93\"}]\xb3U\x8d\vpƏ\xe9<\xe3)\xdd\xfe\xe3\x85\xcb\xfd\xd2\x11M7{\xa1\x03ꪩ\xbb\xed\xf5ELJA\xe9\x9b1\x94\f6J\v61Nv=\xf2i\x9ax3\xae\x05\xcf\\lt\x9a\v\x1c\xb4\xed\xba\x89\xb7g\xebs\xb1\x16\x0f#\xf4\xde\v\x9e7\x9dĞ\xeb\x8b\x06\x7f\xf5\xf4\xb0\xe8#̦_\xd4\x03\x19\xad\xf8lHZ7\x8c\r&\x8fAh8\xd1\xf7\xef\xc1l\xaf\x8d\xdc=\u0378\x19\x11z\x94\xcfiGľh\xcb{̊.uK\xe9I\xf8\xe2\x86D\xa1\xe8\xb9\xd5ki^\xb0\x81\xeb\xf1\x8a\xd0\xfd3\xbc\xf5\t\xd1`x\xe5ǖ\xee\x8eٴ\xc9\xd4}CG_\\\x1a*\x11R\x87<<\x86譶f\xa2F\x0e\x87_\xa6\xc2\xcf\x0f6\xb4\xc1\xae\xa6j\xff\xa4\xc8[\xe4!\xd6N\x80\x1e\xafK\x9de\xce\x1cf\xa4\xe2e\x81fҽ\x1a\xb4\x96\x95\x97\xfc\xeb\xc7(\x15\xfb$\xed\x12`+\xe5ݡQ\xd2:ZKŕm\xad\xe0y͚\x8a\xd9KP\xeeIf\xca\xe2\x0e.\x7f\xde\xe4\xe0L(\xbb\xc3\xdd\xc4\xe8\xa8\xd3ߝ\xbcN&41\xf7}\xb0\x8eg\x11\xd0nt\x89\x83V\f*U'\xebV\x8e\x12\xb9oVh\x88\x88\xf0\xf3Bb$\x05\x8e\xa9\xceSx\xb1\x1e\x99\x93\vx\xdc\x10J\x9e\xe0\xe4,\x16*\x88%\x0e5h\xef&\xbd\xa7s\xe4`\xe4\x05\x99\xec\tN\bƇRfG粂kO\x900\xf7S\xad\x87@\xbb&\xef\xe0:L\xda\x0f\x88\bt\xbc\xf3\x91G0,\x16d\xd1\xfc\xe6H\xbe \t-\xe0\xfe\x82mF\xb2u\u0087H\xd8wڑȞ\x82\x03\xab37\x8a\xd1C\rxZB`\xec;嬌\x139\xbe߈qk\xb8߾H\xb3\x117\xce#\xd3\xc8%?I\xd0_\xa4\xc1/WA\xa7[\xf8\x05\xc8t\x03\xf1x\t'\xb6-\x1e\xba\x11\xb6\f\xe6vm\xe3\x02)\x910֝\xc6\xce\xc04\xb1\xf4=R~\x1e\bOlNZ\xd9\x02\xdc)r\xb9;\xb3Xn\xc8\xf3Aj\xa7Sw\fx*d\xd3oL\x93\xb7Opz{s&\a\xden\xc4[\xa7\xe0\x17\x8b\x9bh-H\xc1O\xe4-\x8e}\xfb\x12#(\x93\x133\xbb\xfdX=Őܪ\xa2\xf5\xcas\xaf\x91\x15+Fljdx\xbcm=v\xea\x86\xc8\xdbظ7\x8f\xa7v\x9bſ\xb5\xd4\xe6\xe7t\xa0od=\xf7aDߦM\xc4\xcbfm}\x1f\xfb\x8a\xc2\xd8Z\x80;\x03\xca\a\xff\x9c\x80\x0e\x9e\xc3\v}\xaa\xb9\xe0^\f\xec\xd1\x18\x90\xb5\b\x9e\xe1&\x97*\xc9Y\xe2\x12k\xd3\xe2e\xa1\x9d\xfe\xe9G'6iO\xb6\xfd\xbb\xbb\x91\u05f6\x86\vYUt\x98\x1c\xccZ\xeaG72\xf0\xb4\a䨯\xf6\r\x9e\xe7|31\xf0\x10\xa6\x05\x9f\x9990Ah\x10\x1b\xa0\x82\xb2Qp\xa0G lG\x98! \nK\x17PNd\xe3\x14\x1e\x19\x88\x9al\xb6\xcc\x13\xf0\xb6\x81h\xaa<\x04\xac\xf0d31\x19L\xebv\xffL\x19\xbf\x06\xd9,\xe7}\x96\xea\x01hyI\x00\xe6\xd7\xcep\x02B7\n\x13\xf7N\xbc<3\x9e\xb7fK9\xc2i#\x8a\x03\xa0\x9c\x12=\xf1A\x1cx&\xb4\x01\x9a\xcb\v\xd6jj\x84`b\x9fG\xbb\xec\x10g\xdb\x1c\xaa\xb7Rr\xa0Â\xa7T\xb3\xb8\xbe\\\f\xfdڎ\xfe]\xc4P\xa4@\xbe\xb9\xb0\x05O*/\x8b\xa81P\xd5\xee\xbcI\xa2\x1a\xd1\xd5>W\x90BK|p\xbf\x8a\xd7t\xae\x99`\x19\x84\x1d\xe4\\\x98\xe9Z\x96\x16\xc4U-K;A4*.\t\x9fmz\x00\xec\xe9\fN\n\xae=r\xcd\x02+s\v\x84\x96%\x94.0iM\x15ﳸ\xf2\xb2\x91R\x85\xe4\ue59b\x89Y\x94\r\xad\xe7\x91b(V\x1daՈ'!\x9f\xc5\n=y\xbdX\x80\xe4\x87\x06_uzs\xb1$\xfa=\xa5P\x9f_\xf3y*\x18OW\x902\xd9|\xb3(\x1a2\xc5\x05sr͕.\x8f\xfc8\xbb\x8a\xa9\xf9'\x06\xfbD\xf3GWs\x9c[϶I\x8f\xea\x18\x7f\xcf\a0\aP\xa1\x98y\x85u\xdb)9\xdd\xe6\xa3[?&\x16\xb8Y\xfe\t\xa6\xb0+\xbc\x1c\x94\xbc\xa5\x1d\x1dk\x05\xdcXƦ\r7\xae\xfcX5\t&\xca*\xfcJ[\x069\x95\x13s\xf5\x12\xfd\x1a\xc0X\xaf\x10\x8a\x00e\x98$\xb1CGKW\xe9\xdbM\xc6\xf7\v\x1f0\xe4\x17V\xfa7/\x0f̨i\x98\xa9d\x98.\x9a\x9c\xc2\xd79\xdbt1\xd6\xf2\xa0\xef\xe7\xabi\xffX\xe83P}\xad\xfd9\x185@\xfb\x18L\f\x19\x94\x83\xa0\xe4\xb6.;\x16\x16P\x96\x12.\xf6\x14\xfap\xa0\x85xW\xe0I\x94\x01\xb0\xc6P\xb3?m\xbe\xba\x9di\xf2\x81\x1cd\x93(\xa9\x9b\xc0\xceL\x81\xc5xY\x85O\"\x80\xa1\xc7\x0f\xeb\xfe/F\xfa\"\v\x8c|%v\x87\x8eJ\x1bMe\xa2dGV6\x94\xf7\x0eY\x87-Z\xee!R\x11\xc1x*\xbfj\xd9*\x8c\xef\xb1\x11\xf9Z\xbb<\xcbbq4m\"\xe6\xd5b\\\\\x81ѯ\xb0\x18QRKS\x0e\xf9\xa5\xa6\xf95\x16\xd3E\x11K*+\x86u\x13\xa3@\xe7\xeb)r\xac\xfb\x99ډ\v*&2\xab\xe5^\x9c ɩ\x89\xb8\xa8\x12b\xb6\xa0,\xb3\xfe\xa1_\xd90\rrA\xd5C\x16r\xe6+\x1c\x16\xd75\xf8:\x82\xc9}dW3$\xea\x14&\x01\x8f\xd60LU'L\xa3\xd42/*\x1bM9\xb5\xe2W\xf1\xa5\xae\xe8M]ß\xbạ\x9a\x019\xa8\x01ϩ\xee\xceJ\xe3e\xe7lr\xb2l\xf3\x99\xe3\xe9\xaa\xed\x8cj\xed\x8cl\xd0\xdcJ3\xaa\xb2\x97Ucg\xe0\xf0J\xbe֕\xbc\xadk\xf8[\xd7\xf5\xb8f}\xaeYΙ\xf9yY\x15\xf5\xc5I\x86\x90\x8e\xfe\"K\xb8\x97\xca\xcc9\b\xf7\xc3\xfe\x89\x14`\xc7i\x92\xbc$\"tMe\x1a\xac\xed\xef\xed\xfe\xcb6\x95\xce\xd6\x05\xf3\xf7\x17Yڵ\xcd\xe5\x16\x1e\x06\xddϮ\xd0\xee@\x81p\x0fK\xfc\xd7\xe3\xd7/\x11~\xca\x1e\xf5F\xef\xe0M\x03g`\x94\x1e9>\xfb\xe4\vn\x1c\xb6P\x87\xbfr\x92\x80\xd6\xec?\xf1ͮ\xf9\x80\xcc\xdd\xfd\x06\xbb\x06k\t\xdf\xfa\x8a\t\xfd\x98{ۂ\xd5\x1e\x11#\xa3ܿ\xd9\xf5 &\xcaN\xe3\x9f\x04_L\nڋ\x8d\xd5d\xb9\"${\xea\xee7nuk\xf2ٚn\xe2D\xa4c\xbc\x03S媦ʜ\x90;\xf4M\\\xc3xP&萩\xd0ɨ\xa8=\x7f\v*\x89\xdb\xf0$\x14&\xe0Nu?\x9b9\xc4\xe8%\xeb\x18\xbf=1{o\xe2\x15\xd71\xae\x8eW\x88\xa9\xc4\xe7d\x05ī\x85\xa4\xbc\x18\xba\xff>'\xd6\x1eb\xc7iyf=\xd9\x10\xd6I\xe0ǎG\x91\xa6\x05\xad\xf5!\x11\x15z\x99LÇ\xaa\f5M\xe6~\\\xdfޖXq\xe8\b\xa0g\b\"Ju\x9e\xed\x1b\xacɞU\a\b\xd50\x9aЂ\xf1\x9b\x8ec\xfe\xfb\xa4<3\x9f\x05\xb9\xf8A\x10\x87\x9e\x11Q\x81\x91&+\xc6\x02/\xb4x\xb9 \xd99k\xc2e\x14\xb6N\u06dd\x99/J\\\xfc\x96\xc4<\xb2\x12\x88\x1a{F\"穈\xbf)>'D\x92.\x0eP6\x1c2\x1ex{\xect\x9d\x7f\xe2-\x00N\x9dI\xd9\x7f\xe4\xcdⵣ^\xad\xc1\xdb\x7fL\xce#\xddC\x1e)\xf1\xee\x82t\x96\xbd{u\xaa\xb0v\xbcn\x8a\x02\xb4\xde5\xf0{\x86\x88\xcfݾ>\xdc\xe9p\xe0^\x93\xa1\xae\xbe\x0f߃5,:\x98#\x8a\xc4μH<\x1f\xa4|ʲB\x7f\x8e\x1d\xdbh\v\x13\x8e\x8f\xf0j\xdbV6\xa6k_ʱ\xd8,\xbe\xbb\xf8\xca\xf2\x1aa\u07b9\xbb.c!\xc4\xf3\x87\x1d\xe3\x80\xe8\xcfIC9\x11M\xb5\x05\x85\f\x19;\x1c&\xee{?\x86\xd7e9?\xdd\f!\x0f\x9e\x9bnaOAD\xd2{\x19й\x03\x1cb_\x03 \xae\xfb\xc4ӓ\x04\xdf\n\x0e*\x7f챻)\x8e\x8e8\xfe\x8c\x8b\xcaE\xb0\xeb=\x86]\xb7x\xe7ƀH{a$\xe4\x9eñ\xb8`\xe9\x13\xf6^}\xa0z\xceл\xb7}\xe2\xe5ߎN\x8a6\xde\xc3ȑL_F\\\x91/\xf0\x9c\xf8ꐅ\x19\x9c\xb4&Y\x91\x8d\xb8Wr\xaf@\x9f3\xce\no\xa71\xb1\xff,\xd5=o\xf6L\xc4\xe2\xe1e\x9d\xef\xa92̲\xb2[Ob\xecǠ\xcb\x12\xbf͏\x1e\xf9aBF\xd5~ϳ\xe1L\xd7mN>y\x01\xfaN\xb7\xfa\"\x15\xfa\xf3\xd0\xd6\xe4\x8b4\x10\xb2\x8c\xac\x0f\x94i\xb2\x05mV\xb0\xdbIe\\\xf4y\xb5\"l\xe7\xed\x94T\x94\x942\x8e^\x92{\x9dۺN\xb1..\xda\xec>\x12\xa6P)\xa0{Uѓ\v\xa8Ѣ\xb0&0\xbc׆\xa6L\xf1\x17\x89Qt\xcb<7\xe7\x1c\xf2M\xb7\x7f\fF\xc5\x03\x8e\xe0\x1c\xea\xf0V\xb0\xd3\xc0\xc9\n\v\x827P;\x8f\x12\x10m\xb5\xd9%ǝ8A\xb3\x19w1\xfbW\xa9b\xe719\xe5\xb7\xd1{\x87x\xbc\x8a\x9a\xe90\xd4Ҭ8P\xb1\xb7\xec\xa3d\xb3?\x04\x16\x1c3TF\x80\x96\rF\xabk<\xa9:d\xebM\xa3D'\xd9\xe4\xf3\xf7e\xbb\xdc)\xa0\x17KL\xd5\xde%heƄ\xe6M\x19\xfb#\x83G\xf0\x9f\n\x85\xc7!T\x9fD1}\xc1\xc1y\x8al\xe2\x1e\xe3\x142\x92\xfb\x8d\x12\xf0\x92\xfd\xc6\xc1\xf9\xfb\xed*\xef֕X\xb2\xf9\xf1\b\xc1+\xa0c\xcc(\x98\xc7Ŵ\x81\x80\xfbK\xac|\x11\xb9\xbb\x06F0%R\xf1Hk\\,Å\xee9Ys\xe1\xa2^\xe7\x979\x938\xb1u%\xff\xb8N\xe01\x9a1\x9fr\xdc\xc1\xef\x83\xee\x83\xdbb\xd61l!z\x17.\x81\x9c\x7ff\xbb\xf0\x0f]\xb6\x1c\xfe\xe5\xac\xc7\xef|\xeb\xeb\x99*\xc1\xc4~n\xf3\xbf\xfan\to\xd8CH\xf8ÉMD\x0fy\x91?\x1c\x169\xf2?\v\xa2\x8f\xfc\x02\x8f8\xa9N\xce>\"#\x97\x1d$\xfb\x99\xfc\x97\xff\x0f\x00\x00\xff\xff\xf4\xd4_Pfi\x00\x00"), - []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xec=Mw\x1c)\x92w\xfd\n\x9e\xf6\xe0\x99}\xaar{\xf7\xb2O7\xaf\xecޭ7ݶ\x9e\xa5\xf1i\x0fCeF\xa9heB\x0e\x90%\xd7Λ\xff\xbe/\x02ȯJ2ɲ\xb4\xdd=#.\xb6\xb2 \b\"\x82\xf8\x80\x00V\xab\xd5\x05\xaf\xc4W\xd0F(y\xcdx%\xe0\x9b\x05\x89\x7f\x99\xf5\xe3\x7f\x98\xb5Po\x0f\xef.\x1e\x85̯\xd9Mm\xac*\xbf\x80Q\xb5\xce\xe0\x03\xec\x84\x14V(yQ\x82\xe59\xb7\xfc\xfa\x821.\xa5\xb2\x1c?\x1b\xfc\x93\xb1LI\xabUQ\x80^=\x80\\?\xd6[\xd8֢\xc8A\x13\xf0\xd0\xf5\xe1\x87\xf5\xbb\x7f[\xffp\xc1\x98\xe4%\\3\x93\xed!\xaf\v0\xeb\x03\x14\xa0\xd5Z\xa8\vSA\x86@\x1f\xb4\xaa\xabk\xd6\xfe\xe0\x1a\xf9\x0e\x1d\xb2w\xbe=}*\x84\xb1\x7f\xea}\xfeI\x18K?UE\xady\xd1鏾\x1a!\x1f\xea\x82\xeb\xf6\xfb\x05c&S\x15\\\xb3O\xd8U\xc53\xc8/\x18\xf3\xf8S\xd7+\xc6\xf3\x9c(\u008b[-\xa4\x05}\xa3\x8a\xba\f\x94X\xb1\x1cL\xa6Eei\xc4w\x96\xdb\xda0\xb5cv\x0f\xdd~\xb0\xfcb\x94\xbc\xe5v\x7f\xcdֆ꭫=7\xe1WG\"\a\xc0\x7f\xb2G\xc4\xcdX-\xe4\xc3Xo\xefٍV\x92\xc1\xb7J\x83A\x94YN\f\x94\x0f\xeci\x0f\x92Y\xc5t-\t\x95\xff\xe4\xd9c]\x8d RA\xb6\x1e\xe0\xe91\xe9\x7f\x9c\xc3\xe5~\x0f\xac\xe0\xc62+J`\xdcwȞ\xb8!\x1cvJ3\xbb\x17f\x9e&\b\xa4\x87\xadC\xe7\xa7\xe1g\x87P\xce-xt:\xa0\x82\xf0\xae3\r$\xb7\xf7\xa2\x04cyه\xf9\xfe\x01\x12\x80\x11\x89*^\x1b\x12\x8e\xb6\xf5m\xf7\x93\x03\xb0U\xaa\x00./\xdaJ\x87wN\xf6\xb2=\x94\xfc\xdaWV\x15\xc8\xf7\xb7\x9b\xaf\xff~\xd7\xfb\xcc\x06\xb2\xe4)ńa\x9c}\xa5\x89\xc1\xb4\x9f\xa9\xcc\xee\xb9e\x1a\x90\xf3 -֨4\xac\x02u\xf3\x06$cJ\xb3\n\xb4P\xb9\xc8\x02W\xa8\xb1٫\xba\xc8\xd9\x16\x90A\xeb\xa6A\xa5U\x05ڊ0\xf5\\\xe9h\x94\xce\xd7\x01\xc6opP\xae\x96\x93D0$|~BA\xee\xe9\xe0\xe6\x870-\xfeĤ\x1e`\x86\x95\xb8dj\xfb\vdv\xcd\xee@#\x98\x80u\xa6\xe4\x014R S\x0fR\xfco\x03۠\xd4[\x12F\v^\x1f\xb4\x85&\xb0\xe4\x05;\xf0\xa2\x86+\xc6e\xceJ~d\x1a\xb0\x17V\xcb\x0e<\xaab\xd6\xecg\xa5\x81\t\xb9S\xd7lome\xae߾}\x106h\xd2L\x95e-\x85=\xbe%\xa5(\xb6\xb5Uڼ\xcd\xe1\x00\xc5[#\x1eV\\g{a!\xb3\xb5\x86\xb7\xbc\x12+B]\x926]\x97\xf9\xbf\x04\x8e\x9a7=\\O\xe6\x9b+\xa4\b'8\x80\x1a\xd1\t\x8ck\xeaF\xd1\x12\x1a?!u\xbe|\xbc\xbb\xef\n\x930C\xea\x13\xdd;\x12ֲ\x00\t&\xe4\x0e\xfc\x8c\xdeiU\x12L\x90y\xa5\x84\xb4\xf4GV\b\x90C\xf2\x9bz[\n\x8b|\xffk\r\xc6\"\xaf\xd6\xec\x86\xcc\v\xcaa]\xe1\f\xcc\xd7l#\xd9\r/\xa1\xb8\xe1\x06^\x9c\x01Hi\xb3B¦\xb1\xa0k\x19\x87\x95\x1d\xd5:?\x04\xf3\x16\xe1W\x98\xe3w\x15d\xbd)\x83\xed\xc4Nd41H{6*`\xa0A]\x19\x9f\xb5\xf4\v\xa9\xa9\xe1\xd7\x01\x1eN\x97\x85^\xc1\xa0\xfd\xb0{\xe2pk\xc6P\xae\x1c4\xd4)R\r\xb9;\xa6\x05;\x94\xf0Pf0\xe9k\xbdT\xfbv\x02\x93yU\xb7\x8e\xe0x\xc2UB\xf1QT\x9b\xb2\x84\\p\v\xc5q\x06\xd37w\xfd\xeac\xd4S\x04\x93m\x9d\xda\x15\xbb\x11<\xbb\xd4\xcdk`\xa2\x03\x91\xa6\xd6_B\x8dS\v\xf9\x17\xb2\xb6]\xc3\xd6-D\xa3.\xf8Z\xb6\xec\x13\xbb\xdeO\x12\x9e\xd6l\xb3cV\xa3Z\xdcv\rm\x0f\xa4(\n\x9c\xa98\xaa\n\xf2\x1e\xb2\xf1\xeeĎ\t\xeb\xc77\x02t\xcb\t'\xc9\xd6\xce\xfbY\xb7\xb6\xbe\xb1ۈ\xf2\x00_\xa7\xbd\x11\xa3\x11\x98(\x17\xdc2\t\xdfl\xdb\x0e\x89E\xa3\xdc\xf1\xc24\xc3t\x83\xf2*\xc8\x0fl\x04b\xd2P\xafض\xb6\x0e\xe0\x18\x06#`\x1b\x9c\xa0\xac\xec\xf1ʵݩ\xa2PO̐\xc2E[\xb7\x13\x0f\xb5v\xba\xe0\x0f9\xecx]\xd8k7\x8a?\xae\xdf,\x9a\x86\x16\xca\nM\xe3\x8cp\xdf\xfbj88\xd4\x01y\x13\x19\x04\xe76\xb8\x12\xca{\x10\xecĀSw{@\xddt\x10\xb9\xb7Уd\x88k/,\x99\x11w\x92Wf\xaf,ʃ\xaa\xedX\xad\xc1\x00n\xee6\x83F\x9d\xf9\x89X\x91\x9fJ\xe2i\x15{\xe2\xe2T\x9b\xb9\x82\xba\xf7\xe6nþ\xa2\xdb\x0f\x01&ss\x91\xd9ZKr9\xbe\x00Ϗ\xf7\xea\xcf\x06X^\x93\xe5\r\xbe\xe7U\x04\xf0\x16vh\xd84 \fl\x00Z\xa3\x9e7\x84\x9a\xaa횜j\xcfnoȅa\xef~`\xa5\x90\xb5\x85S\xddƦ\xf5\x1b\x11\x89[^\xaa\x03\xe8\x04\x1a~\xe0\x96\xff\x8cu\a\xa4C\x18\x8c\x80x\xf6\x13\x19\xb7\xc7\xc8@\xb1\x89\x9bj$\xe8-Ta\xd8\xe5%\xce\xecK\x17\xf6]^\xb9\xba\xb5(\xecJH\xea'\x02\xd3\xf5\x1e\xd4\x11\xf6\x7f\x1e5\x1cq\x1doͽ\xfa\xd18\xb1N!N\xa4\xe9\x88\x19\xa8T\xce\x0eT/&c\xa2\x00f\x8e\xc6B\x19\x94R\xeb\x9d\x13q\xc9\x03(\n\x0fư\xed1\xe0>>nY\x17\x05\xdf\x16pM\x1a}\x824\xe3Zb\x8c6_\xc0X\x91%P\xe6rH\x1a\xd7r\x840\x9a~\x88\x10e@\x01t\xeb\xf9#\x86\x96\x9eB\x18\x1f\x14E\x87\xb8\xf3Ta\xec\x7f$\xfb\x80.m\x86\x8e\xe6\xb5w`\x05\x14\xe44K\xc5\n%\x1f@\xbb\x1e\x83y!\x05\a(qcv\x01\vz\x93\x1a\r\x83\x90lW\xa3\xa3\xb9f\xa8\t\xa22\"\xa4\xb1\xc0\xf3\xf5\xe5K1\x0f\xbeeE\x9dC~S\xd4Ƃ\xbe\xcbT\x05yX\xe6\x19ղ\x03&~\x9c\x04\xe0C\x8cBd\x80|\xc8\\\xa5\x15\xad\xa6Ĉ\xd4F\x1b\xc7\n\xdc\xe2\x0e2\xd5cچ\x11\x1dUa\xc0b\x95\xcb\x7f\xbd\x8c)Q\x94\x80~\xef\xfd~\f\xe3\x1a\x1aj\xf44j\x04b\xa3g\xc9 \x8fˑ\xb0PF\x888\xabr\x16\xb0\x97k\xcdǔj\x18N\xb3ju>{c \x06\f\x96\xa1گ\xc4\xe2a\xff\xff\x8cL>\x8b\xad\x86\xd6j\xb9\x90\xc8\xceB\x18\xdb\xe3\xe60\xe8o0\xdbsK4E\xafXH\a\x13\x95[\x87y\xbfe\x9a\x9d3\x13b\xa2\xdfH\x9a\x17\xe7=\x8f\t\xd5\xef\x90`{\xa5\x1eS\x88\xf4\xdfX\xaf]\fb\x19m\x1b\xb0-\xec\xf9A(m\x86+\x8a\xf0\r\xb2\xdaF\xf5\x04\xb7,\x17\xbb\x1dh\x84E\x8b\xe0͚\xf9\x14\xb1\xa6\xc3\x04\xd6Q@\xd1\n\x83q\xb5LG\xe6\x115bC\xa1\xa0/\n\x95\x11\xe2\xe8œu\xcf\xc5A\xe45/\xc8\xd0s\x99\xb9\xf1\xf1\x06\xbf\x98{2#\x10'\xf8;w\"\x8c\x02\xb9\xd4[IR\x12н.\x95\x8ey\x9e\xae\x9c\x82\x89\x93\xa1\x89\xd6Ǘ]ڢ\xeb\x02\x8cG\xc59\xb0\xad\u07b9j9\xe5\xc2\xf8\x82o\xa1`\x06\nȬ\xd2q\xf2\xa4\b\x81+\xa9\xfa3B\xd9\x11M\xda\x0f\x82f\x95h[0\xc0܋l\xef\xdcM\x942\x82\xc5r\x05\x864\x06\xaf\xaa\"b\x85\xda2+\x19\xbe\xb39\xa5і\x04\xf51\x84\x1bS$mI\xd4\xc1m\x99\xd1\xc6}\xaa7b\xf3J\xf4\x1e\x9a\xf2\xbb\x84}s\xd2\xfc\xf9\x85\x1d\xc9-\xc0t\u05fa\x84\r_S\xa0\xf6\xfc@\xf3\x0fƸ\xf3f\xcbf\xd8\xfa\xd9g˳p\xadA\xe3\x1f\x84id\xac\uef2dZİ\x9f\xba-\xafh\x11\xde3,\xbfb;QX _j\x0eю\xa33˹\xe7$P\xaa\xed\xc5Rr\x9b\xed?6[7\t-\x06\xb4\x1a\x02p~y\x88a\x88\a\t Y\xe3TЮ\xa3\xd0P\xba\xdd\xcc{\x9a\x1f\xed\x17\xf2\x00\xdf\x7f\xfa\x10[I\xec\x97DI=\x19\xd4\xfb\x81\xa7\xd3E\x81\x06\x98\x04\xb23(rӚ\x18\xcf\xedY_1\xce\x1e\xe1\xe8<\xab\xd1塱\x82\xac\xe5\rH\r\xb4\x81Nj\xe4\x11\x8e\x04\xca\xef\x88'\xc1[\"*\xae<\xc2Ȏ[\xac\xf4\x88\x8a\xf8\xf9}\nG]\xfc@\xa3H\x99Jmi\x88\xea\xe7\x0e\xb3*m\xb0l\x99R\n%P\xfc\xcca7\f륁<\xc2\xf1\x8dq\xec\xc3Y\xb3\x17c\xfbx\xd1A(\f}\x81fX\xc8\x7f\xf8\xca\v\x917\x9d\xd1(0\x9f\x94\xa5//Jb7\x883\t\xec\x1aӴ\x94\xce, ]\x16\xf5\xdf\xe2\xe0\xb6W\xf7вM\x18\xb6\x91\x18\x9f9\xfa,a\xd3\x1e\x02r\x0e\xad\xb26\xb4})\x95\\\xb9%-\xdf\xdb\x02\xa0]\xbc<\xab\x94\xeeq\xeaj!\xc4Q\x14=z\xf7h\xad\xdc/'\xb9'SECU\xf0\f\xf2\xb0\xcbF\x89.\xdc\u0083\xc8X\t\xfa\x01X\x85v#]\xa8\x16hrWΐ\xc2t\xd7\"\x14o\x16F\xf26\xc6\xca\ng}b\xcd\xc0\xe6\xa4ꑬ\x96\xe9\xeai\xa3$\xf3N\xfeP\x12\xf5\xbbi\x98\xcb,\xcbB~\x9d\xfa \x0eI\xe7~\x94\x9c6\x9e\xfe\x86\xe6\x95\xc4\xfb\xefi\u0590\vm\xd6\xec=%\xa1\x16\xd0m\x1fV\t;]%\x81DL\x84a('\a^\xa0\xfb\x80\xca[2(\x9c3\xa1v'\x1eT\x9a\x8ay\xda+\xe3l~\xb31v\xf9\bG\xbf9\xdb\xd5\x12\x97\x1b\x19]\xb5\xef\x17\xd4\xf9'J\xab\xf1Z\x94,\x8e\xec\x92~\xbb$\xc7l\xc9\x149\xc3y[ \xd5\v\xaa~[=\xd6[\xd0\x12,\x98Uɫ\x95\x9f\rV\x95\xd1=NW(UtI\x18\x81qz\xf0x\xb0q\x93P\x89\xee\xff\x1c\x05\x92\xe7C\xa5L$\xd3\"\x82֭2\xd6-\x1e\xf6\\\xf5\x91\xd5Ŕ\xc8ѯ82\xbe\xb3\xa0\x99\xb1J\x87\xe4ETك\xc5u\x94\x1a3/7n\x9fȯd:\xc0\x18\xa0^\xb6\xda\xc5كK\xb7W\x85\xff\x9f\x87\x99\x91\xa3E\xb0+\xad20\xd1l\x84\xb6$Z\x9d\x99\xc5\xdef\xa1\x97\xbb\xc0o\x97\xa4\xd6S\x96\xa1CY\xe6\xc6#i\xcf\b\x8a>~\xeb\xacY\xa3\nÿSD\xf9\x1c\x1c\x19\x9d_(K>L\xa4MF\xf7Ƶ\x0e\x13\xd0\x03s\xc1\x96~\xa8I!-\xf3\xb9\xbdH\xfe֜\x96R\xc8\ru\xc4\u07bd\x98\xa3Â\x19\x88e$\x8d\x95\x01;|\xfb\x96!͇\xd4ؗ\x85T5E\xfb<\x1az\x9c=\xdd\x05I\xe7\x14CG\\*\xdb]\xe8\xf1=\xbd1l'\xb4\xb1-\xc2\v\xa0\n3\x91\xf54:\xbc3\xe2S\xf9Q\xeb\xb3\xc3\xd3ϮugIr\xaf\x9e|\x12\xf3\x92\xa0<\x10\x7f\xcf\x0f\xe0SIAf\xaa\x96\xb4X\x86\xea\x02\xbbY\x00\xd11\xd1\x19\x93D\x9b\xd9i,\xeb2\x9d +\x92N!gWֺM~\xe4\"me\x8b\x9d\xc7V;\x95D9V\xfa\x99\xa1>\x9b\xb2\x9b\xad^\xf2o\xa2\xacK\xc6Kd˒\x98s\xe7\xf20Cj\xbb\xe3\xf5\x13\x17֟\x18r\x9b\xb2˴i\xa6ʪ\x00\v!\xc32S҈\x1c\x1a\xf7\xc1\xf3\x7f4_5V8\xdbqQ\xd4z\x81\x8e^̙\xa51\x9fWO\xcf\x1fȥ#\xb2\"b&.\xd8/p\xb8\xe7\xedG\xa5\x97\xb9̷\x1a\x9e\xdf5\xad\xb4P.\xcb\x7f\xda;\x9d\x85I\xdek\xdf;\xf5\xc2\xcb\xe51\xe6\x9e\xceB%L^\xddӦ\xbc\xba\xa7\xaf\xee\xe9\xab{:(\xaf\xee\xe9\xab{\xfaꞎ\x97W\xf7\xb4S^\xdd\xd3d\xfb\x91\x82\xe1\x8aVn'*$a\x95\x98\xbe1\x87\xf6L_>Kɟ\x05Y\x92]\xbd\x19o9r\x16h\xd1\x19\x12\xd31zM\xba5N\xc90\x99\xdcY\xc9\x04/\xfc\x19\xce\xda\x04\x04\xce>k\xb3\x99\x04\xf0\x8cgm<\xa6õ\xf3gG\x92S\xcf\xdfĜ\xf3\xb8n\xeb\x10S\xf7\xce{\xfc\xb6ii\xa1\xfc\\yK\x96~\xee}3\xd2\xec;N\xbess\x94\xd9^+\xa9j\xe3Wx\xb0\x87\xf7\x99\xbb\n td\x96(\x83wl\xaf\xea\xc8\x19\x8f\x19\xba&d\xde\xc6\xf3m}\x06\aX~x\xb7\xee\xffb\x95Ͼ\x8d`\xfd$\xec\xde\xdd\xfa\xc0\xf3\x1c\x1d\xf5\xce\x11\x9f0y\xfd\xbdCC\xc1\x8b@T\x9aIQ8\xa9\f\x10\xfa\x06\xf4s\xe5\x96\xfc\xce\xf6[\xe6\x17\x9e\xd2st\x97f\xe66\xb9\x94\xf3^\xf2w\xe4\xe3.;,5\x9b{\x9b\x824Kɸ\x1dϥ\x9d\x81\xba$\xcf6uM1!\xa76=\x936\x8d<\x8cn\vK͟M\x8eBSse_&C61/\xb6\x93\xed:\v\xf2\xccl\xd8d\x82\xa5e\xbe&\xe7\xbbv\xb2X\xe7\xa95\x91\xe5:\x9e\xbb:\vr,\xb75%c5\t\xd7\xe4<\xd5&\xfbt~g仲S\x9f\xff\x1c\xccs\xae[L\xe7\x9a&e\x98&\xadm\xcc㜔C\xba4s4\x89\xaaK\xb3D\x9b\fЉ\x8e\x93rCO\xf3>\xa7\x862\x9b\x11\x1a\xcf\xf6\x9c\x02;\x96\a\x9a\x90\xe39\x01\xb2\x9b\xfd\xb9\xd8\r\x98\x95\xa6\xd9\nKs7\xc7\xef\x00\fe\xde:\x17\xbf\x86\xcc~/\x99\x94\xee9\xcd)\xc1\xdd\xe7A\x13\x94\x96\xe0'\x8e9\xe2\xf1Pٹ\xe7g8\xe2\x11\x90\x9b\x1d+\xeb\u008a\xaa\xe8\\Pf\xf7pl\xae\xfc\xf9E\xd1\xc1\xf5푠}\xfe҈|\fd?\xa4\xe0\x86=AQ\xe0\xbf'T\xc8ܕ\x97\x99Z\x01Z\xa9\xf8F\xa0\xbf\xea\xc8ߗy\xe5\x96\xc5\xe8T?Y\xc0\x12!M_\x805iJ\xa6\xddc\xe7\xd5ӷ\xbf֠\x8f\x8c\xee\xdc\n~PT\xcc\xdaӞ~2\x1b\x8c\t\x83\xf2\xf1Z\xcc]\xbc\xdaWF\xf1\xd9Ш\x00\xf6^:\xc3<ĕ`\xa1\xd6ié)e\x8b\xd1S\f\x84T\r\x84H\xfb\x14\xef{\xc9\xf1Ǘ\b\xae\x9e#\xbcJrD^\"\xc4z\xa9 ki\x98\xb5$y#\xe9\xf8\xe2K\x04[K\u00adE>c\xfa\xf1ė:\x96\xf8\x02a\xd7ف\xd7\"ҥ\x1e;\\\x1c~%\x8co\xe6\x98\u124f\x96\x002z\xbcp<\x04K\x80xr\xacp6\bK\x99\a\xc30\xed\xbb\x0f\t&'2-\xdaMOMBJ\xdb\xe8\x9e?\xfc\x97x\xe8/q\x1b<\x05\xfb\xc4\xc3}\xcb\x0f\xf5%\xd2\xf9\xcc\xf0l\xb2\xeb\xc4\xc3{\x8b\x02\xb43C\xb4I\x88S\x87\xf5\xa6\x83\xb4\xe9\x05\xb8\xe1!\xbd3܉\x04\tK\xa8\xb2\xfc\xa0\xddwo\xc6(\x9d\x83\x9e\xdd\xd7Z\"γ\x82<\x88\xa3\xfa\xfd\x0fvt\u008d\xa8X\xab\xbbg\x16\xe3\xa8j\xee\x1d\xc9؟\x84\xf4\xbb\xf5(\xb8\x1d\x9f\xa4\xb7\xf1\xd6:L\xf1}\x9d\xd6K\xf5\x8f\a\xb8\x1d;\x03\x15״\x8f\xbf=\xba\xa4 \xb3f\x1fy\xb6oz\x88\x80\xa4~\xf7ܰ\x9d\xd2%\xb7\xec\xb2\xd9\n}\xeb:\xc0\xbf/\u05cc\xfd\xa8\x9a\xf4\x91νb\x11\xa8F\x94UqĈ\x89]v\xc1|\x9f\xe0D\x056\xe0s\xab\n\x91E<\xc4\xd1+\xe9\\\x83\x93\v}\xe8Ҽ\xac\x93\x05\x11\x8bQ\xb0\xb9\xf0\x17\x16\x0e.\x15v\u05cb\x9f\xb9t\xc1+\xf1_\xf4VO\xda\xda\xd8\xfb\xdb\rU\x0fRE\xef\xfc4\xd9s\x8d\x8cmaZ\xa1\xb7\x03'ף\vu${\xb5\xf9s\x02\"\xbd\x94\x11\xfc\f\xaf\xc63\x85Z\xecv\xe3\xb0\\\x93`qydʿ\x85 t\xbe\xaa\xb8\x8en\xea1/\x0f檇a\xb0\xe3s+X\x93f\xed\xf4\xe5\x8fn\xe9\xd1<<\x02B\x9b\xbdǪ\xbf\x8dN\x94\xee\xd0\xf3{p\x9a>\xb8<{d\xf9\x05p\x9av\x99VD\xc5\xc8O\xd1t\xbcg_=4\xfe\x12\xf9\x9f\xd5\x01>DW\x11\xfb\xcfb\f\x9a\x8c$\xd0\x05\xa8Sצ\xb7Ys\xf1묟!#.\xa0\xe2/\xbe^0>\xdfb\xfc-\r\xba\xff;\xc0\x9e\xb0m8eo\xbfR\xb8٨K?\xc3}0\x19\x96\n\aW\xb5F@ƞ\x12y.jY\xa5\xf9\x03\xfc\xa4\xdck/)\xd4\xea\xb7\xe8=\xf8㝹\x90M\xec\xe7ZL\x99\xfb\xb1\r\x01\xb6\x87\fN\xae\xa3Glϼm\xdf\xda\"ap\xf7\xf7?\xb9\x01\xd1\xd3\x18\x1f\xfc\xbb\x17\xa8w\r \xa5\xc3@]\xa3m\\;\xed\xd5\x13\xdd\xe7\xde}\xae\xa2\xf3\xe8\x15С\x06J\x1b=k4uU(\x9e\x83\xbe\xa1\xe79\x12\x06\xf6\xe7^\x83\x81e\xef?\xf2\xe1\xedcd`\xa1\xe7\x17\xcc\fA\x97\xad(\xa0\xf8Q\x14`\x1c≊\xfe\xf6\xb4e\xa3\xf7\xebr\xeb\x1c\xd4\x1d\xfe\xd8t2aM\xddPiM\xbf\x02\x8d\x8e\xa0[\xfd\xafM\x10\xf0ib\xb0\x86\x8fBZx\x88$\xf8\xcch\xf8C\xef\xe9\x8f0IR\xd4\xda\xd7\xf1\x96\x1do\xb93]\xa7\x92\x05\xd5.\n\x8b\x1b\xa32A\x0e6\xed\x8eЩ\x91\x97\xbbsz*P\x9a\xa0cm\xe0\xf3\x93\x04\xfd%\xa8d\xb3\x91\xb1\xb76\xfa\xb3\xe6\xa4a\xf4\x9d\r\xabȭ\x1fT\x1f\v\xe7\xa4'\x90q\xaf\xb4\x84m\x1ea\x9a\xf7\xa7NI73\xaf\xe2Z~\xdc'Y\x8d?\x87\xb3j^\xe8\xb9H\xa0\xac{\x85\xa6\x0fx\xfc\x811\xf7\\M\xc6+[\xeb\xa0rjM\x17p#\x10p\xf7S\x9f\xf7\xc4X\xfb \xd5\f/ۇ\x9dڅ\xef\xd9'#G\xf8\xd7<\x12\x16}Qȅ\x8c\xeeI\xc7\x15\xc2?\x8f\x9d\xa3\xf3\x00q\xbes\xefD%\x8c\xf7.<\x95u:\xe0f\x188\xe4\xd8\xcbS/9\x12\xbaz}f\f\xb7X\xa79\xbb\xe8E\x86\x1a\x86+\xdb\xefbL\x18?\x8c\xb6b\x9f\xe04\xf8\\\xb1\x8f\x12\aqJ\x00w\xe2\frZ\xf8\x1f{(rr\x88\x87\xa6\x15\x1d\xf7\x1b\xd1{}\x85=\xa8>HF\xa6'x\x9a*\xeehߘ\x80\xfeA\xecܮL\x86c\xfa\xe3I\x8d\xa8\n\x9eT\xbf1\xd5;\xaa\x1cN>\xd2kbyGH\xbc\xdf\xd9\xfdRo\xdb\x1b\xf4\xd9\xdf\xfe~\xf1\x7f\x01\x00\x00\xff\xff=3w\xbfMx\x00\x00"), + []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xdc=Ms\x1b\xb9rw\xff\n\x94spR%\xd2\xcfI\x0e)\xdd\x14\xaf\x9de\xde[[%9\xde38\xd3$\xf1\x84\x01f\x01\fe&\x95\xff\x9eB\xe3c>\x88\x99\xc1P\xe2\xbe}\xc1MC\xa0\x01t7\xfa\x1b\xd0j\xb5zCk\xf6\x1d\x94fR\xdc\x12Z3\xf8a@ؿ\xf4\xfa\xe9\xdf\xf4\x9a\xc9\xf7\xc7\x0fo\x9e\x98(o\xc9\xc7F\x1bY=\x80\x96\x8d*\xe0'\xd81\xc1\f\x93\xe2M\x05\x86\x96\xd4\xd0\xdb7\x84P!\xa4\xa1\xf6\xb3\xb6\x7f\x12RHa\x94\xe4\x1c\xd4j\x0fb\xfd\xd4la\xdb0^\x82B\xe0a\xea\xe3\x9f\xd6\x1f\xfey\xfd\xa77\x84\bZ\xc1-Q\xa0\x8dT\xa0\xd7G\xe0\xa0\xe4\x9a\xc97\xba\x86\xc2\xc2\xdc+\xd9Է\xa4\xfd\xc1\x8d\xf1\xf3\xb9\xb5>\xb8\xe1\xf8\x853m\xfe\xdc\xfd\xfa\x17\xa6\r\xfeR\xf3FQ\xdeN\x86\x1f5\x13\xfb\x86S\x15?\xbf!D\x17\xb2\x86[\xf2\xc5NS\xd3\x02\xca7\x84\xf8\xa5\xe3\xb4+\xbf\xea\xe3\a\a\xa28@E\xddz\b\x915\x88\xbb\xfb\xcd\xf7\x7fy\xec}&\xa4\x04](V\x1bD\x80_\x1ba\x9aP\xf2\x1d\xf7f\x17\x80\xb8&\xe6@\rQP+\xd0 \x8c&\xe6\x00\x84\xd65g\x05\xa2:B$D\xee\xe2(MvJV-\xb4--\x9e\x9a\x9a\x18I(1T\xed\xc1\x90?7[P\x02\fhR\xf0F\x1bP\xeb\b\xabV\xb2\x06eX@\xack\x1dv\xe9|\x1d\xec\xe5\x9dݮ\xebEJ\xcb'\xe0\x96\xecQ\x06\xa5ǐ]\xad90\xddnm\xb8\x1d\xbf%*\x88\xdc\xfe\x15\n\xb3&\x8f\xa0,\x18\xa2\x0f\xb2\xe1\xa5e\xaf#(\x8b\x9cB\xee\x05\xfb\xef\b[ۍ\xdaI95\xe0\xe9\xdd6&\f(A99R\xde\xc0\r\xa1\xa2$\x15=\x11\x05v\x16҈\x0e<\xec\xa2\xd7\xe4\x17$\x8f\xd8\xc9[r0\xa6ַ\xef\xdf\xef\x99\tǤ\x90U\xd5\bfN\xef\x91\xe3ٶ1R\xe9\xf7%\x1c\x81\xbf\xd7l\xbf\xa2\xaa80\x03\x85i\x14\xbc\xa75[\xe1\xd2\x05\x1e\x95uU\xfeC$ۻ\xdeZ\xcd\xc9r\x9e6\x8a\x89}\xe7\ad\xf3\t\nX\x86w\xbc䆺]\xb4\x88\xb6\x9f,v\x1e>=~\xeb\xf2\x19\xd3C\xec#\xde;\xccג\xc0\"\x8c\x89\x1d(GD\xe46\v\x13DYK&\f\xfeQp\x06b\x88~\xddl+f,\xdd\x7fk@[\x86\x96k\xf2\x11e\a\xd9\x02i\xea\x92\x1a(\xd7d#\xc8GZ\x01\xffH5\\\x9d\x00\x16\xd3ze\x11\x9bG\x82\xae\xd8\x1bvvX\xeb\xfc\x10\x84\xd7\b\xbd\xfc\xe9\x7f\xac\xa1\xe8\x9d\x18;\x8c\xed\xfc1';\xa9z\xc2\xc1\x0eY\xf7\x80\xa6\x0f\xadm\xee\xf4[\t6\xfce\xb0\x94\x7f\x8f\x1d-\xff\xd8E4\x82\xfd\xd6\x00\x8a8wb\xe1L\xa4\x9c\x81$a}\xc8\x16\xeb\xb3\xdfGpj\x1b\xfc(xSB\x19\xa5\xed\xd9^\x06+\xfet6\x00\xb5\x0ee\xc2\xf2\xbf\x15\xffv٢\xfdՊ\xd3Ċ\xa9\x02b9\x90\t\a\x8f0\x81\x9bMb\xda6f\xa0J,nrw\x84\x88\x86s\xba\xe5pK\x8cj`\x043T)z\x1aALP\xc1\xb9x\x89\xfd\xbd@\u0b00\xae\xa2p\xa8qJ\x86\xaa\xf3\x15\x91?8V\x98\xb6\xe2,\xec\xf2^rV\x9cfQ\x93\x1a\x14\x8e\x9b?|\x81\x83\xb7p\xa0G&UbK\xf6Dڮ\x1dE\xda\nSie\x99\aR^\xb6\xe1$\xb2\x0eR>\xcd\xd1\xfegۧ\x95ڤ@\xe3-n\xc5S\xdb+\xd1-\x10\xf8\x01Ec\x12\xcb$\xa4lP\x81HEj\xa9\xcd8\xdd\xc7e\x0fq\xe2`\x8ci\xc9\x14Ӝ\xed̋\xca@9\xbbўؔ\x02\xecZ+K\xb9\xb6\xaf\x92\x8d\xeb;\xd4o\x1d\x8c\xa71B\xb6TCI\xa4\xe7\xfa\x86\x83\xf6s\x95H\xfeV\xae܌\x82\x8e\x9bw\x96\x06\xa7[\xe0D\x03\x87\xc2Hu\x8e\xc9\x1c|\xba\x96#+G𘐚}\xf6o76\x01\x92X6\x7f>\xb0\xe2\xe0\x8c\x00˛\b\x87\x94\x124\n\x0ek\xa8\x9e\xc66I\xe6h\xef'\x99\x12\x1dm\x9b9SCx)qҶ\fq۶\x19\xc1{&X\xfc\xf7\xa4\xe6l\xdb\xffO\xc4\x06Mr\x01\xd3nΆ\xbe.ӢSe\x8d\xfd͎@U\x9b\xd3\ra&|\x9d\x83H9\xef\xcc\xffwL\x98\xe5\x1c\xbf\x19\x8e|U\x8e\x9f\xa4\xca\x1cDK\x958\xfd\xdf!QPY\x942;:\x97\x15\\{\x82\x84\xb9\x9fj=\x04\xda5y\a\xd7a\xd2~@D\xa0㝏<\x82a\xb1 \x8b\xe67G\xf2\x05Ih\x01\xf7\x17l3\x92\xad\x13>D¾ӎD\xf6\x14\x1cX\x9d\xb9Q\x8c\x1ej\xc0\xd3\x12\x02c\xdf)ge\x9c\xc8\xf1\xfdF\x8c[\xc3\xfd\xf6E\x9a\x8d\xb8q\x1e\x99F.\xf9I\x82\xfe\"\r~\xb9\n:\xdd\xc2/@\xa6\x1b\x88\xc7K8\xb1m\xf1Ѝ\xb0e0\xb7k\x1b\x17H\x89\xe4a\x9al\x84u\\<>0^ꦛ\xd6\x0f\xfdV5\x1aChB\x8a\x15\xaa\xcauj&\x87\xecL\x90R\xf5(r\xbe\xb48\xa9\x9b0\x13\xec7\xabI\xdcx\x17\x01洀2x\x9b\x18\xb7\xa4\x06\xf6\xac \x15\xa8\xfd\x94\xe2\xe8\xb6\xda\xca\xf7\xbc%dJ]\xd7\x16rX\x9ej\x0f͋\xeer~1+{r3z\x05b\xcfv\x1d\tW\x8ew\x9d\xdf\x11\xaaX\xb4?f\xb1K\xcb\x12SH\x94\xdf/\x90\xf8\vhq\xae\xfb\xdd\u009c\x86\xachm\xcf\xef\xffX5\x87\f\xfd\xbf\xa4\xa6Le\x9c\xe1;L\x13q\xe8\x8d\xf5\x81\xb1\xee4v\x06\xa6\x89\xa5\xef\x91\xf2\xf3@xbs\xd2\xca\x16\xe0N\x91\xcbݙ\xc5rC\x9e\x0fR;\x9d\xbac\xc0S!\x9b~c\x9a\xbc}\x82\xd3ۛ39\xf0v#\xde:\x05\xbfX\xdcDkA\n~\"oq\xecۗ\x18A\x99\x9c\x98\xd9\xed\xc7\xea)\x86\xe4V\x15\xadW\x9e{\x8d\xacX1:N$\xc3\xe3m\xeb\xb1S7D\xde\xc6ƽy<\xb5\xdb,\xfe\xad\xa56?\xa7\x03}#\xeb\xb9\x0f#\xfa6m\"^6k\xeb\xfb\xd8W\x14\xc6\xd6\x02\xdc\x19P>\xf8\xe7\x04t\xf0\x1c^\xe8S\xcd\x05\xf7b`\x8fƀ\xacE\xf0\f7\xb9TI\xce\x12\x97X\x9b\x16/\v\xed\xf4O?:\xb1I{\xb2\xed\xdfݍ\xbc\xb65\\Ȫ\xa2\xc3\xe4`\xd6R?\xba\x91\x81\xa7= G}\xb5o\xf0<盉\x81\x870-\xf8\xcć\tB\x83\xd8\x00\xe5\x19\x8a\x92Z\xceK0\xd7\x0eT\x93-\x80\x881\xf5?\x82\x9e\xaf\x98\xd8\xe0\x04\xe4ë\xdb\x05\xa4E\xd7E\xe4\f\xa8\x8e\x04\x8d\x1fPS\xe5\x9aT\xb2$\xcf\aP\xd0\xe3\x8a\xf3@\xb9\xb543A\ni\xba\xf1\b\v\xb7\x96\xe5;MvLi\xd3]h.\xc35:\x97\x1d\x16R\xd8\xee\xee\x1b\xab@6\xe6\x02\x1a|jG\xf7\xf2\xba\x15\xfd\xc1\xaa\xa6\"\xb4\x92M\x86Q\xe0\x9a\xd5/\xac\x8a\xc9WO\x81g\xcaL\xccCad\xc6HK\xa5\x9a\x83\xc9%\xf1\x16vV\x1c\x15RhV\x82\n\xc5\x01\x8e\xb2Lڃ\xbb\xa3\x8c7\xa9\xb4O\xaa-uo\xc5'\xa5.\xf2n\xbf\xba\x91\x9dh\xe3A>\xf7\x11\x94\x8d\x82\x03=\x02a;\xc2\f\x01QX\xba\x80r\"\x1b\xa7\xf0\xc8@\xd4d\xb3e\x9e\x80\xb7\rDS\xe5!`\x85'\x9b\x89\xc9`Z\xb7\xfbg\xca\xf85\xc8f9\xef\xb3T\x0f@\xcbK\x020\xbfv\x86\x13\x10\xbaQ\x98\xb8w\xe2\xe5\x99\xf1\xbc5[\xca\x11N\x1bQ\x1c\x00\xe5\x94\xe8\x89\x0f\xe2\xc03\xa1\r\xd0\\^\xb0VS#\x04\x13\xfb<\xdae\x878\xdb\xe6P\xbd\x95\x92\x03\x1d\x16<\xa5\x9a\xc5\xf5\xe5b\xe8\xd7v\xf4\xef\"\x86\"\x05\xf2ͅ-xRyYD\x8d\x81\xaav\xe7M\x12Ո\xae\xf6\xb9\x82\x14Z\xe2\x83\xfbU\xbc\xa6s\xcd\x04\xcb \xec \xe7\xc2Lײ\xb4 \xaejY\xda\t\xa2QqI\xf8l\xd3\x03`OgpRp\xed\x91k\x16X\x99[ \xb4,\xa1t\x81Ik\xaax\x9fŕ\x97\x8d\x94*$w\xb7\xdcL̢lh=\x8f\x14C\xb1\xea\b\xabF<\t\xf9,V\xe8\xc9\xeb\xc5\x02$?4\xf8\xaaӛ\x8b%\xd1\xef)\x85\xfa\xfc\x9a\xcfS\xc1x\xba\x82\x94\xc9\xe6\x9bEѐ).\x98\x93k\xaety\xe4\xc7\xd9UL\xcd?1\xd8'\x9a?\xba\x9a\xe3\xdcz\xb6MzT\xc7\xf8{>\x809\x80\n\xc5\xcc+\xac\xdbN\xc9\xe96\x1f\xdd\xfa1\xb1\xc0\xcd\xf2O0\x85]\xe1\xe5\xa0\xe4-\xed\xe8X+\xe0\xc626m\xb8q\xe5ǪI0QV\xe1W\xda2ȩ\x9c\x98\xab\x97\xe8\xd7\x00\xc6z\x85P\x04(\xc3$\x89\x1d:Z\xbaJ\xdfn2\xbe_\xf8\x80!\xbf\xb0ҿyy`FM\xc3L%\xc3t\xd1\xe4\x14\xbe\xce٦\x8b\xb1\x96\a}?_M\xfb\xc7B\x9f\x81\xeak\xed\xcf\xc1\xa8\x01\xda\xc7`bȠ\x1c\x04%\xb7uٱ\xb0\x80\xb2\x94p\xb1\xa7Ї\x03-Ļ\x02O\xa2\f\x805\x86\x9a\xfdi\xf3\xd5\xedL\x93\x7f%\a\xd9$J\xea&\xb03S`1^V\xe1\x93\b`\xe8\xf1ú\xff\x8b\x91\xbe\xc8\x02#_\x89ݡ\xa3\xd2FS\x99(ّ\x95\r\xe5\xbdC\xd6a\x8b\x96{\x88TD0\x9eʯZ\xb6\n\xe3{lD\xbe\xd6.ϲX\x1cM\x9b\x88y\xb5\x18\x17W`\xf4+,F\x94\xd4ҔC~\xa9i~\x8d\xc5tQĒʊa\xdd\xc4(\xd0\xf9z\x8a\x1c\xeb~\xa6v₊\x89\xccj\xb9\x17'Hrj\".\xaa\x84\x98-(ˬ\x7f\xe8W6L\x83\\P\xf5\x90\x85\x9c\xf9\n\x87\xc5u\r\xbe\x8e`r\x1f\xd9\xd5\f\x89:\x85I\xc0\xa35\fS\xd5\t\xd3(OT.\xe4\xd7$L\x82\xc6z\x85\xf9J\x84\u05eb7|\r/`\\\xd4\xccV\x13\xbc\xc8KȨ\x17XR%0\x8b\xb1\v+\x02b\xc6\x7fdޥu\x00\xfd<\xff\bМ\xec\xffHv\x7f\x04\xe2d\xce?7\xa7?\x02{F\xedNr\xc9\xe4\x8fKr\xf9\xd1\r\xf9\x85\xd65\x13\xfbs>\xc9\xe5\xa6IN:+\x04\xe8\xce\xd9c\xa5\xae\xb7\xd0\xf3\xb3RS\xba[\xb9\t\x9f,\x84\xf5\x980rM\xee\xc4\xe9\f.^\tH\xfa \xfdk[vYό\xf3\xee\xdd$\x04\xdb\x05\xe5o\xf9\xe9td\xc0v\x1c\xb3\xb0\x93$\x94\xaag\x1dϹ`_\aݻ\x81\xc2ik;eh3s\xb8\xd0ڮ\x1anX\x9d<\xf2\xb5\x92G\x86a\xc7\x03\x9c\">\xff*\xf1V\xd0\xf6\x84\x90\xbe>\xc4Ӹ\x1e8\x0e4u\x86\x9e\x81sB\xf5\xf9\xf6\vw1\xb6\x90+\xbc\xebf)\x19\xf8\xc1_\xa0\xbd\xc1\x13\x9b\xf2\xd8E\xb8\xb2YY0x\xb9V'\"\"\xa3\xbah\xda\x1ev\xa6;~\xfb\xad\x01u\"\xf2\x889}o ͔\xdd;\xb9\xa2\xad\xff\x16$\x9d\x17\x97\xee:\xf6\xc0Oh\xe5\v\xb9\x13Nc'\xc1\x0eֈp\xac\x88k}#+ͭ\xdb3\xd25\tU\xc88:\xcd\x0f\x93\x8a)\xb7f\xfd\xba\x9e\xd2r_i\xd6J\xb9\x8a\xbft\xb9\xc74\x012\xb7\x06=/'2[s~-\xcfi\xcew\xca6\x1a\xf3jʯQK\xbe\xa0\x86|\x81\x0f\xb5̋\xcaFSN\xad\xf8U|\xa9+zS\xd7\xf0\xa7.\xf3\xa8f@\x0ej\xc0s\xaa\xbb\xb3\xd2x\xd99\x9b\x9c,\xdb|\xe6x\xbaj;\xa3Z;#\x1b4\xb7Ҍ\xaa\xece\xd5\xd8\x198\xbc\x92\xafu%o\xeb\x1a\xfe\xd6u=\xaeY\x9fk\x96sf~^VE}q\x92!\xa4\xa3\xbf\xc8\x12\xee\xa52s\x0e\xc2\xfd\xb0\x7f\"\x05\xd8q\x9a$/\x89\b]S\x99\x06k\xfb{\xbb\xff\xb2M\xa5\xb3u\xc1\xfc\xfdE\x96vms\xb9\x85\x87A\xf7\xb3+\xb4;P \xdc\xc3\x12\xff\xf9\xf8\xf5K\x84\x9f\xb2G\xbd\xd1;x\xd3\xc0\x19\x18\xa5G\x8e\xcf>\xf9\x82\x1b\x87-\xd4ᯜ$\xa05\xfb\x0f|\xb3k> sw\xbf\xc1\xae\xc1Z·\xbebB?\xe6\u07b6`\xb5G\xc4\xc8(\xf7ov=\x88\x89\xb2\xd3\xf8'\xc1\x17\x93\x82\xf6bc5Y\xae\bɞ\xba\xfb\x8d[ݚ|\xb6\xa6\x9b8\x11\xe9\x18\xef\xc0T\xb9\xaa\xa92'\xe4\x0e}\x13\xd70\x1e\x94\t:d*t2*j\xcf߂J\xe26<\t\x85\t\xb8S\xdd\xcff\x0e1z\xc9:\xc6oO\xccޛx\xc5u\x8c\xab\xe3\x15b*\xf19Y\x01\xf1j!)/\x86\xee\xbfω\xb5\x87\xd8qZ\x9eYO6\x84u\x12\xf8\xb1\xe3Q\xa4iAk}HD\x85^&\xd3\xf0\xa1*CM\x93\xb9\x1f\u05f7\xb7%V\x1c:\x02\xe8\x19\x82\x88R\x9dg\xfb\x06k\xb2g\xd5\x01B5\x8c&\xb4`\xfc\xa6\xe3\x98\xff>)\xcf\xccgA.~\x10ġgDT`\xa4Ɋ\xb1\xc0\v-^.HvΚp\x19\x85\xad\xd3vg\xe6\x8b\x12\x17\xbf%1\x8f\xac\x04\xa2ƞ\x91\xc8y*\xe2o\x8a\xcf\t\x91\xa4\x8b\x03\x94\r\x87\x8c\a\xde\x1e;]\xe7\x9fx\v\x80SgR\xf6\x1fy\xb3x\xed\xa8Wk\xf0\xf6\x1f\x93\xf3H\xf7\x90GJ\xbc\xbb \x9de\xef^\x9d*\xac\x1d\xaf\x9b\xa2\x00\xadw\r\x0f\xd52\x85\x02j\xa0\fݓ\x95\xf9a\x0f\x8b\xcaB\x9a\x9aKZ\x82\xfa(Ŏ%\xf2\b=\x9c\xfeW\xaf\xf3\x80a\v\xfcب\xf6\x11\xbf\xc9\xf7\xd3^$\x9d\x9e\x153\xf0XS\xa5\xe13\xe3Y\xe7\xed\xd7\xc1\x10\xe7\x9d\xed8ݻꦒ\x15\xd4@TD8\xc3\xc8\x01\xd8\xe1x\x8d\xb0\xb8+T\x91#a\x98\xec\xa30V#?z\x18\xd2\x06\xc0\xaa\xf3\x02\xe2\x9b\f8:\xa1\xe1&\xb4[Ak\x83\x17\x18\x90\xe0\x8dRȭ\xee7\xb9;{\xb5\xb1\av\x9c\xa2\xbe\x12\xd5\xd7QiC\xab\x84\x11=\xbc\xc37\x1c\x81o\xa3\xaa\xb2Sy\xd5}\xdc.\x16T\xa5\x98\x89\xeaX\f[\xae;\xb0\x1d\x18\xb4[-h(\t\x1cA\x10\xcbݔq(\xa7*\xaf\xbfa\xc4S\x1dA\xbd\xd3\x11\x0eւٓ\xf1h\xa82q\xe9\xe7\x87y'UE\xcd-)\xa9\x81\x95\x1d}\x99q\x93~xR\xa9\xf9\xe4\x14\xde7\xf2g\x04/\t!y9\xf7\xb7\x84*К\xee\x83\xe7\xf5\f\n\xc8\x1e\x84E\xf1\xd4\xdbz\xedE+/|cɠ\xc5\x16-LC\xfd\x04\xceȉi\xbb\x04H\xff`+:s\xfbQ\x91DŽ\x81\xfdY\xc2\xcc_\xf2z\x00\xaa\x87\x0f\xfc\x9e!\xe2s\xb7\xaf\x0fw:\x1c\xb8\xd7d\xa8\xab\xef\xc3\xf7`\r\x8b\x0e\xe6\x88\"\xb13/\x12\xcf\a)\x9f\xb2\xacПc\xc76\xda\u0084\xe3#\xbcڶ\x95\x8d\xe9ڗr,6\x8b\xef.\xbe\xb2\xbcF\x98w\xee\xae\xcbX\b\xf1\xfca\xc78 \xfas\xd2PNDSmA!C\xc6\x0e\x87\x89\xfbޏ\xe1uY\xceO7Cȃ\xe7\xa6[\xd8S\x10\x91\xf4^\x06t\xee\x00\x87\xd8\xd7\x00\x88\xeb>\xf1\xf4$\xc1\xb7\x82\x83\xca\x1f{\xecn\x8a\xa3#\x8e?\xe3\xa2r\x11\xecz\x8fa\xd7-\u07b91 \xd2^\x18\t\xb9\xe7p,.X\xfa\x84\xbdW\x1f\xa8\x9e3\xf4\xeem\x9fx\xf9\xb7\xa3\x93\xa2\x8d\xf70r$ӗ\x11W\xe4\v<'\xbe:da\x06'\xadIVd#\xee\x95\xdc+\xd0猳\xc2\xdbiL\xec?Kuϛ=\x13\xb1xxY\xe7{\xaa\f\xb3\xac\xec֓\x18\xfb1\xe8\xb2\xc4o\xf3\xa3G~\x98\x90Q\xb5\xdf\xf3l8\xd3u\x9b\x93O^\x80\xbeӭ\xbeH\x85\xfe<\xb45\xf9\"\r\x84,#\xeb\x03e\x9alA\x9b\x15\xecvR\x19\x17}^\xad\b\xdby;%\x15%\xa5\x8c\xa3\x97\xe4^綮S\xac\x8b\x8b6\xbb\x8f\x84)T\n\xe8^U\xf4\xe4\x02j\xb4(\xac\t\fﵡ)S\xfcEb\x14\xdd2\xcf\xcd9\x87|\xd3\xed\x1f\x83Q\xf1\x80#8\x87:\xbc\x15\xec4p\xb2\u0082\xe0\r\xd4Σ\x04D[mv\xc9q'N\xd0l\xc6]\xcc\xfeU\xaa\xd8yLN\xf9m\xf4\xde!\x1e\xaf\xa2f:\f\xb54+\x0eT\xec-\xfb(\xd9\xec\x0f\x81\x05\xc7\f\x95\x11\xa0e\x83\xd1\xea\x1aO\xaa\x0e\xd9z\xd3(\xd1I6\xf9\xfc}\xd9.w\n\xe8\xc5\x12S\xb5w\tZ\x991\xa1yS\xc6\xfe\xc8\xe0\x11\xfc\xa7B\xe1q\b\xd5'QL_pp\x9e\"\x9b\xb8\xc78\x85\x8c\xe4~\xa3\x04\xbcd\xbfqp\xfe~\xbbʻu%\x96l~aW\x15\xcf \xbf`\xcc\xe3O]\xaf\x18\xcfs\xa2\b/n\xb5\x90\x16\xf4\x8d*\xea2Pb\xc5r0\x99\x16\x95\xa5\x11\xdfYnk\xc3Ԏ\xd9=t\xfb\xc1\xf2\x8bQ\xf2\x96\xdb\xfd5[\x1b\xaa\xb7\xae\xf6܄_\x1d\x89\x1c\x00\xff\xc9\x1e\x117c\xb5\x90\x0fc\xbd\xbdg7ZI\x06\xdf*\r\x06Qf91P>\xb0\xa7=Hf\x15ӵ$T\xfe\x83g\x8fu5\x82H\x05\xd9z\x80\xa7Ǥ\xffq\x0e\x97\xfb=\xb0\x82\x1bˬ(\x81q\xdf!{\xe2\x86p\xd8)\xcd\xec^\x98y\x9a \x90\x1e\xb6\x0e\x9d\x9f\x86\x9f\x1dB9\xb7\xe0\xd1\xe9\x80\n»\xce4\x90\xdcދ\x12\x8c\xe5e\x1f\xe6\xfb\aH\x00F$\xaaxmH8\xdaַ\xddO\x0e\xc0V\xa9\x02\xb8\xbch+\x1d\xde9\xd9\xcb\xf6P\xf2k_YU \xdf\xdfn\xbe\xfe\xeb]\xef3\x1bȒ\xa7\x14\x13\x86q\xf6\x95&\x06\xd3~\xa62\xbb\xe7\x96i@\u0383\xb4X\xa3Ұ\n\xd4\xcd\x1b\x90\x8c)\xcd*\xd0B\xe5\"\v\\\xa1\xc6f\xaf\xea\"g[@\x06\xad\x9b\x06\x95V\x15h+\xc2\xd4s\xa5\xa3Q:_\a\x18\xbf\xc1A\xb9ZN\x12\xc1\x90\xf0\xf9\t\x05\xb9\xa7\x83\x9b\x1f´\xf8\x13\x93z\x80\x19V⒩\xed/\x90\xd95\xbb\x03\x8d`\x02֙\x92\a\xd0H\x81L=H\xf1?\rl\x83RoI\x18-x}\xd0\x16\x9a\xc0\x92\x17\xec\xc0\x8b\x1a\xae\x18\x979+\xf9\x91i\xc0^X-;\xf0\xa8\x8aY\xb3\x9f\x95\x06&\xe4N]\xb3\xbd\xb5\x95\xb9~\xfb\xf6AؠI3U\x96\xb5\x14\xf6\xf8\x96\x94\xa2\xd8\xd6Vi\xf36\x87\x03\x14o\x8dxXq\x9d텅\xcc\xd6\x1a\xde\xf2J\xac\buI\xdat]\xe6\xff\x148j\xde\xf4p=\x99o\xae\x90\"\x9c\xe0\x00jD'0\xae\xa9\x1bEKh\xfc\x84\xd4\xf9\xf2\xf1\xee\xbe+L\xc2\f\xa9Ot\xefHX\xcb\x02$\x98\x90;\xf03z\xa7UI0A\xe6\x95\x12\xd2\xd2\x1fY!@\x0e\xc9o\xeam),\xf2\xfd\xaf5\x18\x8b\xbcZ\xb3\x1b2/(\x87u\x8530_\xb3\x8dd7\xbc\x84\xe2\x86\x1bxq\x06 \xa5\xcd\n\t\x9bƂ\xaee\x1cVvT\xeb\xfc\x10\xcc[\x84_a\x8e\xdfU\x90\xf5\xa6\f\xb6\x13;\x91\xd1\xc4 \xed٨\x80\x81\x06ue|\xd6\xd2/\xa4\xa6\x86_\ax8]\x16z\x05\x83\xf6\xc3\xee\x89í\x19C\xb9r\xd0P\xa7H5\xe4\xee\x98\x16\xecP\xc2C\x99\xc1\xa4\xaf\xf5R\xed\xdb\tL\xe6U\xdd:\x82\xe3\tW\t\xc5GQm\xca\x12r\xc1-\x14\xc7\x19L\xdf\xdc\xf5\xab\x8fQO\x11L\xb6ujW\xecF\xf0\xecR7\xaf\x81\x89\x0eD\x9aZ\x7f\t5N-\xe4_\xc8\xdav\r[\xb7\x10\x8d\xba\xe0kٲO\xecz?IxZ\xb3͎Y\x8djq\xdb5\xb4=\x90\xa2(p\xa6\xe2\xa8*\xc8{\xc8ƻ\x13;&\xac\x1f\xdf\b\xd0-'\x9c$[;\xefg\xdd\xda\xfa\xc6n#\xca\x03|\x9d\xf6F\x8cF`\xa2\\p\xcb$|\xb3m;$\x16\x8dr\xc7\v\xd3\f\xd3\rʫ ?\xb0\x11\x88IC\xbdb\xdb\xda:\x80c\x18\x8c\x80mp\x82\xb2\xb2\xc7+\xd7v\xa7\x8aB=1C\n\x17m\xddN<\xd4\xda\xe9\x82?\xe4\xb0\xe3ua\xaf\xdd(\xfe\xb8~\xb3h\x1aZ(+4\x8d3\xc2}\xef\xab\xe1\xe0P\a\xe4Md\x10\x9c\xdb\xe0J(\xefA\xb0\x13\x03N\xdd\xed\x01u\xd3A\xe4\xdeB\x8f\x92!\xae\xbd\xb0dF\xdcI^\x99\xbd\xb2(\x0f\xaa\xb6c\xb5\x06\x03\xb8\xb9\xdb\f\x1au\xe6'bE~*\x89\xa7U쉋Sm\xe6\n\xeaޛ\xbb\r\xfb\x8an?\x04\x98\xcc\xcdEfk-\xc9\xe5\xf8\x02\xbe\xec\xd2\x16]\x17`<*\u0381m\xf5\xceU\xcb)\x17\xc6\x17|\v\x053P@f\x95\x8e\x93'E\b\\I՟\x11ʎh\xd2~\x104\xabDۂ\x01\xe6^d{\xe7n\xa2\x94\x11,\x96+0\xa41xU\x15\x11+ԖY\xc9\xf0\x9d\xcd)\x8d\xb6$\xa8\x8f!ܘ\"iK\xa2\x0enˌ6\xeeS\xbd\x11\x9bW\xa2\xf7Д\xdf%웓\xe6\xcf/\xecHn\x01\xa6\xbb\xd6%l\xf8\x9a\x02\xb5\xe7\a\x9a\x7f0Ɲ7[6\xc3\xd6\xcf>[\x9e\x85k\r\x1a\xff L#cu\xe7m\xd5\"\x86\xfd\xd4myE\x8b\xf0\x9ea\xf9\x15ۉ\xc2\x02\xf9Rs\x88v\x1c\x9dY\xce='\x81Rm/\x96\x92\xdbl\xff\xb1ٺIh1\xa0\xd5\x10\x80\xf3\xcbC\fC)\x8b\xff|\xfc&\f\xa2(s\xf6A\x81\xf9\xa4,}yQ\x12\xbbA\x9cI`ט\xa6\xa5tf\x01鲨\xff\x16\a\xb7\xbd\xba\x87\x96m°\x8d\xc4\xf8\xcc\xd1g\t\x9b\xf6\x10\x90sh\x95\xb5\xa1\xedK\xa9\xe4\xca-i\xf9\xde\x16\x00\xed\xe2\xe5Y\xa5t\x8fSW\v!\x8e\xa2\xe8ѻGk\xe5~9\xc9=\x99*\x1a\xaa\x82g\x90\x87]6Jt\xe1\x16\x1eD\xc6J\xd0\x0f\xc0*\xb4\x1b\xe9B\xb5@\x93\xbbr\x86\x14\xa6\xbb\x16\xa1x\xb30\x92\xb71VV8\xeb\x13k\x066'U\x8fd\xb5LWO\x1b%\x99w\U000874a8\xdfM\xc3\\fY\x16\xf2\xeb\xd4\aqH:\xf7\xa3\xe4\xb4\xf1\xf474\xaf$\xde\x7fO\xb3\x86\\h\xb3f\xef)\t\xb5\x80n\xfb\xb0J\xd8\xe9*\t$b\"\fC99\xf0\x02\xdd\aTޒA\xe1\x9c\t\xb5;\xf1\xa0\xd2T\xcc\xd3^\x19g\U000db371\xcbG8\xfa\xcdٮ\x96\xb8\xdc\xc8\xe8\xaa}\xbf\xa0\xce?QZ\x8dעdqd\x97\xf4\xdb%9fK\xa6\xc8\x19\xce\xdb\x02\xa9^P\xf5\xdb\xea\xb1ނ\x96`\xc1\xacJ^\xad\xfcl\xb0\xaa\x8c\xeeq\xbaB\xa9\xa2K\xc2\b\x8cӃǃ\x8d\x9b\x84Jt\xff\xe7(\x90<\x1f*e\"\x99\x16\x11\xb4n\x95\xb1n\xf1\xb0窏\xac.\xa6D\x8e~ő\xf1\x9d\x05͌U:$/\xa2\xca\x1e,\xae\xa3Ԙy\xb9q\xfbD~%\xd3\x01\xc6\x00\xf5\xb2\xd5.\xce\x1e\\\xba\xbd*\xfc\xff<̌\x1c-\x82]i\x95\x81\x89f#\xb4%\xd1\xea\xcc,\xf66\v\xbd\xdc\x05~\xbb$\xb5\x9e\xb2\f\x1d\xca27\x1eI{FP\xf4\xf1[g\xcd\x1aU\x18\xfe\x9d\"\xca\xe7\xe0\xc8\xe8\xfcBY\xf2a\"m2\xba7\xaeu\x98\x80\x1e\x98\v\xb6\xf4CM\ni\x99\xcf\xedE\xf2\xb7洔Bn\xa8#\xf6\xee\xc5\x1c\x1d\x16\xcc@,#i\xac\f\xd8\xe1۷\fi>\xa4ƾ,\xa4\xaa)\xda\xe7\xd1\xd0\xe3\xec\xe9.H:\xa7\x18:\xe2R\xd9\xeeB\x8f\xef\xe9\x8da;\xa1\x8dm\x11^\x00U\x98\x89\xac\xa7\xd1\xe1\x9d\x11\x9fʏZ\x9f\x1d\x9e~v\xad;K\x92{\xf5䓘\x97\x04\xe5\x81\xf8{~\x00\x9fJ\n2S\xb5\xa4\xc52T\x17\xd8\xcd\x02\x88\x8e\x89Θ$\xda\xccNcY\x97\xe9\x04Y\x91t\n9\xbb\xb2\xd6m\xf2#\x17i+[\xec<\xb6ک$ʱ\xd2\xcf\f\xf5ٔ\xddl\xf5\x92\x7f\x13e]2^\"[\x96Ĝ;\x97\x87\x19R\xdb\x1d\xaf\x9f\xb8\xb0\xfeĐ۔]\xa6M3UV\x05X\b\x19\x96\x99\x92F\xe4и\x0f\x9e\xff\xa3\xf9\xaa\xb1\xc2َ\x8b\xa2\xd6\vt\xf4b\xce,\x8d\xf9\xbczz\xfe@.\x1d\x91\x15\x113q\xc1~\x81\xc3=o?*\xbd\xcce\xbe\xd5\xf0\xfc\xaei\xa5\x85rY\xfe\xd3\xde\xe9,L\xf2^\xfbީ\x17^.\x8f1\xf7t\x16*a\xf2\xea\x9e6\xe5\xd5=}uO_\xdd\xd3AyuO_\xdd\xd3W\xf7t\xbc\xbc\xba\xa7\x9d\xf2\xea\x9e&ۏ\x14\fW\xb4r;Q!\t\xab\xc4\xf4\x8d9\xb4g\xfa\xf2YJ\xfe,Ȓ\xec\xea\xcdxˑ\xb3@\x8bΐ\x98\x8e\xd1kҭqJ\x86\xc9\xe4\xceJ&x\xe1\xcfp\xd6& p\xf6Y\x9b\xcd$\x80g\xf0\x9d\xdf\x19\xf9\xae\xec\xd4\xe7?\a\xf3\x9c\xeb\x16ӹ\xa6I\x19\xa6Ik\x1b\xf38'\xe5\x90.\xcd\x1cM\xa2\xea\xd2,\xd1&\x03t\xa2\xe3\xa4\xdc\xd0Ӽϩ\xa1\xccf\x84Ƴ=\xa7\xc0\x8e\xe5\x81&\xe4xN\x80\xecf\x7f.v\x03f\xa5i\xb6\xc2\xd2\xdc\xcd\xf1;\x00C\x99\xb7\xceů!\xb3\xdfK&\xa5{NsJp\xf7y\xd0\x04\xa5%\xf8\x89c\x8ex\xc5\xfb^r\xfc\xf1%\x82\xab\xe7\b\xaf\x92\x1c\x91\x97\b\xb1^*\xc8Z\x1af-I\xdeH:\xbe\xf8\x12\xc1֒pk\x91Ϙ~<\xf1\xa5\x8e%\xbe@\xd8uvൈt\xa9\xc7\x0e\x17\x87_\t\xe3\x9b9fx\xe2\xa3%\x80\x8c\x1e/\x1c\x0f\xc1\x12 \x9e\x1c+\x9c\r\xc2R\xe6\xc10L\xfb\xeeC\x82ɉL\x8bv\xd3S\x93\x90\xd26\xba\xe7\x0f\xff%\x1e\xfaK\xdc\x06O\xc1>\xf1p\xdf\xf2C}\x89t>3<\x9b\xec:\xf1\xf0ޢ\x00\xed\xcc\x10m\x12\xe2\xd4a\xbd\xe9 mz\x01nxH\xef\fw\"A\xc2\x12\xaa,?h\xf7ݛ1J\xe7\xa0g\xf7\xb5\x96\x88\xf3\xac \x0f\xe2\xa8~\xff\x83\x1d\x9dp#*\xd6\xea\xee\x99\xc58\xaa\x9a{G2\xf6'!\xfdn=\nn\xc7'\xe9m\xbc\xb5\x0eS|_\xa7\xf5R\xfd\xe3\x01n\xc7\xce@\xc55\xed\xe3o\x8f.)Ȭ\xd9G\x9e\xed\x9b\x1e\" \xa9\xdf=7l\xa7t\xc9-\xbbl\xb6Bߺ\x0e\xf0\xef\xcb5c?\xaa&}\xa4s\xafX\x04\xaa\x11eU\x1c1bb\x97]0\xdf'8Q\x81\r\xf8ܪBd\x11\x0fq\xf4J:\xd7\xe0\xe4B\x1f\xba4/\xebdA\xc4b\x14l.\xfc\x85\x85\x83K\x85\xdd\xf5\xe2g.]\xf0J\xfc'\xbdՓ\xb66\xf6\xfevCՃT\xd1;?M\xf6\\#c[\x98V\xe8\xed\xc0\xc9\xf5\xe8B\x1d\xc9^m\xfe\x9c\x80H/e\x04?ë\xf1L\xa1\x16\xbb\xdd8,\xd7$X\\\x1e\x99\xf2o!\b\x9d\xaf*\xae\xa3\x9bz\xcc˃\xb9\xeaa\x18\xec\xf8\xdc\n֤Y;}\xf9\xa3[z4\x0f\x8f\x80\xd0f\xef\xb1\xeao\xa3\x13\xa5;\xf4\xfc\x1e\x9c\xa6\x0f.\xcf\x1eY~\x01\x9c\xa6]\xa6\x15Q1\xf2S4\x1d\xef\xd9W\x0f\x8d\xbfD\xfegu\x80\x0f\xd1U\xc4\xfe\xb3\x18\x83&#\tt\x01\xeaԵ\xe9m\xd6\\\xfc:\xebgȈ\v\xa8\xf8\x8b\xaf\x17\x8cϷ\x18\x7fK\x83\xee\xff\x0e\xb0'l\x1bN\xd9ۯ\x14n6\xea\xd2\xcfp\x1fL\x86\xa5\xc2\xc1U\xad\x11\x90\xb1\xa7D\x9e\x8bZVi\xfe\x00?)\xf7\xdaK\n\xb5\xfa-z\x0f\xfexg.d\x13\xfb\xb9\x16S\xe6~lC\x80\xed!\x83\x93\xeb\xe8\x11\xdb3o۷\xb6H\x18\xdc\xfd\xfdOn@\xf44\xc6\a\xff\xee\x05\xea]\x03H\xe90P\xd7h\x1b\xd7N{\xf5D\xf7\xb9w\x9f\xab\xe83\x1a\xfe\xd0{\xfa#L\x92\x14\xb5\xf6u\xbce\xc7[\xeeLשdA\xb5\x8b\xc2\xe2ƨL\x90\x83M\xbb#tj\xe4\xe5\ue71e\n\x94&\xe8X\x1b\xf8\xfc$A\x7f\t*\xd9ld쭍\xfe\xac9i\x18}g\xc3*r\xeb\a\xd5\xc7\xc29\xe9\td\xdc+-a\x9bG\x98\xe6\xfd\xa9S\xd2\xcd̫\xb8\x96\x1f\xf7IV\xe3\xcfᬚ\x17z.\x12(\xeb^\xa1\xe9\x03\x1e\x7f`\xcc=W\x93\xf1\xca\xd6:\xa8\x9cZ\xd3\x05\xdc\b\x04\xdc\xfd\xd4\xe7=1\xd6>H5\xc3\xcb\xf6a\xa7v\xe1{\xf6\xc9\xc8\x11\xfe5\x8f\x84E_\x14r!\xa3{\xd2q\x85\xf0\xcfc\xe7\xe8<@\x9c\xef\xdc;Q\t\xe3\xbd\vOe\x9d\x0e\xb8\x19\x06\x0e9\xf6\xf2\xd4K\x8e\x84\xae^\x9f\x19\xc3-\xd6i\xce.z\x91\xa1\x86\xe1\xca\xf6\xbb\x18\x13\xc6\x0f\xa3\xad\xd8'8\r>W\xec\xa3\xc4A\x9c\x12\xc0\x9d8\x83\x9c\x16\xfe\xc7\x1e\x8a\x9c\x1c\xe2\xa1iE\xc7\xfdF\xf4^_a\x0f\xaa\x0f\x92\x91\xe9\t\x9e\xa6\x8a;\xda7&\xa0\x7f\x10;\xb7+\x93\xe1\x98\xfexR#\xaa\x82'\xd5oL\xf5\x8e*\x87\x93\x8f\xf4\x9aX\xde\x11\x12\xefwv\xbf\xd4\xdb\xf6\x06}\xf6\xb7\xbf_\xfco\x00\x00\x00\xff\xff}\xef\x8a\xf1Mx\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4V\xcfo\xeb6\f\xbe\xe7\xaf \xb0û\xcc\xce\xebv\x19r\x1b\xba\x1d\x8am\x0fE\xf3л\"\xd3\tWY\xf2H*]\xf6\xd7\x0f\x92\xec&\xb1\x9d\xb5\x1b0\xdd\"\xf1\xc7Ǐ\xe4\xe7TU\xb52==#\v\x05\xbf\x01\xd3\x13\xfe\xa9\xe8\xd3/\xa9_~\x90\x9a\xc2\xfax\xb7z!\xdfl\xe0>\x8a\x86\xee\t%D\xb6\xf8\x13\xb6\xe4I)\xf8U\x87j\x1a\xa3f\xb3\x020\xde\a5\xe9Z\xd2O\x00\x1b\xbcrp\x0e\xb9ڣ\xaf_\xe2\x0ew\x91\\\x83\x9c\x83\x8f\xa9\x8f\x9f\xeb\xbb\xef\xea\xcf+\x00o:܀ \xa775\x1a\x85\U0004f222R\x1f\xd1!\x87\x9a\xc2Jz\xb4)\xfe\x9eC\xec7p~(\xfeC\xee\x82{\x9bCms\xa8\xa7\x12*\xbf:\x12\xfd\xe5\x96ů4X\xf5.\xb2qˀ\xb2\x81\x1c\x02\xeb\x97s\xd2\nD\xb8\xbc\x90\xdfGgx\xd1y\x05 6\xf4\xb8\x81\xec\xdb\x1b\x8b\xcd\n` $Ǫ\x06.\x8ew%\x9c=`gJ\x12\x80У\xff\xf1\xf1\xe1\xf9\xfb\xed\xd55@\x83b\x99zʹ.T\x06$``@\x01\x1a\xc0X\x8b\"`#3z\x85\x82\x12ȷ\x81\xbb\xdcɷ\xd0\x00f\x17\xa2\x82\x1e\x10\x9e3\xe5Ce\xf5\x9bIϡGV\x1a\xd9\x18\xdc\xceCvq;\xc1\xfa)\x95S\xac\xa0IӅ\x923\r\x94`30\x00\xa1\x05=\x90\x00c\xcf(\xe8u\x8a2\xf3ӂ\xf1\x10v\xbf\xa3\xd5z\xe0AR\xb3\xa2k\xd2P\x1e\x91\x15\x18m\xd8{\xfa\xeb-\xb6$BRRgt\x9c\x93\xf3!\xaf\xc8\xde88\x1a\x17\xf1[0\xbe\x81Μ\x801e\x81\xe8/\xe2e\x13\xa9\xe1\xb7\xc0\x98\xc9\xdc\xc0A\xb5\x97\xcdz\xbd'\x1d\x97ˆ\xae\x8b\x9e\xf4\xb4\xce{B\xbb\xa8\x81e\xdd\xe0\x11\xddZh_\x19\xb6\aR\xb4\x1a\x19צ\xa7*C\xf7y\xc1\xea\xae\xf9\x86\x87u\x94OWX\xf5\x94&K\x94\xc9\xef/\x1e\xf2B\xfcC\a\xd2:\x94\xf9(\xae\xa5\x8a3\xd1\xe9*\xb1\xf3\xf4\xf3\xf6+\x8c\xa9s3\xa6\xecg\xdeώrnA\"\x8c|\x8b\\\x9a\xd8r\xe8rL\xf4M\x1fȗ鲎\xd0O闸\xebHe\x9c\xddԫ\x1a\xee\xb3\xe2\xc0\x0e!\xf6\x8dQljx\xf0po:t\xf7F\xf0\x7fo@bZ\xaaD\xec\xc7Zp)\x96S\xe3\xc2\xda\xc5\xc3(s7\xfa\xb5\xb0\xdd\xdb\x1em\xea`\"1ySK6\xaf\a\xb4\x81\xc1,\xb9\xd4\x1fB\x92=\xfe%\x96AI\n\x9a\x89\xbe\xa4\xfd|\x1fͲ\x9c䗃\x11\x9c^N0=&\x9bi~G-ړuXB\x145\xc1\xf7\xa1\xa4\x83>v\xf3\x9c\x15|\xc1ׅ\xdbG\x0eIY\xb3\xae_\x9f\x1b\xb3\x01\xe5{\xb3'?+wZY\xb1\xca߰K\xa9\xbe\x10\xe8!\x10p\xf4>\xed\xedL!3\x90\xa9\x92\xcflH\xb1[@\xb3\x88\xe7\xc1\xb7!\x7f\xf0MJl\xb4\xec\x13\x0e\xcd\x1e\xf2\x14\\\v\x01o\xf7\xba\x9c\xb9x}\x88\xd0r\xf2\x97\xf4\xbf9'\xb9!\xc6\xc5\xdcUF\xb5\xf8\x902.1\xbe\xbc_\x03\xca\xe8\x9c\xd99܀r\x9c{\x17_\xc3lNө\x19G\xed+u(j\xba\xfe\xbd\x01\x9a9\xa4=y=\xa0\xbf\xb5\r\xf0j\xa6*\x7f\x95\x19v\xa7[\xae\xf7o\xff\x01\xe7+UFw\x03I\xbb+\xa5\x05\xce>D\xcab\xf7\xcaH/\xfe\xf3\x98\x11\xb2\xbd\xb4\x1d5\xe3j5\xc6?\"\xf3\x1anBXl\xf6\xec2\x87o.\xca\x13\rl\xf6c\xc1\x7f\a\x00\x00\xff\xff\xb1J-\xe7\xa6\v\x00\x00"), []byte("\x1f\x8b\b\x00\x00\x00\x00\x00\x00\xff\xb4VA\x93\xdb6\x0f\xbd\xfbW`&\x87\xbdDr\xf2}\x97\x8e/\x9d̦\x87L\x93f'N\xf7N\x8b\x90\x8d\x9a\"U\x10\xd4\xc6\xfd\xf5\x1d\x90\xd2\xdak\xcb\xc9n\xa7\xd5\xc5c\n\x04\x1f\xde\xc3\x03UU\xd5\xc2\xf4t\x8f\x1c)\xf8\x15\x98\x9e\xf0\x9b\xa0\xd7\x7f\xb1\xde\xff\x14k\n\xcb\xe1\xedbOޮ\xe06E\t\xdd\x17\x8c!q\x83\xef\xb1%OB\xc1/:\x14c\x8d\x98\xd5\x02\xc0x\x1f\xc4\xe8rԿ\x00M\xf0\xc2\xc19\xe4j\x8b\xbeާ\rn\x129\x8b\x9c\x93OG\x0fo\xea\xb7\xff\xab\xdf,\x00\xbc\xe9p\x05Cp\xa9\xc3\xe8M\x1fwA\\hJ\xcez@\x87\x1cj\n\x8b\xd8c\xa3Gl9\xa4~\x05\xc7\x17%\xc5x|\x81~\x9f\xb3\xad\xc7l\x1f\xc7l9\xc0Q\x94_\xbf\x13\xf4\x91\xa2\xe4\xc0\xde%6\xee*\xb2\x1c\x13w\x81\xe5\xb7\xe3\xe9\x15\fѕ7\xe4\xb7\xc9\x19\xbe\xb6\x7f\x01\x10\x9b\xd0\xe3\n\xf2\xf6\xde4h\x17\x00#?9]5Q\xf3\xb6dlvؙr\x0e@\xe8ѿ\xbb\xfbp\xff\xff\xf5\x93e\x00\x8b\xb1a\xea%\xb3<_\"P\x04\x03\x13\x12x\xd8!#\xdcg>!J`\x8c#\xe8Ǥ\x00\x13\xfeX?.\xf6\x1czd\xa1\xa9\xf8\xf2\x9c\xf4\xd7\xc9\xea\x19\xae\x1b\x85^\xa2\xc0jca\x04\xd9\xe1T>ڱZ\b-Ȏ\"0\xf6\x8c\x11\xbd\x1c\x85<>\xa1\x05\xe3!l\xfe\xc0FjX#k\x1a\xd5&9\xab\xfd8 \v06a\xeb\xe9\xaf\xc7\xdc\x11$\xe4C\x9d\x11\x1c5?>\xe4\x05\xd9\x1b\a\x83q\t_\x83\xf1\x16:s\x00F=\x05\x92?ɗCb\r\x9f\x02#\x90o\xc3\nv\"}\\-\x97[\x92\xc9WM\xe8\xba\xe4I\x0e\xcbl\x11\xda$\t\x1c\x97\x16\at\xcbH\xdb\xcap\xb3#\xc1F\x12\xe3\xd2\xf4Te\xe8\xbe\xf8\xa0\xb3\xafxtb\xbcy\x82U\x0e\xdaEQ\x98\xfc\xf6\xe4E6\xc2w\x14P\x0f\x94F([K\x15G\xa2uI\xd9\xf9\xf2\xcb\xfa+LGg1\xce\xd9ϼ\x1f7ƣ\x04J\x18\xf9\x16\xb9\x88\xd8r\xe8rN\xf4\xb6\x0f\xe4%\xffi\x1c\xa1?\xa7?\xa6MG\xa2\xba\xff\x990\x8ajU\xc3m\x1e6\xb0AH\xbd5\x82\xb6\x86\x0f\x1enM\x87\xee\xd6D\xfc\xcf\x05P\xa6c\xa5\xc4>O\x82\xd39y\x1e\\X;5\xd88ޮ\xe85\xef\xe4u\x8f\xcd\x13\x03i\x16jitv\x1b\xf8\x8cW3\xf9|>_\xfd$|\xde\xe0P\x86|K\xdb\xf3U\x00cm\xbe\"\x8c\xbb\xbb\xba\xf7;\x84\xcd\xd4}\x9bO\xd2Fm\x03+\xa2\x81,r5\xd59\"I<\x16L\xe8l\xac/R^\xe1<\x97\xc2hUc\xe3.\x81>E\xf2\x18\x98\xef8C\xbeP~L\x90[\x8f\xbbq\xc6zAo\xf3P\xbf@\x13r\x0fG\xb4\xf0@\xb2+\xe6p\xa7\x97\xd4\xf3T\xd0g\x8f\x87\xb9\xe53\xec_w\xa8\x91e\x9c\"Dl\x18EqDtj^uf\r\xf0)\xc5l/3\x9b\x11tD\x90\x9dv\xef\xf1pI4\xfcH\xdc\xf1\xbe\xff1\xe4\x1b\xbd\x17'\xc0\x8c-2z\x99\xb5\xb8~b\xb0G\xc1\xecr\x1b\x9a\xa8\x06o\xb0\x97\xb8\f\x03\xf2@\xf8\xb0|\b\xbc'\xbf\xad\x94\xf0\xaa4B\\\xe6\xef\x86\xe5\xab\xfcs\xa5䯟\xdf\x7f^\xc1;k!\xc8\x0eYUk\x93\x9b\x1a\xed\xe4\xb6{\x9d'\xeekHd\x7f\xbe\xf9'\xbc\x84\xbe8\xe7\x19ܬs\xf7\x1f\xf4\xe6Π\x94\xa2uQ%0\xe8\xdcT\xb1\xbbQ\xcd2\x1f\xe6\x1aq´\t\xc1\xa1\xb9l=\x9d\xbe\xc4h/!Uz\xc2Kl\x06\xf0\xad:\nUu\xa6\xafJ\xb4\x91\xd0Qs\x16=\xf9\xfc\a\x96\xbc\x1b\xc3t<(\aӶ\xa9m\xcaWL\xfe\xa61[\xbc6\x16f\x14\x99/\xbcz<\xe0Y\x03]\x8c\xa4\xf8\U000917b7\x8d\x91\x9bq\xac7\x89\xb5\xfdǜ3\x9f?\xff\xceX\xefw&\xcex\xf3\x19\xa8\xeft\xe7$\x83\xa3\x16\x9bC\xe3\xb0$\x84\xd0\xce\xf4ދ \xeb\x83>us\x8d\xf8n0\xe4\xcc\xc6\xe1̻߽\xb9\xfa\xf6\xaa\xf8\xb3z^,F\xfdƱ+\x10N%\xf7\xd8e\xe3\xca\xdf\x01\x00\x00\xff\xff\xec\xa0\xe0\xa1k\r\x00\x00"), } diff --git a/pkg/apis/velero/v1/backup_types.go b/pkg/apis/velero/v1/backup_types.go index 66a827bf0..858894dc7 100644 --- a/pkg/apis/velero/v1/backup_types.go +++ b/pkg/apis/velero/v1/backup_types.go @@ -159,7 +159,7 @@ type BackupSpec struct { CSISnapshotTimeout metav1.Duration `json:"csiSnapshotTimeout,omitempty"` // ItemOperationTimeout specifies the time used to wait for asynchronous BackupItemAction operations - // The default value is 1 hour. + // The default value is 4 hour. // +optional ItemOperationTimeout metav1.Duration `json:"itemOperationTimeout,omitempty"` // ResourcePolicy specifies the referenced resource policies that backup should follow diff --git a/pkg/apis/velero/v1/restore_types.go b/pkg/apis/velero/v1/restore_types.go index 417813152..82ed0e660 100644 --- a/pkg/apis/velero/v1/restore_types.go +++ b/pkg/apis/velero/v1/restore_types.go @@ -115,7 +115,7 @@ type RestoreSpec struct { ExistingResourcePolicy PolicyType `json:"existingResourcePolicy,omitempty"` // ItemOperationTimeout specifies the time used to wait for RestoreItemAction operations - // The default value is 1 hour. + // The default value is 4 hour. // +optional ItemOperationTimeout metav1.Duration `json:"itemOperationTimeout,omitempty"` diff --git a/site/content/docs/main/api-types/backup.md b/site/content/docs/main/api-types/backup.md index dc55344b5..1768f0934 100644 --- a/site/content/docs/main/api-types/backup.md +++ b/site/content/docs/main/api-types/backup.md @@ -35,8 +35,8 @@ spec: csiSnapshotTimeout: 10m # ItemOperationTimeout specifies the time used to wait for # asynchronous BackupItemAction operations - # The default value is 1 hour. - itemOperationTimeout: 1h + # The default value is 4 hour. + itemOperationTimeout: 4h # resourcePolicy specifies the referenced resource policies that backup should follow # optional resourcePolicy: diff --git a/site/content/docs/main/api-types/restore.md b/site/content/docs/main/api-types/restore.md index 722f85b89..95e8f6c9a 100644 --- a/site/content/docs/main/api-types/restore.md +++ b/site/content/docs/main/api-types/restore.md @@ -37,8 +37,8 @@ spec: scheduleName: my-scheduled-backup-name # ItemOperationTimeout specifies the time used to wait for # asynchronous BackupItemAction operations - # The default value is 1 hour. - itemOperationTimeout: 1h + # The default value is 4 hour. + itemOperationTimeout: 4h # UploaderConfig specifies the configuration for the restore. uploaderConfig: # WriteSparseFiles is a flag to indicate whether write files sparsely or not diff --git a/site/content/docs/v1.13/api-types/backup.md b/site/content/docs/v1.13/api-types/backup.md index dc55344b5..1768f0934 100644 --- a/site/content/docs/v1.13/api-types/backup.md +++ b/site/content/docs/v1.13/api-types/backup.md @@ -35,8 +35,8 @@ spec: csiSnapshotTimeout: 10m # ItemOperationTimeout specifies the time used to wait for # asynchronous BackupItemAction operations - # The default value is 1 hour. - itemOperationTimeout: 1h + # The default value is 4 hour. + itemOperationTimeout: 4h # resourcePolicy specifies the referenced resource policies that backup should follow # optional resourcePolicy: diff --git a/site/content/docs/v1.13/api-types/restore.md b/site/content/docs/v1.13/api-types/restore.md index 722f85b89..95e8f6c9a 100644 --- a/site/content/docs/v1.13/api-types/restore.md +++ b/site/content/docs/v1.13/api-types/restore.md @@ -37,8 +37,8 @@ spec: scheduleName: my-scheduled-backup-name # ItemOperationTimeout specifies the time used to wait for # asynchronous BackupItemAction operations - # The default value is 1 hour. - itemOperationTimeout: 1h + # The default value is 4 hour. + itemOperationTimeout: 4h # UploaderConfig specifies the configuration for the restore. uploaderConfig: # WriteSparseFiles is a flag to indicate whether write files sparsely or not From 1b22a49d22de719d4e3d9aa5ad8fd12960917845 Mon Sep 17 00:00:00 2001 From: danfengl Date: Thu, 4 Jan 2024 05:05:28 +0000 Subject: [PATCH 05/32] Bumpup E2E test plugins matirx for v1.13 Signed-off-by: danfengl --- test/util/velero/velero_utils.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/util/velero/velero_utils.go b/test/util/velero/velero_utils.go index d1a6b0c5b..61e1db22a 100644 --- a/test/util/velero/velero_utils.go +++ b/test/util/velero/velero_utils.go @@ -96,6 +96,13 @@ var pluginsMatrix = map[string]map[string][]string{ "gcp": {"velero/velero-plugin-for-gcp:v1.8.0"}, "csi": {"velero/velero-plugin-for-csi:v0.6.0"}, }, + "v1.13": { + "aws": {"velero/velero-plugin-for-aws:v1.9.0"}, + "azure": {"velero/velero-plugin-for-microsoft-azure:v1.9.0"}, + "vsphere": {"vsphereveleroplugin/velero-plugin-for-vsphere:v1.5.2"}, + "gcp": {"velero/velero-plugin-for-gcp:v1.9.0"}, + "csi": {"velero/velero-plugin-for-csi:v0.7.0"}, + }, "main": { "aws": {"velero/velero-plugin-for-aws:main"}, "azure": {"velero/velero-plugin-for-microsoft-azure:main"}, From 9a1be6f53fae95717591883172607698a5a82318 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wenkai=20Yin=28=E5=B0=B9=E6=96=87=E5=BC=80=29?= Date: Wed, 10 Jan 2024 09:45:57 +0800 Subject: [PATCH 06/32] Make "disable-informer-cache" option false(enabled) by default to keep it consistent with the help message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make "disable-informer-cache" option false(enabled) by default to keep it consi stent with the help message Fixes #7264 Signed-off-by: Wenkai Yin(尹文开) --- changelogs/unreleased/7294-ywk253100 | 1 + pkg/cmd/cli/install/install.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/7294-ywk253100 diff --git a/changelogs/unreleased/7294-ywk253100 b/changelogs/unreleased/7294-ywk253100 new file mode 100644 index 000000000..a12df3439 --- /dev/null +++ b/changelogs/unreleased/7294-ywk253100 @@ -0,0 +1 @@ +Make "disable-informer-cache" option false(enabled) by default to keep it consistent with the help message \ No newline at end of file diff --git a/pkg/cmd/cli/install/install.go b/pkg/cmd/cli/install/install.go index 5d2ccd28a..1c6772887 100644 --- a/pkg/cmd/cli/install/install.go +++ b/pkg/cmd/cli/install/install.go @@ -155,7 +155,7 @@ func NewInstallOptions() *Options { DefaultVolumesToFsBackup: false, UploaderType: uploader.KopiaType, DefaultSnapshotMoveData: false, - DisableInformerCache: true, + DisableInformerCache: false, ScheduleSkipImmediately: false, } } From 0b307ca035c7790e0ed2eabe5d20d0e72e4f15af Mon Sep 17 00:00:00 2001 From: Jose Arevalo Date: Wed, 10 Jan 2024 11:57:09 +1000 Subject: [PATCH 07/32] Add CRD name to error message when it is not ready to use When debugging this error it is currently hard to identify what CRD is causing the issue. This is particularly difficult when dealing with over a hundred CRDs. Signed-off-by: Jose Arevalo --- changelogs/unreleased/7295-josemarevalo | 1 + pkg/restore/restore.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/7295-josemarevalo diff --git a/changelogs/unreleased/7295-josemarevalo b/changelogs/unreleased/7295-josemarevalo new file mode 100644 index 000000000..724dcfb85 --- /dev/null +++ b/changelogs/unreleased/7295-josemarevalo @@ -0,0 +1 @@ +Add CRD name to error message when it is not ready to use \ No newline at end of file diff --git a/pkg/restore/restore.go b/pkg/restore/restore.go index e59d14be9..43833b2b3 100644 --- a/pkg/restore/restore.go +++ b/pkg/restore/restore.go @@ -1757,7 +1757,7 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso if groupResource == kuberesource.CustomResourceDefinitions { available, err := ctx.crdAvailable(name, resourceClient) if err != nil { - errs.Add(namespace, errors.Wrapf(err, "error verifying custom resource definition is ready to use")) + errs.Add(namespace, errors.Wrapf(err, "error verifying the CRD %s is ready to use", name)) } else if !available { errs.Add(namespace, fmt.Errorf("the CRD %s is not available to use for custom resources", name)) } From a9c820c9d6240d6db631d09d6a64f98f1cbc9ccc Mon Sep 17 00:00:00 2001 From: danfengl Date: Wed, 10 Jan 2024 02:57:44 +0000 Subject: [PATCH 08/32] Fix nightly issue of missing param WithoutDisableInformerCacheParam during Velero installation Signed-off-by: danfengl --- test/e2e/migration/migration.go | 1 - test/e2e/upgrade/upgrade.go | 1 - test/util/velero/install.go | 9 +++++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/test/e2e/migration/migration.go b/test/e2e/migration/migration.go index 6b389d495..202cad701 100644 --- a/test/e2e/migration/migration.go +++ b/test/e2e/migration/migration.go @@ -168,7 +168,6 @@ func MigrationTest(useVolumeSnapshots bool, veleroCLI2Version VeleroCLI2Version) //TODO: Remove this setting when migration path is from 1.13 to higher version //TODO: or self, because version 1.12 and older versions have no this parameter. OriginVeleroCfg.WithoutDisableInformerCacheParam = true - OriginVeleroCfg.DisableInformerCache = false } Expect(VeleroInstall(context.Background(), &OriginVeleroCfg, false)).To(Succeed()) if veleroCLI2Version.VeleroVersion != "self" { diff --git a/test/e2e/upgrade/upgrade.go b/test/e2e/upgrade/upgrade.go index 9a70daae5..25c4b1ab6 100644 --- a/test/e2e/upgrade/upgrade.go +++ b/test/e2e/upgrade/upgrade.go @@ -148,7 +148,6 @@ func BackupUpgradeRestoreTest(useVolumeSnapshots bool, veleroCLI2Version VeleroC //TODO: Remove this setting when upgrade path is from 1.13 to higher //TODO: version, or self version 1.12 and older versions have no this parameter. tmpCfgForOldVeleroInstall.WithoutDisableInformerCacheParam = true - tmpCfgForOldVeleroInstall.DisableInformerCache = false Expect(VeleroInstall(context.Background(), &tmpCfgForOldVeleroInstall, false)).To(Succeed()) Expect(CheckVeleroVersion(context.Background(), tmpCfgForOldVeleroInstall.VeleroCLI, diff --git a/test/util/velero/install.go b/test/util/velero/install.go index 42aedceb4..9c621259c 100644 --- a/test/util/velero/install.go +++ b/test/util/velero/install.go @@ -134,10 +134,11 @@ func VeleroInstall(ctx context.Context, veleroCfg *VeleroConfig, isStandbyCluste veleroInstallOptions.DisableInformerCache = veleroCfg.DisableInformerCache err = installVeleroServer(ctx, veleroCfg.VeleroCLI, veleroCfg.CloudProvider, &installOptions{ - Options: veleroInstallOptions, - RegistryCredentialFile: veleroCfg.RegistryCredentialFile, - RestoreHelperImage: veleroCfg.RestoreHelperImage, - VeleroServerDebugMode: veleroCfg.VeleroServerDebugMode, + Options: veleroInstallOptions, + RegistryCredentialFile: veleroCfg.RegistryCredentialFile, + RestoreHelperImage: veleroCfg.RestoreHelperImage, + VeleroServerDebugMode: veleroCfg.VeleroServerDebugMode, + WithoutDisableInformerCacheParam: veleroCfg.WithoutDisableInformerCacheParam, }) if err != nil { From ac4c9ed91933ff2dea04ad99040526767487d9e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wenkai=20Yin=28=E5=B0=B9=E6=96=87=E5=BC=80=29?= Date: Wed, 10 Jan 2024 13:36:51 +0800 Subject: [PATCH 09/32] Add changelog for v1.13.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add changelog for v1.13.0 Signed-off-by: Wenkai Yin(尹文开) --- CHANGELOG.md | 6 +- changelogs/CHANGELOG-1.13.md | 163 ++++++++++++++++++ changelogs/unreleased/5211-cleverhu | 1 - changelogs/unreleased/6475-nilesh-akhade | 1 - changelogs/unreleased/6635-27149chen | 1 - changelogs/unreleased/6637-Lyndon-Li | 1 - changelogs/unreleased/6649-sseago | 1 - changelogs/unreleased/6680-dzaninovic | 1 - changelogs/unreleased/6686-ywk253100 | 1 - changelogs/unreleased/6704-27149chen | 3 - changelogs/unreleased/6712-kaovilai | 1 - changelogs/unreleased/6715-nilesh-akhade | 1 - changelogs/unreleased/6723-sseago | 1 - changelogs/unreleased/6724-27149chen | 1 - changelogs/unreleased/6751-Lyndon-Li | 1 - changelogs/unreleased/6757-Lyndon-Li | 1 - changelogs/unreleased/6760-blackpiglet | 1 - changelogs/unreleased/6770-ywk253100 | 1 - changelogs/unreleased/6771-qiuming-best | 1 - changelogs/unreleased/6784-yanggangtony | 1 - changelogs/unreleased/6797-27149chen | 1 - changelogs/unreleased/6827-Lyndon-Li | 1 - changelogs/unreleased/6830-sseago | 1 - changelogs/unreleased/6833-Lyndon-Li | 1 - changelogs/unreleased/6838-yanggangtony | 1 - changelogs/unreleased/6863-qiuming-best | 1 - changelogs/unreleased/6872-Lyndon-Li | 1 - changelogs/unreleased/6875-Lyndon-Li | 1 - changelogs/unreleased/6883-ywk253100 | 1 - changelogs/unreleased/6885-Lyndon-Li | 1 - .../unreleased/6914-shubham-pampattiwar | 1 - changelogs/unreleased/6917-27149chen | 1 - changelogs/unreleased/6918-Ripolin | 1 - changelogs/unreleased/6923-reasonerjt | 1 - changelogs/unreleased/6926-Lyndon-Li | 1 - changelogs/unreleased/6938-yanggangtony | 1 - changelogs/unreleased/6946-Lyndon-Li | 1 - changelogs/unreleased/6947-0x113 | 1 - changelogs/unreleased/6950-Lyndon-Li | 1 - changelogs/unreleased/6958-blackpiglet | 1 - changelogs/unreleased/6962-blackpiglet | 1 - changelogs/unreleased/6968-blackpiglet | 1 - changelogs/unreleased/6976-Lyndon-Li | 1 - changelogs/unreleased/6989-blackpiglet | 1 - changelogs/unreleased/6990-Lyndon-Li | 1 - changelogs/unreleased/6995-kaovilai | 1 - changelogs/unreleased/7000-qiuming-best | 1 - changelogs/unreleased/7001-Lyndon-Li | 1 - changelogs/unreleased/7004-kaovilai | 1 - changelogs/unreleased/7011-Lyndon-Li | 1 - changelogs/unreleased/7022-allenxu404 | 1 - changelogs/unreleased/7026-blackpiglet | 1 - .../unreleased/7028-shubham-pampattiwar | 1 - changelogs/unreleased/7032-deefdragon | 1 - changelogs/unreleased/7034-ywk253100 | 1 - changelogs/unreleased/7038-Lyndon-Li | 1 - changelogs/unreleased/7041-blackpiglet | 1 - changelogs/unreleased/7046-kaovilai | 1 - changelogs/unreleased/7051-blackpiglet | 1 - changelogs/unreleased/7052-qiuming-best | 2 - changelogs/unreleased/7059-Lyndon-Li | 1 - changelogs/unreleased/7061-blackpiglet | 1 - changelogs/unreleased/7069-27149chen | 1 - changelogs/unreleased/7070-blackpiglet | 1 - changelogs/unreleased/7072-ywk253100 | 1 - changelogs/unreleased/7077-Lyndon-Li | 1 - changelogs/unreleased/7081-ywk253100 | 1 - changelogs/unreleased/7095-Lyndon-Li | 1 - changelogs/unreleased/7096-Lyndon-Li | 1 - changelogs/unreleased/7100-blackpiglet | 1 - changelogs/unreleased/7102-Lyndon-Li | 1 - changelogs/unreleased/7115-reasonerjt | 1 - changelogs/unreleased/7117-allenxu404 | 1 - changelogs/unreleased/7125-Lyndon-Li | 1 - changelogs/unreleased/7130-qiuming-best | 1 - changelogs/unreleased/7138-blackpiglet | 1 - changelogs/unreleased/7141-qiuming-best | 1 - changelogs/unreleased/7143-reasonerjt | 1 - changelogs/unreleased/7150-Lyndon-Li | 1 - changelogs/unreleased/7151-blackpiglet | 1 - changelogs/unreleased/7152-reasonerjt | 1 - changelogs/unreleased/7153-allenxu404 | 1 - changelogs/unreleased/7161-Lyndon-Li | 1 - changelogs/unreleased/7169-kaovilai | 1 - changelogs/unreleased/7175-blackpiglet | 1 - changelogs/unreleased/7184-blackpiglet | 2 - changelogs/unreleased/7201-Lyndon-Li | 1 - changelogs/unreleased/7229-allenxu404 | 1 - changelogs/unreleased/7238-allenxu404 | 1 - changelogs/unreleased/7245-Lyndon-Li | 1 - changelogs/unreleased/7274-reasonerjt | 1 - changelogs/unreleased/7282-Lyndon-Li | 1 - changelogs/unreleased/7294-ywk253100 | 1 - 93 files changed, 168 insertions(+), 96 deletions(-) create mode 100644 changelogs/CHANGELOG-1.13.md delete mode 100644 changelogs/unreleased/5211-cleverhu delete mode 100644 changelogs/unreleased/6475-nilesh-akhade delete mode 100644 changelogs/unreleased/6635-27149chen delete mode 100644 changelogs/unreleased/6637-Lyndon-Li delete mode 100644 changelogs/unreleased/6649-sseago delete mode 100644 changelogs/unreleased/6680-dzaninovic delete mode 100644 changelogs/unreleased/6686-ywk253100 delete mode 100644 changelogs/unreleased/6704-27149chen delete mode 100644 changelogs/unreleased/6712-kaovilai delete mode 100644 changelogs/unreleased/6715-nilesh-akhade delete mode 100644 changelogs/unreleased/6723-sseago delete mode 100644 changelogs/unreleased/6724-27149chen delete mode 100644 changelogs/unreleased/6751-Lyndon-Li delete mode 100644 changelogs/unreleased/6757-Lyndon-Li delete mode 100644 changelogs/unreleased/6760-blackpiglet delete mode 100644 changelogs/unreleased/6770-ywk253100 delete mode 100644 changelogs/unreleased/6771-qiuming-best delete mode 100644 changelogs/unreleased/6784-yanggangtony delete mode 100644 changelogs/unreleased/6797-27149chen delete mode 100644 changelogs/unreleased/6827-Lyndon-Li delete mode 100644 changelogs/unreleased/6830-sseago delete mode 100644 changelogs/unreleased/6833-Lyndon-Li delete mode 100644 changelogs/unreleased/6838-yanggangtony delete mode 100644 changelogs/unreleased/6863-qiuming-best delete mode 100644 changelogs/unreleased/6872-Lyndon-Li delete mode 100644 changelogs/unreleased/6875-Lyndon-Li delete mode 100644 changelogs/unreleased/6883-ywk253100 delete mode 100644 changelogs/unreleased/6885-Lyndon-Li delete mode 100644 changelogs/unreleased/6914-shubham-pampattiwar delete mode 100644 changelogs/unreleased/6917-27149chen delete mode 100644 changelogs/unreleased/6918-Ripolin delete mode 100644 changelogs/unreleased/6923-reasonerjt delete mode 100644 changelogs/unreleased/6926-Lyndon-Li delete mode 100644 changelogs/unreleased/6938-yanggangtony delete mode 100644 changelogs/unreleased/6946-Lyndon-Li delete mode 100644 changelogs/unreleased/6947-0x113 delete mode 100644 changelogs/unreleased/6950-Lyndon-Li delete mode 100644 changelogs/unreleased/6958-blackpiglet delete mode 100644 changelogs/unreleased/6962-blackpiglet delete mode 100644 changelogs/unreleased/6968-blackpiglet delete mode 100644 changelogs/unreleased/6976-Lyndon-Li delete mode 100644 changelogs/unreleased/6989-blackpiglet delete mode 100644 changelogs/unreleased/6990-Lyndon-Li delete mode 100644 changelogs/unreleased/6995-kaovilai delete mode 100644 changelogs/unreleased/7000-qiuming-best delete mode 100644 changelogs/unreleased/7001-Lyndon-Li delete mode 100644 changelogs/unreleased/7004-kaovilai delete mode 100644 changelogs/unreleased/7011-Lyndon-Li delete mode 100644 changelogs/unreleased/7022-allenxu404 delete mode 100644 changelogs/unreleased/7026-blackpiglet delete mode 100644 changelogs/unreleased/7028-shubham-pampattiwar delete mode 100644 changelogs/unreleased/7032-deefdragon delete mode 100644 changelogs/unreleased/7034-ywk253100 delete mode 100644 changelogs/unreleased/7038-Lyndon-Li delete mode 100644 changelogs/unreleased/7041-blackpiglet delete mode 100644 changelogs/unreleased/7046-kaovilai delete mode 100644 changelogs/unreleased/7051-blackpiglet delete mode 100644 changelogs/unreleased/7052-qiuming-best delete mode 100644 changelogs/unreleased/7059-Lyndon-Li delete mode 100644 changelogs/unreleased/7061-blackpiglet delete mode 100644 changelogs/unreleased/7069-27149chen delete mode 100644 changelogs/unreleased/7070-blackpiglet delete mode 100644 changelogs/unreleased/7072-ywk253100 delete mode 100644 changelogs/unreleased/7077-Lyndon-Li delete mode 100644 changelogs/unreleased/7081-ywk253100 delete mode 100644 changelogs/unreleased/7095-Lyndon-Li delete mode 100644 changelogs/unreleased/7096-Lyndon-Li delete mode 100644 changelogs/unreleased/7100-blackpiglet delete mode 100644 changelogs/unreleased/7102-Lyndon-Li delete mode 100644 changelogs/unreleased/7115-reasonerjt delete mode 100644 changelogs/unreleased/7117-allenxu404 delete mode 100644 changelogs/unreleased/7125-Lyndon-Li delete mode 100644 changelogs/unreleased/7130-qiuming-best delete mode 100644 changelogs/unreleased/7138-blackpiglet delete mode 100644 changelogs/unreleased/7141-qiuming-best delete mode 100644 changelogs/unreleased/7143-reasonerjt delete mode 100644 changelogs/unreleased/7150-Lyndon-Li delete mode 100644 changelogs/unreleased/7151-blackpiglet delete mode 100644 changelogs/unreleased/7152-reasonerjt delete mode 100644 changelogs/unreleased/7153-allenxu404 delete mode 100644 changelogs/unreleased/7161-Lyndon-Li delete mode 100644 changelogs/unreleased/7169-kaovilai delete mode 100644 changelogs/unreleased/7175-blackpiglet delete mode 100644 changelogs/unreleased/7184-blackpiglet delete mode 100644 changelogs/unreleased/7201-Lyndon-Li delete mode 100644 changelogs/unreleased/7229-allenxu404 delete mode 100644 changelogs/unreleased/7238-allenxu404 delete mode 100644 changelogs/unreleased/7245-Lyndon-Li delete mode 100644 changelogs/unreleased/7274-reasonerjt delete mode 100644 changelogs/unreleased/7282-Lyndon-Li delete mode 100644 changelogs/unreleased/7294-ywk253100 diff --git a/CHANGELOG.md b/CHANGELOG.md index eeeeb0810..973bf8e53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,9 @@ ## Current release: - * [CHANGELOG-1.11.md][21] + * [CHANGELOG-1.13.md][23] ## Older releases: + * [CHANGELOG-1.12.md][22] + * [CHANGELOG-1.11.md][21] * [CHANGELOG-1.10.md][20] * [CHANGELOG-1.9.md][19] * [CHANGELOG-1.8.md][18] @@ -24,6 +26,8 @@ * [CHANGELOG-0.3.md][1] +[23]: https://github.com/vmware-tanzu/velero/blob/main/changelogs/CHANGELOG-1.13.md +[22]: https://github.com/vmware-tanzu/velero/blob/main/changelogs/CHANGELOG-1.12.md [21]: https://github.com/vmware-tanzu/velero/blob/main/changelogs/CHANGELOG-1.11.md [20]: https://github.com/vmware-tanzu/velero/blob/main/changelogs/CHANGELOG-1.10.md [19]: https://github.com/vmware-tanzu/velero/blob/main/changelogs/CHANGELOG-1.9.md diff --git a/changelogs/CHANGELOG-1.13.md b/changelogs/CHANGELOG-1.13.md new file mode 100644 index 000000000..13ac4b257 --- /dev/null +++ b/changelogs/CHANGELOG-1.13.md @@ -0,0 +1,163 @@ +## v1.13 +### 2024-01-10 + +### Download +https://github.com/vmware-tanzu/velero/releases/tag/v1.13.0 + +### Container Image +`velero/velero:v1.13.0` + +### Documentation +https://velero.io/docs/v1.13/ + +### Upgrading +https://velero.io/docs/v1.13/upgrade-to-1.13/ + +### Highlights + +#### Resource Modifier Enhancement +Velero introduced the Resource Modifiers in v1.12.0. This feature allows users to specify a ConfigMap with a set of rules to modify the resources during restoration. However, only the JSON Patch is supported when creating the rules, and JSON Patch has some limitations, which cannot cover all use cases. In v1.13.0, Velero adds new support for JSON Merge Patch and Strategic Merge Patch, which provide more power and flexibility and allow users to use the same ConfigMap to apply patches on the resources. More design details can be found in [Support JSON Merge Patch and Strategic Merge Patch in Resource Modifiers](https://github.com/vmware-tanzu/velero/blob/main/design/Implemented/merge-patch-and-strategic-in-resource-modifier.md) design. For instructions on how to use the feature, please refer to the [Resource Modifiers](https://velero.io/docs/v1.13/restore-resource-modifiers/) doc. + +#### Node-Agent Concurrency +Velero data movement activities from fs-backups and CSI snapshot data movements run in Velero node-agent, so may be hosted by every node in the cluster and consume resources (i.e. CPU, memory, network bandwidth) from there. With v1.13, users are allowed to configure how many data movement activities (a.k.a, loads) run in each node globally or by node, so that users can better leverage the performance of Velero data movement activities and the resource consumption in the cluster. For more information, check the [Node-Agent Concurrency](https://velero.io/docs/v1.13/node-agent-concurrency/) document. + +#### Parallel Files Upload Options +Velero now supports configurable options for parallel files upload when using Kopia uploader to do fs-backups or CSI snapshot data movements which makes speed up backup possible. +For more information, please check [Here](https://velero.io/docs/v1.13/backup-reference/#parallel-files-upload). + +#### Write Sparse Files Options +If using fs-restore or CSI snapshot data movements, it’s supported to write sparse files during restore. For more information, please check [Here](https://velero.io/docs/v1.13/restore-reference/#write-sparse-files). + +#### Backup Describe +In v1.13, the Backup Volume section is added to the velero backup describe command output. The backup Volumes section describes information for all the volumes included in the backup of various backup types, i.e. native snapshot, fs-backup, CSI snapshot, and CSI snapshot data movement. Particularly, the velero backup description now supports showing the information of CSI snapshot data movements, which is not supported in v1.12. + +Additionally, backup describe command will not check EnableCSI feature gate from client side, so if a backup has volumes with CSI snapshot or CSI snapshot data movement, backup describe command always shows the corresponding information in its output. + +#### Backup's new VolumeInfo metadata +Create a new metadata file in the backup repository's backup name sub-directory to store the backup-including PVC and PV information. The information includes the backing-up method of the PVC and PV data, snapshot information, and status. The VolumeInfo metadata file determines how the PV resource should be restored. The Velero downstream software can also use this metadata file to get a summary of the backup's volume data information. + +#### Enhancement for CSI Snapshot Data Movements when Velero Pod Restart +When performing backup and restore operations, enhancements have been implemented for Velero server pods or node agents to ensure that the current backup or restore process is not stuck or interrupted after restart due to certain exceptional circumstances. + +#### New status fields added to show hook execution details +Hook execution status is now included in the backup/restore CR status and displayed in the backup/restore describe command output. Specifically, it will show the number of hooks which attempted to execute under the HooksAttempted field and the number of hooks which failed to execute under the HooksFailed field. + +#### AWS SDK Bump Up +Bump up AWS SDK for Go to version 2, which offers significant performance improvements in CPU and memory utilization over version 1. + +#### Azure AD/Workload Identity Support +Azure AD/Workload Identity is the recommended approach to do the authentication with Azure services/AKS, Velero has introduced support for Azure AD/Workload Identity on the Velero Azure plugin side in previous releases, and in v1.13.0 Velero adds new support for Kopia operations(file system backup/data mover/etc.) with Azure AD/Workload Identity. + +#### Runtime and dependencies +To fix CVEs and keep pace with Golang, Velero made changes as follows: +* Bump Golang runtime to v1.21.6. +* Bump several dependent libraries to new versions. +* Bump Kopia to v0.15.0. + + +### Breaking changes +* Backup describe command: due to the backup describe output enhancement, some existing information (i.e. the output for native snapshot, CSI snapshot, and fs-backup) has been moved to the Backup Volumes section with some format changes. +* API type changes: changes the field [DataMoverConfig](https://github.com/vmware-tanzu/velero/blob/v1.13.0/pkg/apis/velero/v2alpha1/data_upload_types.go#L54) in DataUploadSpec from `*map[string][string]`` to `map[string]string` +* Velero install command: due to the issue [#7264](https://github.com/vmware-tanzu/velero/issues/7264), v1.13.0 introduces a break change that make the informer cache enabled by default to keep the actual behavior consistent with the helper message(the informer cache is disabled by default before the change). + + +### Limitations/Known issues +* The backup's VolumeInfo metadata doesn't have the information updated in the async operations. This function could be supported in v1.14 release. + +### Deprecation announcement +* The generated k8s clients, informers, and listers are deprecated in the Velero v1.13 release. They are put in the Velero repository's pkg/generated directory. According to the n+2 supporting policy, the deprecated are kept for two more releases. The pkg/generated directory should be deleted in the v1.15 release. +* After the backup VolumeInfo metadata file is added to the backup, Velero decides how to restore the PV resource according to the VolumeInfo content. To support the backup generated by the older version of Velero, the old logic is also kept. The support for the backup without the VolumeInfo metadata file will be kept for two releases. The support logic will be deleted in the v1.15 release. + +### All Changes + * Make "disable-informer-cache" option false(enabled) by default to keep it consistent with the help message (#7294, @ywk253100) + * Fix issue #6928, remove snapshot deletion timeout for PVB (#7282, @Lyndon-Li) + * Do not set "targetNamespace" to namespace items (#7274, @reasonerjt) + * Fix issue #7244. By the end of the upload, check the outstanding incomplete snapshots and delete them by calling ApplyRetentionPolicy (#7245, @Lyndon-Li) + * Adjust the newline output of resource list in restore describer (#7238, @allenxu404) + * Remove the redundant newline in backup describe output (#7229, @allenxu404) + * Fix issue #7189, data mover generic restore - don't assume the first volume as the restore volume (#7201, @Lyndon-Li) + * Update CSIVolumeSnapshotsCompleted in backup's status and the metric +during backup finalize stage according to async operations content. (#7184, @blackpiglet) + * Refactor DownloadRequest Stream function (#7175, @blackpiglet) + * Add `--skip-immediately` flag to schedule commands; `--schedule-skip-immediately` server and install (#7169, @kaovilai) + * Add node-agent concurrency doc and change the config name from dataPathConcurrency to loadCocurrency (#7161, @Lyndon-Li) + * Enhance hooks tracker by adding a returned error to record function (#7153, @allenxu404) + * Track the skipped PV when SnapshotVolumes set as false (#7152, @reasonerjt) + * Add more linters part 2. (#7151, @blackpiglet) + * Fix issue #7135, check pod status before checking node-agent pod status (#7150, @Lyndon-Li) + * Treat namespace as a regular restorable item (#7143, @reasonerjt) + * Allow sparse option for Kopia & Restic restore (#7141, @qiuming-best) + * Use VolumeInfo to help restore the PV. (#7138, @blackpiglet) + * Node agent restart enhancement (#7130, @qiuming-best) + * Fix issue #6695, add describe for data mover backups (#7125, @Lyndon-Li) + * Add hooks status to backup/restore CR (#7117, @allenxu404) + * Include plugin name in the error message by operations (#7115, @reasonerjt) + * Fix issue #7068, due to a behavior of CSI external snapshotter, manipulations of VS and VSC may not be handled in the same order inside external snapshotter as the API is called. So add a protection finalizer to ensure the order (#7102, @Lyndon-Li) + * Generate VolumeInfo for backup. (#7100, @blackpiglet) + * Fix issue #7094, fallback to full backup if previous snapshot is not found (#7096, @Lyndon-Li) + * Fix issue #7068, due to an behavior of CSI external snapshotter, manipulations of VS and VSC may not be handled in the same order inside external snapshotter as the API is called. So add a protection finalizer to ensure the order (#7095, @Lyndon-Li) + * Skip syncing the backup which doesn't contain backup metadata (#7081, @ywk253100) + * Fix issue #6693, partially fail restore if CSI snapshot is involved but CSI feature is not ready, i.e., CSI feature gate is not enabled or CSI plugin is not installed. (#7077, @Lyndon-Li) + * Truncate the credential file to avoid the change of secret content messing it up (#7072, @ywk253100) + * Add VolumeInfo metadata structures. (#7070, @blackpiglet) + * improve discoveryHelper.Refresh() in restore (#7069, @27149chen) + * Add DataUpload Result and CSI VolumeSnapshot check for restore PV. (#7061, @blackpiglet) + * Add the implementation for design #6950, configurable data path concurrency (#7059, @Lyndon-Li) + * Make data mover fail early (#7052, @qiuming-best) + * Remove dependency of generated client part 3. (#7051, @blackpiglet) + * Update Backup.Status.CSIVolumeSnapshotsCompleted during finalize (#7046, @kaovilai) + * Remove the Velero generated client. (#7041, @blackpiglet) + * Fix issue #7027, data mover backup exposer should not assume the first volume as the backup volume in backup pod (#7038, @Lyndon-Li) + * Read information from the credential specified by BSL (#7034, @ywk253100) + * Fix #6857. Added check for matching Owner References when synchronizing backups, removing references that are not found/have mismatched uid. (#7032, @deefdragon) + * Add description markers for dataupload and datadownload CRDs (#7028, @shubham-pampattiwar) + * Add HealthCheckNodePort deletion logic for Service restore. (#7026, @blackpiglet) + * Fix inconsistent behavior of Backup and Restore hook execution (#7022, @allenxu404) + * Fix #6964. Don't use csiSnapshotTimeout (10 min) for waiting snapshot to readyToUse for data mover, so as to make the behavior complied with CSI snapshot backup (#7011, @Lyndon-Li) + * restore: Use warning when Create IsAlreadyExist and Get error (#7004, @kaovilai) + * Bump kopia to 0.15.0 (#7001, @Lyndon-Li) + * Make Kopia file parallelism configurable (#7000, @qiuming-best) + * Fix unified repository (kopia) s3 credentials profile selection (#6995, @kaovilai) + * Fix #6988, always get region from BSL if it is not empty (#6990, @Lyndon-Li) + * Limit PVC block mode logic to non-Windows platform. (#6989, @blackpiglet) + * It is a valid case that the Status.RestoreSize field in VolumeSnapshot is not set, if so, get the volume size from the source PVC to create the backup PVC (#6976, @Lyndon-Li) + * Check whether the action is a CSI action and whether CSI feature is enabled, before executing the action. (#6968, @blackpiglet) + * Add the PV backup information design document. (#6962, @blackpiglet) + * Change controller-runtime List option from MatchingFields to ListOptions (#6958, @blackpiglet) + * Add the design for node-agent concurrency (#6950, @Lyndon-Li) + * Import auth provider plugins (#6947, @0x113) + * Fix #6668, add a limitation for file system restore parallelism with other types of restores (CSI snapshot restore, CSI snapshot movement restore) (#6946, @Lyndon-Li) + * Add MSI Support for Azure plugin. (#6938, @yanggangtony) + * Partially fix #6734, guide Kubernetes' scheduler to spread backup pods evenly across nodes as much as possible, so that data mover backup could achieve better parallelism (#6926, @Lyndon-Li) + * Bump up aws sdk to aws-sdk-go-v2 (#6923, @reasonerjt) + * Optional check if targeted container is ready before executing a hook (#6918, @Ripolin) + * Support JSON Merge Patch and Strategic Merge Patch in Resource Modifiers (#6917, @27149chen) + * Fix issue 6913: Velero Built-in Datamover: Backup stucks in phase WaitingForPluginOperations when Node Agent pod gets restarted (#6914, @shubham-pampattiwar) + * Set ParallelUploadAboveSize as MaxInt64 and flush repo after setting up policy so that policy is retrieved correctly by TreeForSource (#6885, @Lyndon-Li) + * Replace the base image with paketobuildpacks image (#6883, @ywk253100) + * Fix issue #6859, move plugin depending podvolume functions to util pkg, so as to remove the dependencies to unnecessary repository packages like kopia, azure, etc. (#6875, @Lyndon-Li) + * Fix #6861. Only Restic path requires repoIdentifier, so for non-restic path, set the repoIdentifier fields as empty in PVB and PVR and also remove the RepoIdentifier column in the get output of PVBs and PVRs (#6872, @Lyndon-Li) + * Add volume types filter in resource policies (#6863, @qiuming-best) + * change the metrics backup_attempt_total default value to 1. (#6838, @yanggangtony) + * Bump kopia to v0.14 (#6833, @Lyndon-Li) + * Retry failed create when using generateName (#6830, @sseago) + * Fix issue #6786, always delete VSC regardless of the deletion policy (#6827, @Lyndon-Li) + * Proposal to support JSON Merge Patch and Strategic Merge Patch in Resource Modifiers (#6797, @27149chen) + * Fix the node-agent missing metrics-address defines. (#6784, @yanggangtony) + * Fix default BSL setting not work (#6771, @qiuming-best) + * Update restore controller logic for restore deletion (#6770, @ywk253100) + * Fix #6752: add namespace exclude check. (#6760, @blackpiglet) + * Fix issue #6753, remove the check for read-only BSL in restore async operation controller since Velero cannot fully support read-only mode BSL in restore at present (#6757, @Lyndon-Li) + * Fix issue #6647, add the --default-snapshot-move-data parameter to Velero install, so that users don't need to specify --snapshot-move-data per backup when they want to move snapshot data for all backups (#6751, @Lyndon-Li) + * Use old(origin) namespace in resource modifier conditions in case namespace may change during restore (#6724, @27149chen) + * Perf improvements for existing resource restore (#6723, @sseago) + * Remove schedule-related metrics on schedule delete (#6715, @nilesh-akhade) + * Kubernetes 1.27 new job label batch.kubernetes.io/controller-uid are deleted during restore per https://github.com/kubernetes/kubernetes/pull/114930 (#6712, @kaovilai) + * This pr made some improvements in Resource Modifiers: 1. add label selector 2. change the field name from groupKind to groupResource (#6704, @27149chen) + * Make Kopia support Azure AD (#6686, @ywk253100) + * Add support for block volumes with Kopia (#6680, @dzaninovic) + * Delete PartiallyFailed orphaned backups as well as Completed ones (#6649, @sseago) + * Add CSI snapshot data movement doc (#6637, @Lyndon-Li) + * Fixes #6636, skip subresource in resource discovery (#6635, @27149chen) + * Add `orLabelSelectors` for backup, restore commands (#6475, @nilesh-akhade) + * fix run preHook and postHook on completed pods (#5211, @cleverhu) \ No newline at end of file diff --git a/changelogs/unreleased/5211-cleverhu b/changelogs/unreleased/5211-cleverhu deleted file mode 100644 index 1ed39117c..000000000 --- a/changelogs/unreleased/5211-cleverhu +++ /dev/null @@ -1 +0,0 @@ -fix run preHook and postHook on completed pods \ No newline at end of file diff --git a/changelogs/unreleased/6475-nilesh-akhade b/changelogs/unreleased/6475-nilesh-akhade deleted file mode 100644 index 3db273f29..000000000 --- a/changelogs/unreleased/6475-nilesh-akhade +++ /dev/null @@ -1 +0,0 @@ -Add `orLabelSelectors` for backup, restore commands diff --git a/changelogs/unreleased/6635-27149chen b/changelogs/unreleased/6635-27149chen deleted file mode 100644 index 229d6e075..000000000 --- a/changelogs/unreleased/6635-27149chen +++ /dev/null @@ -1 +0,0 @@ -Fixes #6636, skip subresource in resource discovery \ No newline at end of file diff --git a/changelogs/unreleased/6637-Lyndon-Li b/changelogs/unreleased/6637-Lyndon-Li deleted file mode 100644 index b816b976b..000000000 --- a/changelogs/unreleased/6637-Lyndon-Li +++ /dev/null @@ -1 +0,0 @@ -Add CSI snapshot data movement doc \ No newline at end of file diff --git a/changelogs/unreleased/6649-sseago b/changelogs/unreleased/6649-sseago deleted file mode 100644 index 8c3b20751..000000000 --- a/changelogs/unreleased/6649-sseago +++ /dev/null @@ -1 +0,0 @@ -Delete PartiallyFailed orphaned backups as well as Completed ones diff --git a/changelogs/unreleased/6680-dzaninovic b/changelogs/unreleased/6680-dzaninovic deleted file mode 100644 index b4d735d14..000000000 --- a/changelogs/unreleased/6680-dzaninovic +++ /dev/null @@ -1 +0,0 @@ -Add support for block volumes with Kopia \ No newline at end of file diff --git a/changelogs/unreleased/6686-ywk253100 b/changelogs/unreleased/6686-ywk253100 deleted file mode 100644 index d2a79be6d..000000000 --- a/changelogs/unreleased/6686-ywk253100 +++ /dev/null @@ -1 +0,0 @@ -Make Kopia support Azure AD \ No newline at end of file diff --git a/changelogs/unreleased/6704-27149chen b/changelogs/unreleased/6704-27149chen deleted file mode 100644 index 62a9084cb..000000000 --- a/changelogs/unreleased/6704-27149chen +++ /dev/null @@ -1,3 +0,0 @@ -This pr made some improvements in Resource Modifiers: -1. add label selector -2. change the field name from groupKind to groupResource diff --git a/changelogs/unreleased/6712-kaovilai b/changelogs/unreleased/6712-kaovilai deleted file mode 100644 index e59718756..000000000 --- a/changelogs/unreleased/6712-kaovilai +++ /dev/null @@ -1 +0,0 @@ -Kubernetes 1.27 new job label batch.kubernetes.io/controller-uid are deleted during restore per https://github.com/kubernetes/kubernetes/pull/114930 \ No newline at end of file diff --git a/changelogs/unreleased/6715-nilesh-akhade b/changelogs/unreleased/6715-nilesh-akhade deleted file mode 100644 index 756432ad1..000000000 --- a/changelogs/unreleased/6715-nilesh-akhade +++ /dev/null @@ -1 +0,0 @@ -Remove schedule-related metrics on schedule delete \ No newline at end of file diff --git a/changelogs/unreleased/6723-sseago b/changelogs/unreleased/6723-sseago deleted file mode 100644 index c971d27dc..000000000 --- a/changelogs/unreleased/6723-sseago +++ /dev/null @@ -1 +0,0 @@ -Perf improvements for existing resource restore diff --git a/changelogs/unreleased/6724-27149chen b/changelogs/unreleased/6724-27149chen deleted file mode 100644 index dcc8962bc..000000000 --- a/changelogs/unreleased/6724-27149chen +++ /dev/null @@ -1 +0,0 @@ -Use old(origin) namespace in resource modifier conditions in case namespace may change during restore \ No newline at end of file diff --git a/changelogs/unreleased/6751-Lyndon-Li b/changelogs/unreleased/6751-Lyndon-Li deleted file mode 100644 index 6f614cb68..000000000 --- a/changelogs/unreleased/6751-Lyndon-Li +++ /dev/null @@ -1 +0,0 @@ -Fix issue #6647, add the --default-snapshot-move-data parameter to Velero install, so that users don't need to specify --snapshot-move-data per backup when they want to move snapshot data for all backups \ No newline at end of file diff --git a/changelogs/unreleased/6757-Lyndon-Li b/changelogs/unreleased/6757-Lyndon-Li deleted file mode 100644 index 1e095ee67..000000000 --- a/changelogs/unreleased/6757-Lyndon-Li +++ /dev/null @@ -1 +0,0 @@ -Fix issue #6753, remove the check for read-only BSL in restore async operation controller since Velero cannot fully support read-only mode BSL in restore at present \ No newline at end of file diff --git a/changelogs/unreleased/6760-blackpiglet b/changelogs/unreleased/6760-blackpiglet deleted file mode 100644 index db9b5a118..000000000 --- a/changelogs/unreleased/6760-blackpiglet +++ /dev/null @@ -1 +0,0 @@ -Fix #6752: add namespace exclude check. \ No newline at end of file diff --git a/changelogs/unreleased/6770-ywk253100 b/changelogs/unreleased/6770-ywk253100 deleted file mode 100644 index 6e55ffcd4..000000000 --- a/changelogs/unreleased/6770-ywk253100 +++ /dev/null @@ -1 +0,0 @@ -Update restore controller logic for restore deletion \ No newline at end of file diff --git a/changelogs/unreleased/6771-qiuming-best b/changelogs/unreleased/6771-qiuming-best deleted file mode 100644 index 73c18f973..000000000 --- a/changelogs/unreleased/6771-qiuming-best +++ /dev/null @@ -1 +0,0 @@ -Fix default BSL setting not work diff --git a/changelogs/unreleased/6784-yanggangtony b/changelogs/unreleased/6784-yanggangtony deleted file mode 100644 index 8d63d94a1..000000000 --- a/changelogs/unreleased/6784-yanggangtony +++ /dev/null @@ -1 +0,0 @@ -Fix the node-agent missing metrics-address defines. \ No newline at end of file diff --git a/changelogs/unreleased/6797-27149chen b/changelogs/unreleased/6797-27149chen deleted file mode 100644 index 463017fc5..000000000 --- a/changelogs/unreleased/6797-27149chen +++ /dev/null @@ -1 +0,0 @@ -Proposal to support JSON Merge Patch and Strategic Merge Patch in Resource Modifiers \ No newline at end of file diff --git a/changelogs/unreleased/6827-Lyndon-Li b/changelogs/unreleased/6827-Lyndon-Li deleted file mode 100644 index 1aac387e0..000000000 --- a/changelogs/unreleased/6827-Lyndon-Li +++ /dev/null @@ -1 +0,0 @@ -Fix issue #6786, always delete VSC regardless of the deletion policy \ No newline at end of file diff --git a/changelogs/unreleased/6830-sseago b/changelogs/unreleased/6830-sseago deleted file mode 100644 index 2055df15a..000000000 --- a/changelogs/unreleased/6830-sseago +++ /dev/null @@ -1 +0,0 @@ -Retry failed create when using generateName diff --git a/changelogs/unreleased/6833-Lyndon-Li b/changelogs/unreleased/6833-Lyndon-Li deleted file mode 100644 index 9ec367314..000000000 --- a/changelogs/unreleased/6833-Lyndon-Li +++ /dev/null @@ -1 +0,0 @@ -Bump kopia to v0.14 \ No newline at end of file diff --git a/changelogs/unreleased/6838-yanggangtony b/changelogs/unreleased/6838-yanggangtony deleted file mode 100644 index 4f48f8fef..000000000 --- a/changelogs/unreleased/6838-yanggangtony +++ /dev/null @@ -1 +0,0 @@ -change the metrics backup_attempt_total default value to 1. \ No newline at end of file diff --git a/changelogs/unreleased/6863-qiuming-best b/changelogs/unreleased/6863-qiuming-best deleted file mode 100644 index 12e0d7356..000000000 --- a/changelogs/unreleased/6863-qiuming-best +++ /dev/null @@ -1 +0,0 @@ -Add volume types filter in resource policies diff --git a/changelogs/unreleased/6872-Lyndon-Li b/changelogs/unreleased/6872-Lyndon-Li deleted file mode 100644 index e5db5e6b4..000000000 --- a/changelogs/unreleased/6872-Lyndon-Li +++ /dev/null @@ -1 +0,0 @@ -Fix #6861. Only Restic path requires repoIdentifier, so for non-restic path, set the repoIdentifier fields as empty in PVB and PVR and also remove the RepoIdentifier column in the get output of PVBs and PVRs \ No newline at end of file diff --git a/changelogs/unreleased/6875-Lyndon-Li b/changelogs/unreleased/6875-Lyndon-Li deleted file mode 100644 index 8d11bca2c..000000000 --- a/changelogs/unreleased/6875-Lyndon-Li +++ /dev/null @@ -1 +0,0 @@ -Fix issue #6859, move plugin depending podvolume functions to util pkg, so as to remove the dependencies to unnecessary repository packages like kopia, azure, etc. \ No newline at end of file diff --git a/changelogs/unreleased/6883-ywk253100 b/changelogs/unreleased/6883-ywk253100 deleted file mode 100644 index bc8d80b92..000000000 --- a/changelogs/unreleased/6883-ywk253100 +++ /dev/null @@ -1 +0,0 @@ -Replace the base image with paketobuildpacks image \ No newline at end of file diff --git a/changelogs/unreleased/6885-Lyndon-Li b/changelogs/unreleased/6885-Lyndon-Li deleted file mode 100644 index f879897c0..000000000 --- a/changelogs/unreleased/6885-Lyndon-Li +++ /dev/null @@ -1 +0,0 @@ -Set ParallelUploadAboveSize as MaxInt64 and flush repo after setting up policy so that policy is retrieved correctly by TreeForSource \ No newline at end of file diff --git a/changelogs/unreleased/6914-shubham-pampattiwar b/changelogs/unreleased/6914-shubham-pampattiwar deleted file mode 100644 index c95688b39..000000000 --- a/changelogs/unreleased/6914-shubham-pampattiwar +++ /dev/null @@ -1 +0,0 @@ -Fix issue 6913: Velero Built-in Datamover: Backup stucks in phase WaitingForPluginOperations when Node Agent pod gets restarted \ No newline at end of file diff --git a/changelogs/unreleased/6917-27149chen b/changelogs/unreleased/6917-27149chen deleted file mode 100644 index 94648eaa4..000000000 --- a/changelogs/unreleased/6917-27149chen +++ /dev/null @@ -1 +0,0 @@ -Support JSON Merge Patch and Strategic Merge Patch in Resource Modifiers \ No newline at end of file diff --git a/changelogs/unreleased/6918-Ripolin b/changelogs/unreleased/6918-Ripolin deleted file mode 100644 index 6bd21f8df..000000000 --- a/changelogs/unreleased/6918-Ripolin +++ /dev/null @@ -1 +0,0 @@ -Optional check if targeted container is ready before executing a hook \ No newline at end of file diff --git a/changelogs/unreleased/6923-reasonerjt b/changelogs/unreleased/6923-reasonerjt deleted file mode 100644 index 2a9c2aabd..000000000 --- a/changelogs/unreleased/6923-reasonerjt +++ /dev/null @@ -1 +0,0 @@ -Bump up aws sdk to aws-sdk-go-v2 diff --git a/changelogs/unreleased/6926-Lyndon-Li b/changelogs/unreleased/6926-Lyndon-Li deleted file mode 100644 index b14c4f3cc..000000000 --- a/changelogs/unreleased/6926-Lyndon-Li +++ /dev/null @@ -1 +0,0 @@ -Partially fix #6734, guide Kubernetes' scheduler to spread backup pods evenly across nodes as much as possible, so that data mover backup could achieve better parallelism \ No newline at end of file diff --git a/changelogs/unreleased/6938-yanggangtony b/changelogs/unreleased/6938-yanggangtony deleted file mode 100644 index a101231df..000000000 --- a/changelogs/unreleased/6938-yanggangtony +++ /dev/null @@ -1 +0,0 @@ -Add MSI Support for Azure plugin. diff --git a/changelogs/unreleased/6946-Lyndon-Li b/changelogs/unreleased/6946-Lyndon-Li deleted file mode 100644 index a39643953..000000000 --- a/changelogs/unreleased/6946-Lyndon-Li +++ /dev/null @@ -1 +0,0 @@ -Fix #6668, add a limitation for file system restore parallelism with other types of restores (CSI snapshot restore, CSI snapshot movement restore) \ No newline at end of file diff --git a/changelogs/unreleased/6947-0x113 b/changelogs/unreleased/6947-0x113 deleted file mode 100644 index 1cf76eb51..000000000 --- a/changelogs/unreleased/6947-0x113 +++ /dev/null @@ -1 +0,0 @@ -Import auth provider plugins diff --git a/changelogs/unreleased/6950-Lyndon-Li b/changelogs/unreleased/6950-Lyndon-Li deleted file mode 100644 index a1d0b5983..000000000 --- a/changelogs/unreleased/6950-Lyndon-Li +++ /dev/null @@ -1 +0,0 @@ -Add the design for node-agent concurrency \ No newline at end of file diff --git a/changelogs/unreleased/6958-blackpiglet b/changelogs/unreleased/6958-blackpiglet deleted file mode 100644 index 7b402258f..000000000 --- a/changelogs/unreleased/6958-blackpiglet +++ /dev/null @@ -1 +0,0 @@ -Change controller-runtime List option from MatchingFields to ListOptions \ No newline at end of file diff --git a/changelogs/unreleased/6962-blackpiglet b/changelogs/unreleased/6962-blackpiglet deleted file mode 100644 index 1159efe9b..000000000 --- a/changelogs/unreleased/6962-blackpiglet +++ /dev/null @@ -1 +0,0 @@ -Add the PV backup information design document. \ No newline at end of file diff --git a/changelogs/unreleased/6968-blackpiglet b/changelogs/unreleased/6968-blackpiglet deleted file mode 100644 index 9a3461f24..000000000 --- a/changelogs/unreleased/6968-blackpiglet +++ /dev/null @@ -1 +0,0 @@ -Check whether the action is a CSI action and whether CSI feature is enabled, before executing the action. \ No newline at end of file diff --git a/changelogs/unreleased/6976-Lyndon-Li b/changelogs/unreleased/6976-Lyndon-Li deleted file mode 100644 index 06cf694ad..000000000 --- a/changelogs/unreleased/6976-Lyndon-Li +++ /dev/null @@ -1 +0,0 @@ -It is a valid case that the Status.RestoreSize field in VolumeSnapshot is not set, if so, get the volume size from the source PVC to create the backup PVC \ No newline at end of file diff --git a/changelogs/unreleased/6989-blackpiglet b/changelogs/unreleased/6989-blackpiglet deleted file mode 100644 index 5dfb8f1cf..000000000 --- a/changelogs/unreleased/6989-blackpiglet +++ /dev/null @@ -1 +0,0 @@ -Limit PVC block mode logic to non-Windows platform. \ No newline at end of file diff --git a/changelogs/unreleased/6990-Lyndon-Li b/changelogs/unreleased/6990-Lyndon-Li deleted file mode 100644 index 6f79e1c9d..000000000 --- a/changelogs/unreleased/6990-Lyndon-Li +++ /dev/null @@ -1 +0,0 @@ -Fix #6988, always get region from BSL if it is not empty \ No newline at end of file diff --git a/changelogs/unreleased/6995-kaovilai b/changelogs/unreleased/6995-kaovilai deleted file mode 100644 index ef3ab9406..000000000 --- a/changelogs/unreleased/6995-kaovilai +++ /dev/null @@ -1 +0,0 @@ -Fix unified repository (kopia) s3 credentials profile selection \ No newline at end of file diff --git a/changelogs/unreleased/7000-qiuming-best b/changelogs/unreleased/7000-qiuming-best deleted file mode 100644 index 61b3ade5d..000000000 --- a/changelogs/unreleased/7000-qiuming-best +++ /dev/null @@ -1 +0,0 @@ -Make Kopia file parallelism configurable diff --git a/changelogs/unreleased/7001-Lyndon-Li b/changelogs/unreleased/7001-Lyndon-Li deleted file mode 100644 index c4f7c3bed..000000000 --- a/changelogs/unreleased/7001-Lyndon-Li +++ /dev/null @@ -1 +0,0 @@ -Bump kopia to 0.15.0 \ No newline at end of file diff --git a/changelogs/unreleased/7004-kaovilai b/changelogs/unreleased/7004-kaovilai deleted file mode 100644 index 3b85df196..000000000 --- a/changelogs/unreleased/7004-kaovilai +++ /dev/null @@ -1 +0,0 @@ -restore: Use warning when Create IsAlreadyExist and Get error \ No newline at end of file diff --git a/changelogs/unreleased/7011-Lyndon-Li b/changelogs/unreleased/7011-Lyndon-Li deleted file mode 100644 index 81c69ad5a..000000000 --- a/changelogs/unreleased/7011-Lyndon-Li +++ /dev/null @@ -1 +0,0 @@ -Fix #6964. Don't use csiSnapshotTimeout (10 min) for waiting snapshot to readyToUse for data mover, so as to make the behavior complied with CSI snapshot backup \ No newline at end of file diff --git a/changelogs/unreleased/7022-allenxu404 b/changelogs/unreleased/7022-allenxu404 deleted file mode 100644 index 139a5e842..000000000 --- a/changelogs/unreleased/7022-allenxu404 +++ /dev/null @@ -1 +0,0 @@ -Fix inconsistent behavior of Backup and Restore hook execution \ No newline at end of file diff --git a/changelogs/unreleased/7026-blackpiglet b/changelogs/unreleased/7026-blackpiglet deleted file mode 100644 index 0decfabf7..000000000 --- a/changelogs/unreleased/7026-blackpiglet +++ /dev/null @@ -1 +0,0 @@ -Add HealthCheckNodePort deletion logic for Service restore. \ No newline at end of file diff --git a/changelogs/unreleased/7028-shubham-pampattiwar b/changelogs/unreleased/7028-shubham-pampattiwar deleted file mode 100644 index 4ed72fa6d..000000000 --- a/changelogs/unreleased/7028-shubham-pampattiwar +++ /dev/null @@ -1 +0,0 @@ -Add description markers for dataupload and datadownload CRDs \ No newline at end of file diff --git a/changelogs/unreleased/7032-deefdragon b/changelogs/unreleased/7032-deefdragon deleted file mode 100644 index 710ed3a11..000000000 --- a/changelogs/unreleased/7032-deefdragon +++ /dev/null @@ -1 +0,0 @@ -Fix #6857. Added check for matching Owner References when synchronizing backups, removing references that are not found/have mismatched uid. \ No newline at end of file diff --git a/changelogs/unreleased/7034-ywk253100 b/changelogs/unreleased/7034-ywk253100 deleted file mode 100644 index b4ce2bbab..000000000 --- a/changelogs/unreleased/7034-ywk253100 +++ /dev/null @@ -1 +0,0 @@ -Read information from the credential specified by BSL \ No newline at end of file diff --git a/changelogs/unreleased/7038-Lyndon-Li b/changelogs/unreleased/7038-Lyndon-Li deleted file mode 100644 index f2a3a9ae2..000000000 --- a/changelogs/unreleased/7038-Lyndon-Li +++ /dev/null @@ -1 +0,0 @@ -Fix issue #7027, data mover backup exposer should not assume the first volume as the backup volume in backup pod \ No newline at end of file diff --git a/changelogs/unreleased/7041-blackpiglet b/changelogs/unreleased/7041-blackpiglet deleted file mode 100644 index b347ce752..000000000 --- a/changelogs/unreleased/7041-blackpiglet +++ /dev/null @@ -1 +0,0 @@ -Remove the Velero generated client. \ No newline at end of file diff --git a/changelogs/unreleased/7046-kaovilai b/changelogs/unreleased/7046-kaovilai deleted file mode 100644 index abf627039..000000000 --- a/changelogs/unreleased/7046-kaovilai +++ /dev/null @@ -1 +0,0 @@ -Update Backup.Status.CSIVolumeSnapshotsCompleted during finalize \ No newline at end of file diff --git a/changelogs/unreleased/7051-blackpiglet b/changelogs/unreleased/7051-blackpiglet deleted file mode 100644 index 5b930f77f..000000000 --- a/changelogs/unreleased/7051-blackpiglet +++ /dev/null @@ -1 +0,0 @@ -Remove dependency of generated client part 3. \ No newline at end of file diff --git a/changelogs/unreleased/7052-qiuming-best b/changelogs/unreleased/7052-qiuming-best deleted file mode 100644 index e1829fd74..000000000 --- a/changelogs/unreleased/7052-qiuming-best +++ /dev/null @@ -1,2 +0,0 @@ -Make data mover fail early - diff --git a/changelogs/unreleased/7059-Lyndon-Li b/changelogs/unreleased/7059-Lyndon-Li deleted file mode 100644 index 77b3a1765..000000000 --- a/changelogs/unreleased/7059-Lyndon-Li +++ /dev/null @@ -1 +0,0 @@ -Add the implementation for design #6950, configurable data path concurrency \ No newline at end of file diff --git a/changelogs/unreleased/7061-blackpiglet b/changelogs/unreleased/7061-blackpiglet deleted file mode 100644 index ac965ed13..000000000 --- a/changelogs/unreleased/7061-blackpiglet +++ /dev/null @@ -1 +0,0 @@ -Add DataUpload Result and CSI VolumeSnapshot check for restore PV. \ No newline at end of file diff --git a/changelogs/unreleased/7069-27149chen b/changelogs/unreleased/7069-27149chen deleted file mode 100644 index 243596d4a..000000000 --- a/changelogs/unreleased/7069-27149chen +++ /dev/null @@ -1 +0,0 @@ -improve discoveryHelper.Refresh() in restore \ No newline at end of file diff --git a/changelogs/unreleased/7070-blackpiglet b/changelogs/unreleased/7070-blackpiglet deleted file mode 100644 index 75843b730..000000000 --- a/changelogs/unreleased/7070-blackpiglet +++ /dev/null @@ -1 +0,0 @@ -Add VolumeInfo metadata structures. \ No newline at end of file diff --git a/changelogs/unreleased/7072-ywk253100 b/changelogs/unreleased/7072-ywk253100 deleted file mode 100644 index 2a6faffe3..000000000 --- a/changelogs/unreleased/7072-ywk253100 +++ /dev/null @@ -1 +0,0 @@ -Truncate the credential file to avoid the change of secret content messing it up \ No newline at end of file diff --git a/changelogs/unreleased/7077-Lyndon-Li b/changelogs/unreleased/7077-Lyndon-Li deleted file mode 100644 index 802609edf..000000000 --- a/changelogs/unreleased/7077-Lyndon-Li +++ /dev/null @@ -1 +0,0 @@ -Fix issue #6693, partially fail restore if CSI snapshot is involved but CSI feature is not ready, i.e., CSI feature gate is not enabled or CSI plugin is not installed. \ No newline at end of file diff --git a/changelogs/unreleased/7081-ywk253100 b/changelogs/unreleased/7081-ywk253100 deleted file mode 100644 index bc142a316..000000000 --- a/changelogs/unreleased/7081-ywk253100 +++ /dev/null @@ -1 +0,0 @@ -Skip syncing the backup which doesn't contain backup metadata \ No newline at end of file diff --git a/changelogs/unreleased/7095-Lyndon-Li b/changelogs/unreleased/7095-Lyndon-Li deleted file mode 100644 index e3a11801d..000000000 --- a/changelogs/unreleased/7095-Lyndon-Li +++ /dev/null @@ -1 +0,0 @@ -Fix issue #7068, due to an behavior of CSI external snapshotter, manipulations of VS and VSC may not be handled in the same order inside external snapshotter as the API is called. So add a protection finalizer to ensure the order \ No newline at end of file diff --git a/changelogs/unreleased/7096-Lyndon-Li b/changelogs/unreleased/7096-Lyndon-Li deleted file mode 100644 index 7ce331248..000000000 --- a/changelogs/unreleased/7096-Lyndon-Li +++ /dev/null @@ -1 +0,0 @@ -Fix issue #7094, fallback to full backup if previous snapshot is not found \ No newline at end of file diff --git a/changelogs/unreleased/7100-blackpiglet b/changelogs/unreleased/7100-blackpiglet deleted file mode 100644 index 1084a29d1..000000000 --- a/changelogs/unreleased/7100-blackpiglet +++ /dev/null @@ -1 +0,0 @@ -Generate VolumeInfo for backup. \ No newline at end of file diff --git a/changelogs/unreleased/7102-Lyndon-Li b/changelogs/unreleased/7102-Lyndon-Li deleted file mode 100644 index 4b5b81ffd..000000000 --- a/changelogs/unreleased/7102-Lyndon-Li +++ /dev/null @@ -1 +0,0 @@ -Fix issue #7068, due to a behavior of CSI external snapshotter, manipulations of VS and VSC may not be handled in the same order inside external snapshotter as the API is called. So add a protection finalizer to ensure the order \ No newline at end of file diff --git a/changelogs/unreleased/7115-reasonerjt b/changelogs/unreleased/7115-reasonerjt deleted file mode 100644 index 5824427b0..000000000 --- a/changelogs/unreleased/7115-reasonerjt +++ /dev/null @@ -1 +0,0 @@ -Include plugin name in the error message by operations \ No newline at end of file diff --git a/changelogs/unreleased/7117-allenxu404 b/changelogs/unreleased/7117-allenxu404 deleted file mode 100644 index 2cfc179b2..000000000 --- a/changelogs/unreleased/7117-allenxu404 +++ /dev/null @@ -1 +0,0 @@ -Add hooks status to backup/restore CR \ No newline at end of file diff --git a/changelogs/unreleased/7125-Lyndon-Li b/changelogs/unreleased/7125-Lyndon-Li deleted file mode 100644 index 0bab9d6aa..000000000 --- a/changelogs/unreleased/7125-Lyndon-Li +++ /dev/null @@ -1 +0,0 @@ -Fix issue #6695, add describe for data mover backups \ No newline at end of file diff --git a/changelogs/unreleased/7130-qiuming-best b/changelogs/unreleased/7130-qiuming-best deleted file mode 100644 index f6f6c6f74..000000000 --- a/changelogs/unreleased/7130-qiuming-best +++ /dev/null @@ -1 +0,0 @@ -Node agent restart enhancement diff --git a/changelogs/unreleased/7138-blackpiglet b/changelogs/unreleased/7138-blackpiglet deleted file mode 100644 index ccd5d0690..000000000 --- a/changelogs/unreleased/7138-blackpiglet +++ /dev/null @@ -1 +0,0 @@ -Use VolumeInfo to help restore the PV. \ No newline at end of file diff --git a/changelogs/unreleased/7141-qiuming-best b/changelogs/unreleased/7141-qiuming-best deleted file mode 100644 index cf7fc0dda..000000000 --- a/changelogs/unreleased/7141-qiuming-best +++ /dev/null @@ -1 +0,0 @@ -Allow sparse option for Kopia & Restic restore diff --git a/changelogs/unreleased/7143-reasonerjt b/changelogs/unreleased/7143-reasonerjt deleted file mode 100644 index 4edac25f5..000000000 --- a/changelogs/unreleased/7143-reasonerjt +++ /dev/null @@ -1 +0,0 @@ -Treat namespace as a regular restorable item \ No newline at end of file diff --git a/changelogs/unreleased/7150-Lyndon-Li b/changelogs/unreleased/7150-Lyndon-Li deleted file mode 100644 index 920544827..000000000 --- a/changelogs/unreleased/7150-Lyndon-Li +++ /dev/null @@ -1 +0,0 @@ -Fix issue #7135, check pod status before checking node-agent pod status \ No newline at end of file diff --git a/changelogs/unreleased/7151-blackpiglet b/changelogs/unreleased/7151-blackpiglet deleted file mode 100644 index 6548832b1..000000000 --- a/changelogs/unreleased/7151-blackpiglet +++ /dev/null @@ -1 +0,0 @@ -Add more linters part 2. \ No newline at end of file diff --git a/changelogs/unreleased/7152-reasonerjt b/changelogs/unreleased/7152-reasonerjt deleted file mode 100644 index 809921f33..000000000 --- a/changelogs/unreleased/7152-reasonerjt +++ /dev/null @@ -1 +0,0 @@ -Track the skipped PV when SnapshotVolumes set as false \ No newline at end of file diff --git a/changelogs/unreleased/7153-allenxu404 b/changelogs/unreleased/7153-allenxu404 deleted file mode 100644 index a8faaf99c..000000000 --- a/changelogs/unreleased/7153-allenxu404 +++ /dev/null @@ -1 +0,0 @@ -Enhance hooks tracker by adding a returned error to record function \ No newline at end of file diff --git a/changelogs/unreleased/7161-Lyndon-Li b/changelogs/unreleased/7161-Lyndon-Li deleted file mode 100644 index 19524276f..000000000 --- a/changelogs/unreleased/7161-Lyndon-Li +++ /dev/null @@ -1 +0,0 @@ -Add node-agent concurrency doc and change the config name from dataPathConcurrency to loadCocurrency \ No newline at end of file diff --git a/changelogs/unreleased/7169-kaovilai b/changelogs/unreleased/7169-kaovilai deleted file mode 100644 index 0865b5691..000000000 --- a/changelogs/unreleased/7169-kaovilai +++ /dev/null @@ -1 +0,0 @@ -Add `--skip-immediately` flag to schedule commands; `--schedule-skip-immediately` server and install \ No newline at end of file diff --git a/changelogs/unreleased/7175-blackpiglet b/changelogs/unreleased/7175-blackpiglet deleted file mode 100644 index 4ca4de1e3..000000000 --- a/changelogs/unreleased/7175-blackpiglet +++ /dev/null @@ -1 +0,0 @@ -Refactor DownloadRequest Stream function \ No newline at end of file diff --git a/changelogs/unreleased/7184-blackpiglet b/changelogs/unreleased/7184-blackpiglet deleted file mode 100644 index ac5d18011..000000000 --- a/changelogs/unreleased/7184-blackpiglet +++ /dev/null @@ -1,2 +0,0 @@ -Update CSIVolumeSnapshotsCompleted in backup's status and the metric -during backup finalize stage according to async operations content. \ No newline at end of file diff --git a/changelogs/unreleased/7201-Lyndon-Li b/changelogs/unreleased/7201-Lyndon-Li deleted file mode 100644 index 21ea28c9c..000000000 --- a/changelogs/unreleased/7201-Lyndon-Li +++ /dev/null @@ -1 +0,0 @@ -Fix issue #7189, data mover generic restore - don't assume the first volume as the restore volume \ No newline at end of file diff --git a/changelogs/unreleased/7229-allenxu404 b/changelogs/unreleased/7229-allenxu404 deleted file mode 100644 index 1ef7bdb7e..000000000 --- a/changelogs/unreleased/7229-allenxu404 +++ /dev/null @@ -1 +0,0 @@ -Remove the redundant newline in backup describe output \ No newline at end of file diff --git a/changelogs/unreleased/7238-allenxu404 b/changelogs/unreleased/7238-allenxu404 deleted file mode 100644 index 419d6b4bb..000000000 --- a/changelogs/unreleased/7238-allenxu404 +++ /dev/null @@ -1 +0,0 @@ -Adjust the newline output of resource list in restore describer \ No newline at end of file diff --git a/changelogs/unreleased/7245-Lyndon-Li b/changelogs/unreleased/7245-Lyndon-Li deleted file mode 100644 index edb945ec3..000000000 --- a/changelogs/unreleased/7245-Lyndon-Li +++ /dev/null @@ -1 +0,0 @@ -Fix issue #7244. By the end of the upload, check the outstanding incomplete snapshots and delete them by calling ApplyRetentionPolicy \ No newline at end of file diff --git a/changelogs/unreleased/7274-reasonerjt b/changelogs/unreleased/7274-reasonerjt deleted file mode 100644 index 022398996..000000000 --- a/changelogs/unreleased/7274-reasonerjt +++ /dev/null @@ -1 +0,0 @@ -Do not set "targetNamespace" to namespace items \ No newline at end of file diff --git a/changelogs/unreleased/7282-Lyndon-Li b/changelogs/unreleased/7282-Lyndon-Li deleted file mode 100644 index 291a25210..000000000 --- a/changelogs/unreleased/7282-Lyndon-Li +++ /dev/null @@ -1 +0,0 @@ -Fix issue #6928, remove snapshot deletion timeout for PVB \ No newline at end of file diff --git a/changelogs/unreleased/7294-ywk253100 b/changelogs/unreleased/7294-ywk253100 deleted file mode 100644 index a12df3439..000000000 --- a/changelogs/unreleased/7294-ywk253100 +++ /dev/null @@ -1 +0,0 @@ -Make "disable-informer-cache" option false(enabled) by default to keep it consistent with the help message \ No newline at end of file From d676bfde2233d0d038286df91162c434331e8856 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wenkai=20Yin=28=E5=B0=B9=E6=96=87=E5=BC=80=29?= Date: Fri, 12 Jan 2024 14:53:38 +0800 Subject: [PATCH 10/32] Increase the k8s client QPS/burst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Increase the k8s client QPS/burst to avoid throttling request errors Fixes #7127 Fixes #3191 Signed-off-by: Wenkai Yin(尹文开) --- changelogs/unreleased/7311-ywk253100 | 1 + pkg/cmd/server/server.go | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/7311-ywk253100 diff --git a/changelogs/unreleased/7311-ywk253100 b/changelogs/unreleased/7311-ywk253100 new file mode 100644 index 000000000..4063972c7 --- /dev/null +++ b/changelogs/unreleased/7311-ywk253100 @@ -0,0 +1 @@ +Increase the k8s client QPS/burst to avoid throttling request errors \ No newline at end of file diff --git a/pkg/cmd/server/server.go b/pkg/cmd/server/server.go index 859301373..06d164d74 100644 --- a/pkg/cmd/server/server.go +++ b/pkg/cmd/server/server.go @@ -88,8 +88,8 @@ const ( defaultResourceTerminatingTimeout = 10 * time.Minute // server's client default qps and burst - defaultClientQPS float32 = 20.0 - defaultClientBurst int = 30 + defaultClientQPS float32 = 100.0 + defaultClientBurst int = 100 defaultClientPageSize int = 500 defaultProfilerAddress = "localhost:6060" From 373b24e2c111e2af58de3a4b4c8d91087b9c7d7b Mon Sep 17 00:00:00 2001 From: Guillaume Delacour Date: Thu, 11 Jan 2024 11:47:06 +0100 Subject: [PATCH 11/32] Upgrade AWS SDK Signed-off-by: Guillaume Delacour --- changelogs/unreleased/7307-guikcd | 1 + go.mod | 40 +++++++-------- go.sum | 81 +++++++++++++++---------------- test/util/providers/aws_utils.go | 2 +- 4 files changed, 62 insertions(+), 62 deletions(-) create mode 100644 changelogs/unreleased/7307-guikcd diff --git a/changelogs/unreleased/7307-guikcd b/changelogs/unreleased/7307-guikcd new file mode 100644 index 000000000..5dd0c39e7 --- /dev/null +++ b/changelogs/unreleased/7307-guikcd @@ -0,0 +1 @@ +Bump up aws-sdk to latest version to leverage Pod Identity credentials. diff --git a/go.mod b/go.mod index aeb128899..9e2f41bd6 100644 --- a/go.mod +++ b/go.mod @@ -16,11 +16,11 @@ require ( github.com/Azure/go-autorest/autorest v0.11.27 github.com/Azure/go-autorest/autorest/azure/auth v0.5.8 github.com/Azure/go-autorest/autorest/to v0.3.0 - github.com/aws/aws-sdk-go-v2 v1.21.0 - github.com/aws/aws-sdk-go-v2/config v1.18.42 - github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.87 - github.com/aws/aws-sdk-go-v2/service/ec2 v1.123.0 - github.com/aws/aws-sdk-go-v2/service/s3 v1.40.0 + github.com/aws/aws-sdk-go-v2 v1.24.1 + github.com/aws/aws-sdk-go-v2/config v1.26.3 + github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.11 + github.com/aws/aws-sdk-go-v2/service/ec2 v1.143.0 + github.com/aws/aws-sdk-go-v2/service/s3 v1.48.0 github.com/bombsimon/logrusr/v3 v3.0.0 github.com/evanphx/json-patch v5.6.0+incompatible github.com/fatih/color v1.15.0 @@ -82,21 +82,21 @@ require ( github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.13 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.13.40 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.3.43 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.14 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.36 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.4 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.14.1 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.1 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.22.0 // indirect - github.com/aws/smithy-go v1.14.2 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.16.14 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.10 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.10 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.10 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.18.6 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.6 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 // indirect + github.com/aws/smithy-go v1.19.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chmduquesne/rollinghash v4.0.0+incompatible // indirect diff --git a/go.sum b/go.sum index 96f78d1c7..bffe4194d 100644 --- a/go.sum +++ b/go.sum @@ -140,46 +140,46 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/aws/aws-sdk-go-v2 v1.21.0 h1:gMT0IW+03wtYJhRqTVYn0wLzwdnK9sRMcxmtfGzRdJc= -github.com/aws/aws-sdk-go-v2 v1.21.0/go.mod h1:/RfNgGmRxI+iFOB1OeJUyxiU+9s88k3pfHvDagGEp0M= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.13 h1:OPLEkmhXf6xFPiz0bLeDArZIDx1NNS4oJyG4nv3Gct0= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.13/go.mod h1:gpAbvyDGQFozTEmlTFO8XcQKHzubdq0LzRyJpG6MiXM= -github.com/aws/aws-sdk-go-v2/config v1.18.42 h1:28jHROB27xZwU0CB88giDSjz7M1Sba3olb5JBGwina8= -github.com/aws/aws-sdk-go-v2/config v1.18.42/go.mod h1:4AZM3nMMxwlG+eZlxvBKqwVbkDLlnN2a4UGTL6HjaZI= -github.com/aws/aws-sdk-go-v2/credentials v1.13.40 h1:s8yOkDh+5b1jUDhMBtngF6zKWLDs84chUk2Vk0c38Og= -github.com/aws/aws-sdk-go-v2/credentials v1.13.40/go.mod h1:VtEHVAAqDWASwdOqj/1huyT6uHbs5s8FUHfDQdky/Rs= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11 h1:uDZJF1hu0EVT/4bogChk8DyjSF6fof6uL/0Y26Ma7Fg= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11/go.mod h1:TEPP4tENqBGO99KwVpV9MlOX4NSrSLP8u3KRy2CDwA8= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.87 h1:e20ZrsgDPUXqg8+rZVuPwNSp6yniUN2Yr2tzFZ+Yvl0= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.87/go.mod h1:0i0TAT6W+5i48QTlDU2KmY6U2hBZeY/LCP0wktya2oc= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41 h1:22dGT7PneFMx4+b3pz7lMTRyN8ZKH7M2cW4GP9yUS2g= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.41/go.mod h1:CrObHAuPneJBlfEJ5T3szXOUkLEThaGfvnhTf33buas= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35 h1:SijA0mgjV8E+8G45ltVHs0fvKpTj8xmZJ3VwhGKtUSI= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.35/go.mod h1:SJC1nEVVva1g3pHAIdCp7QsRIkMmLAgoDquQ9Rr8kYw= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.43 h1:g+qlObJH4Kn4n21g69DjspU0hKTjWtq7naZ9OLCv0ew= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.43/go.mod h1:rzfdUlfA+jdgLDmPKjd3Chq9V7LVLYo1Nz++Wb91aRo= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.4 h1:6lJvvkQ9HmbHZ4h/IEwclwv2mrTW8Uq1SOB/kXy0mfw= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.4/go.mod h1:1PrKYwxTM+zjpw9Y41KFtoJCQrJ34Z47Y4VgVbfndjo= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.123.0 h1:AkjjaINgZQlz3valIVmlrs18jsl+fzYuoxED8oOVrYo= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.123.0/go.mod h1:0FhI2Rzcv5BNM3dNnbcCx2qa2naFZoAidJi11cQgzL0= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.14 h1:m0QTSI6pZYJTk5WSKx3fm5cNW/DCicVzULBgU/6IyD0= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.14/go.mod h1:dDilntgHy9WnHXsh7dDtUPgHKEfTJIBUTHM8OWm0f/0= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.36 h1:eev2yZX7esGRjqRbnVk1UxMLw4CyVZDpZXRCcy75oQk= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.36/go.mod h1:lGnOkH9NJATw0XEPcAknFBj3zzNTEGRHtSw+CwC1YTg= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35 h1:CdzPW9kKitgIiLV1+MHobfR5Xg25iYnyzWZhyQuSlDI= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.35/go.mod h1:QGF2Rs33W5MaN9gYdEQOBBFPLwTZkEhRwI33f7KIG0o= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.4 h1:v0jkRigbSD6uOdwcaUQmgEwG1BkPfAPDqaeNt/29ghg= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.4/go.mod h1:LhTyt8J04LL+9cIt7pYJ5lbS/U98ZmXovLOR/4LUsk8= -github.com/aws/aws-sdk-go-v2/service/s3 v1.40.0 h1:wl5dxN1NONhTDQD9uaEvNsDRX29cBmGED/nl0jkWlt4= -github.com/aws/aws-sdk-go-v2/service/s3 v1.40.0/go.mod h1:rDGMZA7f4pbmTtPOk5v5UM2lmX6UAbRnMDJeDvnH7AM= -github.com/aws/aws-sdk-go-v2/service/sso v1.14.1 h1:YkNzx1RLS0F5qdf9v1Q8Cuv9NXCL2TkosOxhzlUPV64= -github.com/aws/aws-sdk-go-v2/service/sso v1.14.1/go.mod h1:fIAwKQKBFu90pBxx07BFOMJLpRUGu8VOzLJakeY+0K4= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.1 h1:8lKOidPkmSmfUtiTgtdXWgaKItCZ/g75/jEk6Ql6GsA= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.1/go.mod h1:yygr8ACQRY2PrEcy3xsUI357stq2AxnFM6DIsR9lij4= -github.com/aws/aws-sdk-go-v2/service/sts v1.22.0 h1:s4bioTgjSFRwOoyEFzAVCmFmoowBgjTR8gkrF/sQ4wk= -github.com/aws/aws-sdk-go-v2/service/sts v1.22.0/go.mod h1:VC7JDqsqiwXukYEDjoHh9U0fOJtNWh04FPQz4ct4GGU= -github.com/aws/smithy-go v1.14.2 h1:MJU9hqBGbvWZdApzpvoF2WAIJDbtjK2NDJSiJP7HblQ= -github.com/aws/smithy-go v1.14.2/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= +github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU= +github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 h1:OCs21ST2LrepDfD3lwlQiOqIGp6JiEUqG84GzTDoyJs= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4/go.mod h1:usURWEKSNNAcAZuzRn/9ZYPT8aZQkR7xcCtunK/LkJo= +github.com/aws/aws-sdk-go-v2/config v1.26.3 h1:dKuc2jdp10y13dEEvPqWxqLoc0vF3Z9FC45MvuQSxOA= +github.com/aws/aws-sdk-go-v2/config v1.26.3/go.mod h1:Bxgi+DeeswYofcYO0XyGClwlrq3DZEXli0kLf4hkGA0= +github.com/aws/aws-sdk-go-v2/credentials v1.16.14 h1:mMDTwwYO9A0/JbOCOG7EOZHtYM+o7OfGWfu0toa23VE= +github.com/aws/aws-sdk-go-v2/credentials v1.16.14/go.mod h1:cniAUh3ErQPHtCQGPT5ouvSAQ0od8caTO9OOuufZOAE= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.11 h1:I6lAa3wBWfCz/cKkOpAcumsETRkFAl70sWi8ItcMEsM= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.11/go.mod h1:be1NIO30kJA23ORBLqPo1LttEM6tPNSEcjkd1eKzNW0= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10/go.mod h1:6UV4SZkVvmODfXKql4LCbaZUpF7HO2BX38FgBf9ZOLw= +github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 h1:GrSw8s0Gs/5zZ0SX+gX4zQjRnRsMJDJ2sLur1gRBhEM= +github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.10 h1:5oE2WzJE56/mVveuDZPJESKlg/00AaS2pY2QZcnxg4M= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.10/go.mod h1:FHbKWQtRBYUz4vO5WBWjzMD2by126ny5y/1EoaWoLfI= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.143.0 h1:ZAO4y7MSRqU74ZFCA+HC6Ek5fI7dsTdwJg88s72I/gE= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.143.0/go.mod h1:hIsHE0PaWAQakLCshKS7VKWMGXaqrAFp4m95s2W9E6c= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.10 h1:L0ai8WICYHozIKK+OtPzVJBugL7culcuM4E4JOpIEm8= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.10/go.mod h1:byqfyxJBshFk0fF9YmK0M0ugIO8OWjzH2T3bPG4eGuA= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIGQSGs9w4jKm60F5dmCQ3EEruxdc0MFh+3EY4= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10/go.mod h1:wohMUQiFdzo0NtxbBg0mSRGZ4vL3n0dKjLTINdcIino= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.10 h1:KOxnQeWy5sXyS37fdKEvAsGHOr9fa/qvwxfJurR/BzE= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.10/go.mod h1:jMx5INQFYFYB3lQD9W0D8Ohgq6Wnl7NYOJ2TQndbulI= +github.com/aws/aws-sdk-go-v2/service/s3 v1.48.0 h1:PJTdBMsyvra6FtED7JZtDpQrIAflYDHFoZAu/sKYkwU= +github.com/aws/aws-sdk-go-v2/service/s3 v1.48.0/go.mod h1:4qXHrG1Ne3VGIMZPCB8OjH/pLFO94sKABIusjh0KWPU= +github.com/aws/aws-sdk-go-v2/service/sso v1.18.6 h1:dGrs+Q/WzhsiUKh82SfTVN66QzyulXuMDTV/G8ZxOac= +github.com/aws/aws-sdk-go-v2/service/sso v1.18.6/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.6 h1:Yf2MIo9x+0tyv76GljxzqA3WtC5mw7NmazD2chwjxE4= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.6/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8= +github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0= +github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U= +github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM= +github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -406,7 +406,6 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= diff --git a/test/util/providers/aws_utils.go b/test/util/providers/aws_utils.go index 31b3326d2..10ce13e67 100644 --- a/test/util/providers/aws_utils.go +++ b/test/util/providers/aws_utils.go @@ -410,7 +410,7 @@ func (s AWSStorage) GetMinioBucketSize(cloudCredentialsFile, bslBucket, bslPrefi return 0, errors.Wrapf(err, "failed to list objects in bucket %s", bslBucket) } for _, obj := range page.Contents { - totalSize += obj.Size + totalSize += *obj.Size } } return totalSize, nil From 956248e8a6b17999ea1efcaaba7bee61a2498be6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wenkai=20Yin=28=E5=B0=B9=E6=96=87=E5=BC=80=29?= Date: Wed, 17 Jan 2024 16:54:44 +0800 Subject: [PATCH 12/32] Create informer per resources to avoid huge memory consumption MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Create informer per resources to avoid huge memory consumption Fixes #7323 Signed-off-by: Wenkai Yin(尹文开) --- pkg/client/dynamic.go | 8 ++-- pkg/restore/restore.go | 87 ++++++++++++++-------------------------- pkg/test/fake_dynamic.go | 4 +- 3 files changed, 35 insertions(+), 64 deletions(-) diff --git a/pkg/client/dynamic.go b/pkg/client/dynamic.go index 0e9655b11..705c28aaa 100644 --- a/pkg/client/dynamic.go +++ b/pkg/client/dynamic.go @@ -35,8 +35,8 @@ type DynamicFactory interface { // ClientForGroupVersionResource returns a Dynamic client for the given group/version // and resource for the given namespace. ClientForGroupVersionResource(gv schema.GroupVersion, resource metav1.APIResource, namespace string) (Dynamic, error) - // DynamicSharedInformerFactoryForNamespace returns a DynamicSharedInformerFactory for the given namespace. - DynamicSharedInformerFactoryForNamespace(namespace string) dynamicinformer.DynamicSharedInformerFactory + // DynamicSharedInformerFactory returns a DynamicSharedInformerFactory. + DynamicSharedInformerFactory() dynamicinformer.DynamicSharedInformerFactory } // dynamicFactory implements DynamicFactory. @@ -55,8 +55,8 @@ func (f *dynamicFactory) ClientForGroupVersionResource(gv schema.GroupVersion, r }, nil } -func (f *dynamicFactory) DynamicSharedInformerFactoryForNamespace(namespace string) dynamicinformer.DynamicSharedInformerFactory { - return dynamicinformer.NewFilteredDynamicSharedInformerFactory(f.dynamicClient, time.Minute, namespace, nil) +func (f *dynamicFactory) DynamicSharedInformerFactory() dynamicinformer.DynamicSharedInformerFactory { + return dynamicinformer.NewDynamicSharedInformerFactory(f.dynamicClient, time.Minute) } // Creator creates an object. diff --git a/pkg/restore/restore.go b/pkg/restore/restore.go index 43833b2b3..4d739290e 100644 --- a/pkg/restore/restore.go +++ b/pkg/restore/restore.go @@ -45,7 +45,6 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/dynamic/dynamicinformer" - "k8s.io/client-go/informers" corev1 "k8s.io/client-go/kubernetes/typed/core/v1" "k8s.io/client-go/tools/cache" crclient "sigs.k8s.io/controller-runtime/pkg/client" @@ -309,8 +308,6 @@ func (kr *kubernetesRestorer) RestoreWithResolvers( resourceTerminatingTimeout: kr.resourceTerminatingTimeout, resourceTimeout: kr.resourceTimeout, resourceClients: make(map[resourceClientKey]client.Dynamic), - dynamicInformerFactories: make(map[string]*informerFactoryWithContext), - resourceInformers: make(map[resourceClientKey]informers.GenericInformer), restoredItems: req.RestoredItems, renamedPVs: make(map[string]string), pvRenamer: kr.pvRenamer, @@ -362,8 +359,7 @@ type restoreContext struct { resourceTerminatingTimeout time.Duration resourceTimeout time.Duration resourceClients map[resourceClientKey]client.Dynamic - dynamicInformerFactories map[string]*informerFactoryWithContext - resourceInformers map[resourceClientKey]informers.GenericInformer + dynamicInformerFactory *informerFactoryWithContext restoredItems map[itemKey]restoredItemStatus renamedPVs map[string]string pvRenamer func(string) (string, error) @@ -447,11 +443,16 @@ func (ctx *restoreContext) execute() (results.Result, results.Result) { // Need to stop all informers if enabled if !ctx.disableInformerCache { + context, cancel := signal.NotifyContext(go_context.Background(), os.Interrupt) + ctx.dynamicInformerFactory = &informerFactoryWithContext{ + factory: ctx.dynamicFactory.DynamicSharedInformerFactory(), + context: context, + cancel: cancel, + } + defer func() { // Call the cancel func to close the channel for each started informer - for _, factory := range ctx.dynamicInformerFactories { - factory.cancel() - } + ctx.dynamicInformerFactory.cancel() // After upgrading to client-go 0.27 or newer, also call Shutdown for each informer factory }() } @@ -579,28 +580,24 @@ func (ctx *restoreContext) execute() (results.Result, results.Result) { // initialize informer caches for selected resources if enabled if !ctx.disableInformerCache { - // CRD informer will have already been initialized if any CRDs were created, - // but already-initialized informers aren't re-initialized because getGenericInformer - // looks for an existing one first. - factoriesToStart := make(map[string]*informerFactoryWithContext) for _, informerResource := range selectedResourceCollection { - gr := schema.ParseGroupResource(informerResource.resource) + if informerResource.totalItems == 0 { + continue + } + version := "" for _, items := range informerResource.selectedItemsByNamespace { - // don't use ns key since it represents original ns, not mapped ns if len(items) == 0 { continue } - // use the first item in the list to initialize the informer. The rest of the list - // should share the same gvr and namespace - _, factory := ctx.getGenericInformerInternal(gr, items[0].version, items[0].targetNamespace) - if factory != nil { - factoriesToStart[items[0].targetNamespace] = factory - } + version = items[0].version + break } + gvr := schema.ParseGroupResource(informerResource.resource).WithVersion(version) + ctx.dynamicInformerFactory.factory.ForResource(gvr) } - for _, factoryWithContext := range factoriesToStart { - factoryWithContext.factory.WaitForCacheSync(factoryWithContext.context.Done()) - } + ctx.dynamicInformerFactory.factory.Start(ctx.dynamicInformerFactory.context.Done()) + ctx.log.Info("waiting informer cache sync ...") + ctx.dynamicInformerFactory.factory.WaitForCacheSync(ctx.dynamicInformerFactory.context.Done()) } // reset processedItems and totalItems before processing full resource list @@ -1061,42 +1058,15 @@ func (ctx *restoreContext) getResourceClient(groupResource schema.GroupResource, return client, nil } -// if new informer is created, non-nil factory is returned -func (ctx *restoreContext) getGenericInformerInternal(groupResource schema.GroupResource, version, namespace string) (informers.GenericInformer, *informerFactoryWithContext) { - var returnFactory *informerFactoryWithContext - - key := getResourceClientKey(groupResource, version, namespace) - factoryWithContext, ok := ctx.dynamicInformerFactories[key.namespace] - if !ok { - factory := ctx.dynamicFactory.DynamicSharedInformerFactoryForNamespace(namespace) - informerContext, informerCancel := signal.NotifyContext(go_context.Background(), os.Interrupt) - factoryWithContext = &informerFactoryWithContext{ - factory: factory, - context: informerContext, - cancel: informerCancel, - } - ctx.dynamicInformerFactories[key.namespace] = factoryWithContext - } - informer, ok := ctx.resourceInformers[key] - if !ok { - ctx.log.Infof("[debug] Creating factory for %s in namespace %s", key.resource, key.namespace) - informer = factoryWithContext.factory.ForResource(key.resource) - factoryWithContext.factory.Start(factoryWithContext.context.Done()) - ctx.resourceInformers[key] = informer - returnFactory = factoryWithContext - } - return informer, returnFactory -} - -func (ctx *restoreContext) getGenericInformer(groupResource schema.GroupResource, version, namespace string) informers.GenericInformer { - informer, factoryWithContext := ctx.getGenericInformerInternal(groupResource, version, namespace) - if factoryWithContext != nil { - factoryWithContext.factory.WaitForCacheSync(factoryWithContext.context.Done()) - } - return informer -} func (ctx *restoreContext) getResourceLister(groupResource schema.GroupResource, obj *unstructured.Unstructured, namespace string) cache.GenericNamespaceLister { - informer := ctx.getGenericInformer(groupResource, obj.GroupVersionKind().Version, namespace) + informer := ctx.dynamicInformerFactory.factory.ForResource(groupResource.WithVersion(obj.GroupVersionKind().Version)) + // if the restore contains CRDs or the RIA returns new resources, need to make sure the corresponding informers are synced + if !informer.Informer().HasSynced() { + ctx.dynamicInformerFactory.factory.Start(ctx.dynamicInformerFactory.context.Done()) + ctx.log.Infof("waiting informer cache sync for %s, %s/%s ...", groupResource, namespace, obj.GetName()) + ctx.dynamicInformerFactory.factory.WaitForCacheSync(ctx.dynamicInformerFactory.context.Done()) + } + if namespace == "" { return informer.Lister() } else { @@ -1123,6 +1093,7 @@ func (ctx *restoreContext) getResource(groupResource schema.GroupResource, obj * ctx.log.WithError(errors.WithStack(fmt.Errorf("expected *unstructured.Unstructured but got %T", u))).Error("unable to understand entry returned from client") return nil, fmt.Errorf("expected *unstructured.Unstructured but got %T", u) } + ctx.log.Debugf("get %s, %s/%s from informer cache", groupResource, namespace, name) return u, nil } diff --git a/pkg/test/fake_dynamic.go b/pkg/test/fake_dynamic.go index d184758a9..1007ecdf3 100644 --- a/pkg/test/fake_dynamic.go +++ b/pkg/test/fake_dynamic.go @@ -38,8 +38,8 @@ func (df *FakeDynamicFactory) ClientForGroupVersionResource(gv schema.GroupVersi return args.Get(0).(client.Dynamic), args.Error(1) } -func (df *FakeDynamicFactory) DynamicSharedInformerFactoryForNamespace(namespace string) dynamicinformer.DynamicSharedInformerFactory { - args := df.Called(namespace) +func (df *FakeDynamicFactory) DynamicSharedInformerFactory() dynamicinformer.DynamicSharedInformerFactory { + args := df.Called() return args.Get(0).(dynamicinformer.DynamicSharedInformerFactory) } From 198fbf6873d986b5bd33809c67b55ab687e78dfa Mon Sep 17 00:00:00 2001 From: Karthick Udayakumar Date: Wed, 17 Jan 2024 22:26:00 -0500 Subject: [PATCH 13/32] fix typo maintenance of log message Signed-off-by: Karthick Udayakumar --- pkg/controller/backup_repository_controller.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/controller/backup_repository_controller.go b/pkg/controller/backup_repository_controller.go index 12d293051..70918c861 100644 --- a/pkg/controller/backup_repository_controller.go +++ b/pkg/controller/backup_repository_controller.go @@ -260,7 +260,7 @@ func (r *BackupRepoReconciler) getRepositoryMaintenanceFrequency(req *velerov1ap r.logger.WithError(err).WithField("returned frequency", frequency).Warn("Failed to get maitanance frequency, use the default one") frequency = defaultMaintainFrequency } else { - r.logger.WithField("frequency", frequency).Info("Set matainenance according to repository suggestion") + r.logger.WithField("frequency", frequency).Info("Set maintenance according to repository suggestion") } return frequency From bc526b99b12f55c8a1127652d2c29c72d4dd2dc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wenkai=20Yin=28=E5=B0=B9=E6=96=87=E5=BC=80=29?= Date: Thu, 18 Jan 2024 11:01:22 +0800 Subject: [PATCH 14/32] Add release note for the informer cache memory consumption MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add release note for the informer cache memory consumption Signed-off-by: Wenkai Yin(尹文开) --- changelogs/CHANGELOG-1.13.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/changelogs/CHANGELOG-1.13.md b/changelogs/CHANGELOG-1.13.md index 13ac4b257..ec7362bbe 100644 --- a/changelogs/CHANGELOG-1.13.md +++ b/changelogs/CHANGELOG-1.13.md @@ -64,6 +64,9 @@ To fix CVEs and keep pace with Golang, Velero made changes as follows: ### Limitations/Known issues * The backup's VolumeInfo metadata doesn't have the information updated in the async operations. This function could be supported in v1.14 release. +### Note +* Velero introduces the informer cache which is enabled by default. The informer cache improves the restore performance but may cause higher memory consumption. Increase the memory limit of the Velero pod or disable the informer cache by specifying the `--disable-informer-cache` option when installing Velero if you get the OOM error. + ### Deprecation announcement * The generated k8s clients, informers, and listers are deprecated in the Velero v1.13 release. They are put in the Velero repository's pkg/generated directory. According to the n+2 supporting policy, the deprecated are kept for two more releases. The pkg/generated directory should be deleted in the v1.15 release. * After the backup VolumeInfo metadata file is added to the backup, Velero decides how to restore the PV resource according to the VolumeInfo content. To support the backup generated by the older version of Velero, the old logic is also kept. The support for the backup without the VolumeInfo metadata file will be kept for two releases. The support logic will be deleted in the v1.15 release. From 91abc93087143c05ab161fec47250fc60525dc5b Mon Sep 17 00:00:00 2001 From: Xun Jiang Date: Thu, 18 Jan 2024 20:51:30 +0800 Subject: [PATCH 15/32] Modify S3ForcePathStyle description. And cross-version link for CSI snapshot data movement page. Signed-off-by: Xun Jiang --- site/content/docs/main/contributions/minio.md | 2 +- .../content/docs/v1.13/contributions/minio.md | 2 +- site/layouts/index.redirects | 25 ++++++++++--------- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/site/content/docs/main/contributions/minio.md b/site/content/docs/main/contributions/minio.md index a44062c77..4b5aaa8e5 100644 --- a/site/content/docs/main/contributions/minio.md +++ b/site/content/docs/main/contributions/minio.md @@ -87,7 +87,7 @@ These instructions start the Velero server and a Minio instance that is accessib * This example also assumes you have named your Minio bucket "velero". - * Please make sure to set parameter `s3ForcePathStyle=true`. The parameter is used to set the Velero integrated AWS SDK data query address style. There are two types of the address: [virtual-host and path-style](https://docs.aws.amazon.com/AmazonS3/latest/userguide/VirtualHosting.html). If the `s3ForcePathStyle=true` is not set, the default value is false, then the AWS SDK will query in virtual-host style, but the MinIO server only support path-style address by default. The miss match will cause Velero can upload data to MinIO, but cannot download from MinIO. This [link](https://github.com/vmware-tanzu/velero/issues/7268) is an example of this issue. + * Please make sure to set parameter `s3ForcePathStyle=true`. The parameter is used to set the Velero integrated AWS SDK data query address style. There are two types of the address: [virtual-host and path-style](https://docs.aws.amazon.com/AmazonS3/latest/userguide/VirtualHosting.html). If the `s3ForcePathStyle=true` is not set, the default value is false, then the AWS SDK will query in virtual-host style, but the MinIO server only support path-style address by default. The miss match will mean Velero can upload data to MinIO, but **cannot download from MinIO**. This [link](https://github.com/vmware-tanzu/velero/issues/7268) is an example of this issue. It can be resolved by two ways: * Set `s3ForcePathStyle=true` for parameter `--backup-location-config` when installing Velero. This is the preferred way. * Make MinIO server support virtual-host style address. Add the [MINIO_DOMAIN environment variable](https://min.io/docs/minio/linux/reference/minio-server/settings/core.html#id5) for MinIO server will do the magic. diff --git a/site/content/docs/v1.13/contributions/minio.md b/site/content/docs/v1.13/contributions/minio.md index a44062c77..4b5aaa8e5 100644 --- a/site/content/docs/v1.13/contributions/minio.md +++ b/site/content/docs/v1.13/contributions/minio.md @@ -87,7 +87,7 @@ These instructions start the Velero server and a Minio instance that is accessib * This example also assumes you have named your Minio bucket "velero". - * Please make sure to set parameter `s3ForcePathStyle=true`. The parameter is used to set the Velero integrated AWS SDK data query address style. There are two types of the address: [virtual-host and path-style](https://docs.aws.amazon.com/AmazonS3/latest/userguide/VirtualHosting.html). If the `s3ForcePathStyle=true` is not set, the default value is false, then the AWS SDK will query in virtual-host style, but the MinIO server only support path-style address by default. The miss match will cause Velero can upload data to MinIO, but cannot download from MinIO. This [link](https://github.com/vmware-tanzu/velero/issues/7268) is an example of this issue. + * Please make sure to set parameter `s3ForcePathStyle=true`. The parameter is used to set the Velero integrated AWS SDK data query address style. There are two types of the address: [virtual-host and path-style](https://docs.aws.amazon.com/AmazonS3/latest/userguide/VirtualHosting.html). If the `s3ForcePathStyle=true` is not set, the default value is false, then the AWS SDK will query in virtual-host style, but the MinIO server only support path-style address by default. The miss match will mean Velero can upload data to MinIO, but **cannot download from MinIO**. This [link](https://github.com/vmware-tanzu/velero/issues/7268) is an example of this issue. It can be resolved by two ways: * Set `s3ForcePathStyle=true` for parameter `--backup-location-config` when installing Velero. This is the preferred way. * Make MinIO server support virtual-host style address. Add the [MINIO_DOMAIN environment variable](https://min.io/docs/minio/linux/reference/minio-server/settings/core.html#id5) for MinIO server will do the magic. diff --git a/site/layouts/index.redirects b/site/layouts/index.redirects index bd30d5d67..ab3c1f35c 100644 --- a/site/layouts/index.redirects +++ b/site/layouts/index.redirects @@ -1,14 +1,15 @@ {{ $versions := site.Params.versions }} {{ $latest := site.Params.latest }} -/docs /docs/{{ $latest }} 301! -/docs/latest /docs/{{ $latest }} -/docs/latest/* /docs/{{ $latest }}/:splat -/docs/troubleshooting /docs/{{ $latest }}/troubleshooting -/docs/supported-providers /docs/{{ $latest }}/supported-providers -/docs/zenhub /docs/{{ $latest }}/support-process -/docs/install-overview /docs/{{ $latest }}/basic-install -/docs/start-contributing /docs/{{ $latest }}/start-contributing -/docs/customize-installation /docs/{{ $latest }}/customize-installation -/docs/faq /docs/{{ $latest }}/faq -/docs/csi /docs/{{ $latest }}/csi -/docs/file-system-backup /docs/{{ $latest }}/file-system-backup +/docs /docs/{{ $latest }} 301! +/docs/latest /docs/{{ $latest }} +/docs/latest/* /docs/{{ $latest }}/:splat +/docs/troubleshooting /docs/{{ $latest }}/troubleshooting +/docs/supported-providers /docs/{{ $latest }}/supported-providers +/docs/zenhub /docs/{{ $latest }}/support-process +/docs/install-overview /docs/{{ $latest }}/basic-install +/docs/start-contributing /docs/{{ $latest }}/start-contributing +/docs/customize-installation /docs/{{ $latest }}/customize-installation +/docs/faq /docs/{{ $latest }}/faq +/docs/csi /docs/{{ $latest }}/csi +/docs/file-system-backup /docs/{{ $latest }}/file-system-backup +/docs/csi-snapshot-data-movement /docs/{{ $latest }}/csi-snapshot-data-movement \ No newline at end of file From 427a25413629317c31a6e33620cf11140d154e05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wenkai=20Yin=28=E5=B0=B9=E6=96=87=E5=BC=80=29?= Date: Fri, 19 Jan 2024 12:51:56 +0800 Subject: [PATCH 16/32] Specify the Kind explicitly in the API resource MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Specify the Kind explicitly in the API resource to avoid wrong Kind conversion Signed-off-by: Wenkai Yin(尹文开) --- pkg/test/discovery_client.go | 5 +---- pkg/test/resources.go | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/pkg/test/discovery_client.go b/pkg/test/discovery_client.go index e492aafc9..3f65cf5ea 100644 --- a/pkg/test/discovery_client.go +++ b/pkg/test/discovery_client.go @@ -19,9 +19,6 @@ package test import ( "strings" - "golang.org/x/text/cases" - "golang.org/x/text/language" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/discovery" discoveryfake "k8s.io/client-go/discovery/fake" @@ -76,7 +73,7 @@ func (c *DiscoveryClient) WithAPIResource(resource *APIResource) *DiscoveryClien Namespaced: resource.Namespaced, Group: resource.Group, Version: resource.Version, - Kind: cases.Title(language.Und).String(strings.TrimSuffix(resource.Name, "s")), + Kind: resource.Kind, Verbs: metav1.Verbs([]string{"list", "create", "get", "delete"}), ShortNames: []string{resource.ShortName}, }) diff --git a/pkg/test/resources.go b/pkg/test/resources.go index 709497fca..dc1c8e29d 100644 --- a/pkg/test/resources.go +++ b/pkg/test/resources.go @@ -27,6 +27,7 @@ type APIResource struct { Group string Version string Name string + Kind string ShortName string Namespaced bool Items []metav1.Object @@ -59,6 +60,7 @@ func PVCs(items ...metav1.Object) *APIResource { Version: "v1", Name: "persistentvolumeclaims", ShortName: "pvc", + Kind: "PersistentVolumeClaim", Namespaced: true, Items: items, } @@ -70,6 +72,7 @@ func PVs(items ...metav1.Object) *APIResource { Version: "v1", Name: "persistentvolumes", ShortName: "pv", + Kind: "PersistentVolume", Namespaced: false, Items: items, } @@ -81,6 +84,7 @@ func Secrets(items ...metav1.Object) *APIResource { Version: "v1", Name: "secrets", ShortName: "secrets", + Kind: "Secret", Namespaced: true, Items: items, } @@ -92,6 +96,7 @@ func Deployments(items ...metav1.Object) *APIResource { Version: "v1", Name: "deployments", ShortName: "deploy", + Kind: "Deployment", Namespaced: true, Items: items, } @@ -103,6 +108,7 @@ func ExtensionsDeployments(items ...metav1.Object) *APIResource { Version: "v1", Name: "deployments", ShortName: "deploy", + Kind: "Deployment", Namespaced: true, Items: items, } @@ -115,6 +121,7 @@ func VeleroDeployments(items ...metav1.Object) *APIResource { Version: "v1", Name: "deployments", ShortName: "deploy", + Kind: "Deployment", Namespaced: true, Items: items, } @@ -126,6 +133,7 @@ func Namespaces(items ...metav1.Object) *APIResource { Version: "v1", Name: "namespaces", ShortName: "ns", + Kind: "Namespace", Namespaced: false, Items: items, } @@ -137,6 +145,7 @@ func ServiceAccounts(items ...metav1.Object) *APIResource { Version: "v1", Name: "serviceaccounts", ShortName: "sa", + Kind: "ServiceAccount", Namespaced: true, Items: items, } @@ -148,6 +157,7 @@ func ConfigMaps(items ...metav1.Object) *APIResource { Version: "v1", Name: "configmaps", ShortName: "cm", + Kind: "ConfigMap", Namespaced: true, Items: items, } @@ -159,6 +169,7 @@ func CRDs(items ...metav1.Object) *APIResource { Version: "v1beta1", Name: "customresourcedefinitions", ShortName: "crd", + Kind: "CustomResourceDefinition", Namespaced: false, Items: items, } @@ -169,6 +180,7 @@ func VSLs(items ...metav1.Object) *APIResource { Group: "velero.io", Version: "v1", Name: "volumesnapshotlocations", + Kind: "VolumeSnapshotLocation", Namespaced: true, Items: items, } @@ -179,6 +191,7 @@ func Backups(items ...metav1.Object) *APIResource { Group: "velero.io", Version: "v1", Name: "backups", + Kind: "Backup", Namespaced: true, Items: items, } @@ -190,6 +203,7 @@ func Services(items ...metav1.Object) *APIResource { Version: "v1", Name: "services", ShortName: "svc", + Kind: "Service", Namespaced: true, Items: items, } @@ -200,6 +214,7 @@ func DataUploads(items ...metav1.Object) *APIResource { Group: "velero.io", Version: "v2alpha1", Name: "datauploads", + Kind: "DataUpload", Namespaced: true, Items: items, } From 270b1de6a1fbfd672b2c88f46ed1ffbbdfac864c Mon Sep 17 00:00:00 2001 From: Tiger Kaovilai Date: Mon, 22 Jan 2024 08:50:13 +0700 Subject: [PATCH 17/32] Do not attempt restore resource with no available GVK in cluster (#7322) Check for GVK before attempting restore. Signed-off-by: Tiger Kaovilai --- changelogs/unreleased/7322-kaovilai | 1 + pkg/restore/restore.go | 14 ++++++++++++++ pkg/test/resources.go | 1 + 3 files changed, 16 insertions(+) create mode 100644 changelogs/unreleased/7322-kaovilai diff --git a/changelogs/unreleased/7322-kaovilai b/changelogs/unreleased/7322-kaovilai new file mode 100644 index 000000000..000c28709 --- /dev/null +++ b/changelogs/unreleased/7322-kaovilai @@ -0,0 +1 @@ +Check resource Group Version and Kind is available in cluster before attempting restore to prevent being stuck. \ No newline at end of file diff --git a/pkg/restore/restore.go b/pkg/restore/restore.go index 4d739290e..318dec279 100644 --- a/pkg/restore/restore.go +++ b/pkg/restore/restore.go @@ -1059,6 +1059,16 @@ func (ctx *restoreContext) getResourceClient(groupResource schema.GroupResource, } func (ctx *restoreContext) getResourceLister(groupResource schema.GroupResource, obj *unstructured.Unstructured, namespace string) cache.GenericNamespaceLister { + _, _, err := ctx.discoveryHelper.KindFor(schema.GroupVersionKind{ + Group: obj.GroupVersionKind().Group, + Version: obj.GetAPIVersion(), + Kind: obj.GetKind(), + }) + clusterHasKind := err == nil + if !clusterHasKind { + ctx.log.Errorf("Cannot get resource lister %s because GVK doesn't exist in the cluster", groupResource) + return nil + } informer := ctx.dynamicInformerFactory.factory.ForResource(groupResource.WithVersion(obj.GroupVersionKind().Version)) // if the restore contains CRDs or the RIA returns new resources, need to make sure the corresponding informers are synced if !informer.Informer().HasSynced() { @@ -1084,6 +1094,10 @@ func getResourceID(groupResource schema.GroupResource, namespace, name string) s func (ctx *restoreContext) getResource(groupResource schema.GroupResource, obj *unstructured.Unstructured, namespace, name string) (*unstructured.Unstructured, error) { lister := ctx.getResourceLister(groupResource, obj, namespace) + if lister == nil { + // getResourceLister logs the error, this func returns error to the caller to trigger partiallyFailed. + return nil, errors.Errorf("Error getting lister for %s because no informer for GVK found", getResourceID(groupResource, namespace, name)) + } clusterObj, err := lister.Get(name) if err != nil { return nil, errors.Wrapf(err, "error getting resource from lister for %s, %s/%s", groupResource, namespace, name) diff --git a/pkg/test/resources.go b/pkg/test/resources.go index dc1c8e29d..fe2ad6352 100644 --- a/pkg/test/resources.go +++ b/pkg/test/resources.go @@ -51,6 +51,7 @@ func Pods(items ...metav1.Object) *APIResource { ShortName: "po", Namespaced: true, Items: items, + Kind: "Pod", } } From 673bfefd450faa8605c02749da27eb7eb5e764fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wenkai=20Yin=28=E5=B0=B9=E6=96=87=E5=BC=80=29?= Date: Mon, 22 Jan 2024 11:12:00 +0800 Subject: [PATCH 18/32] Log the error got from the discovery helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Log the error got from the discovery helper Signed-off-by: Wenkai Yin(尹文开) --- pkg/restore/restore.go | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/pkg/restore/restore.go b/pkg/restore/restore.go index 318dec279..511add265 100644 --- a/pkg/restore/restore.go +++ b/pkg/restore/restore.go @@ -1058,16 +1058,14 @@ func (ctx *restoreContext) getResourceClient(groupResource schema.GroupResource, return client, nil } -func (ctx *restoreContext) getResourceLister(groupResource schema.GroupResource, obj *unstructured.Unstructured, namespace string) cache.GenericNamespaceLister { +func (ctx *restoreContext) getResourceLister(groupResource schema.GroupResource, obj *unstructured.Unstructured, namespace string) (cache.GenericNamespaceLister, error) { _, _, err := ctx.discoveryHelper.KindFor(schema.GroupVersionKind{ Group: obj.GroupVersionKind().Group, Version: obj.GetAPIVersion(), Kind: obj.GetKind(), }) - clusterHasKind := err == nil - if !clusterHasKind { - ctx.log.Errorf("Cannot get resource lister %s because GVK doesn't exist in the cluster", groupResource) - return nil + if err != nil { + return nil, err } informer := ctx.dynamicInformerFactory.factory.ForResource(groupResource.WithVersion(obj.GroupVersionKind().Version)) // if the restore contains CRDs or the RIA returns new resources, need to make sure the corresponding informers are synced @@ -1078,10 +1076,9 @@ func (ctx *restoreContext) getResourceLister(groupResource schema.GroupResource, } if namespace == "" { - return informer.Lister() - } else { - return informer.Lister().ByNamespace(namespace) + return informer.Lister(), nil } + return informer.Lister().ByNamespace(namespace), nil } func getResourceID(groupResource schema.GroupResource, namespace, name string) string { @@ -1093,10 +1090,9 @@ func getResourceID(groupResource schema.GroupResource, namespace, name string) s } func (ctx *restoreContext) getResource(groupResource schema.GroupResource, obj *unstructured.Unstructured, namespace, name string) (*unstructured.Unstructured, error) { - lister := ctx.getResourceLister(groupResource, obj, namespace) - if lister == nil { - // getResourceLister logs the error, this func returns error to the caller to trigger partiallyFailed. - return nil, errors.Errorf("Error getting lister for %s because no informer for GVK found", getResourceID(groupResource, namespace, name)) + lister, err := ctx.getResourceLister(groupResource, obj, namespace) + if err != nil { + return nil, errors.Wrapf(err, "Error getting lister for %s", getResourceID(groupResource, namespace, name)) } clusterObj, err := lister.Get(name) if err != nil { From c8ad69ab0445eff90c3511cae1703bc8e66d6362 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wenkai=20Yin=28=E5=B0=B9=E6=96=87=E5=BC=80=29?= Date: Tue, 23 Jan 2024 17:04:04 +0800 Subject: [PATCH 19/32] Check whether the API resource exists before creating the informer cache MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Check whether the API resource exists before creating the informer cache Signed-off-by: Wenkai Yin(尹文开) --- pkg/restore/restore.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pkg/restore/restore.go b/pkg/restore/restore.go index 511add265..67ab0e223 100644 --- a/pkg/restore/restore.go +++ b/pkg/restore/restore.go @@ -593,6 +593,11 @@ func (ctx *restoreContext) execute() (results.Result, results.Result) { break } gvr := schema.ParseGroupResource(informerResource.resource).WithVersion(version) + _, _, err := ctx.discoveryHelper.ResourceFor(gvr) + if err != nil { + ctx.log.Infof("failed to create informer for %s: %v", gvr, err) + continue + } ctx.dynamicInformerFactory.factory.ForResource(gvr) } ctx.dynamicInformerFactory.factory.Start(ctx.dynamicInformerFactory.context.Done()) @@ -1059,11 +1064,7 @@ func (ctx *restoreContext) getResourceClient(groupResource schema.GroupResource, } func (ctx *restoreContext) getResourceLister(groupResource schema.GroupResource, obj *unstructured.Unstructured, namespace string) (cache.GenericNamespaceLister, error) { - _, _, err := ctx.discoveryHelper.KindFor(schema.GroupVersionKind{ - Group: obj.GroupVersionKind().Group, - Version: obj.GetAPIVersion(), - Kind: obj.GetKind(), - }) + _, _, err := ctx.discoveryHelper.KindFor(obj.GroupVersionKind()) if err != nil { return nil, err } From df585053e7ad18e21adfeec5194fc7c2789fb49b Mon Sep 17 00:00:00 2001 From: danfengl Date: Thu, 4 Jan 2024 02:46:06 +0000 Subject: [PATCH 20/32] Add a new EKS pipeline with IRSA as credential Signed-off-by: danfengl --- test/e2e/Makefile | 12 +++- .../api-group/enable_api_group_extentions.go | 26 ++++---- test/e2e/e2e_suite_test.go | 20 ++++-- test/e2e/migration/migration.go | 27 ++++---- test/perf/e2e_suite_test.go | 2 +- test/types.go | 10 ++- test/util/eks/eks.go | 65 +++++++++++++++++++ test/util/k8s/clusterrolebinding.go | 47 ++++++++++++++ test/util/k8s/namespace.go | 8 ++- test/util/providers/common.go | 16 +++++ test/util/velero/install.go | 65 ++++++++++++------- test/util/velero/velero_utils.go | 43 +++++++++--- 12 files changed, 271 insertions(+), 70 deletions(-) create mode 100644 test/util/eks/eks.go create mode 100644 test/util/k8s/clusterrolebinding.go diff --git a/test/e2e/Makefile b/test/e2e/Makefile index 7bf80c351..e00e29432 100644 --- a/test/e2e/Makefile +++ b/test/e2e/Makefile @@ -67,6 +67,7 @@ MIGRATE_FROM_VELERO_CLI ?= VELERO_NAMESPACE ?= velero CREDS_FILE ?= +SERVICE_ACCOUNT_NAME ?= BSL_BUCKET ?= BSL_PREFIX ?= BSL_CONFIG ?= @@ -106,6 +107,10 @@ SNAPSHOT_MOVE_DATA ?= false DATA_MOVER_PLUGIN ?= DISABLE_INFORMER_CACHE ?= false +DEFAULT_CLUSTER_NAME ?= +STANDBY_CLUSTER_NAME ?= +EKS_POLICY_ARN ?= + .PHONY:ginkgo ginkgo: # Make sure ginkgo is in $GOPATH/bin @@ -157,8 +162,11 @@ run: ginkgo -standby-cluster-plugins=$(STANDBY_CLUSTER_PLUGINS) \ -standby-cluster-object-store-provider=$(STANDBY_CLUSTER_OBJECT_STORE_PROVIDER) \ -debug-velero-pod-restart=$(DEBUG_VELERO_POD_RESTART) \ - -disable-informer-cache=$(DISABLE_INFORMER_CACHE) - + -disable-informer-cache=$(DISABLE_INFORMER_CACHE) \ + -default-cluster-name=$(DEFAULT_CLUSTER_NAME) \ + -standby-cluster-name=$(STANDBY_CLUSTER_NAME) \ + -eks-policy-arn=$(EKS_POLICY_ARN) \ + -service-account-name=$(SERVICE_ACCOUNT_NAME) build: ginkgo mkdir -p $(OUTPUT_DIR) diff --git a/test/e2e/basic/api-group/enable_api_group_extentions.go b/test/e2e/basic/api-group/enable_api_group_extentions.go index 5c77438fc..af6d32304 100644 --- a/test/e2e/basic/api-group/enable_api_group_extentions.go +++ b/test/e2e/basic/api-group/enable_api_group_extentions.go @@ -39,11 +39,11 @@ func APIExtensionsVersionsTest() { label := "for=backup" srcCrdYaml := "testdata/enable_api_group_versions/case-a-source-v1beta1.yaml" BeforeEach(func() { - if veleroCfg.DefaultCluster == "" && veleroCfg.StandbyCluster == "" { + if veleroCfg.DefaultClusterContext == "" && veleroCfg.StandbyClusterContext == "" { Skip("CRD with apiextension versions migration test needs 2 clusters") } veleroCfg = VeleroCfg - Expect(KubectlConfigUseContext(context.Background(), veleroCfg.DefaultCluster)).To(Succeed()) + Expect(KubectlConfigUseContext(context.Background(), veleroCfg.DefaultClusterContext)).To(Succeed()) srcVersions, err := GetAPIVersions(veleroCfg.DefaultClient, resourceName) Expect(err).ShouldNot(HaveOccurred()) dstVersions, err := GetAPIVersions(veleroCfg.StandbyClient, resourceName) @@ -75,19 +75,19 @@ func APIExtensionsVersionsTest() { By("Uninstall Velero and delete CRD ", func() { ctx, ctxCancel := context.WithTimeout(context.Background(), time.Minute*5) defer ctxCancel() - Expect(KubectlConfigUseContext(context.Background(), veleroCfg.DefaultCluster)).To(Succeed()) + Expect(KubectlConfigUseContext(context.Background(), veleroCfg.DefaultClusterContext)).To(Succeed()) Expect(VeleroUninstall(ctx, veleroCfg.VeleroCLI, veleroCfg.VeleroNamespace)).To(Succeed()) Expect(DeleteCRDByName(context.Background(), crdName)).To(Succeed()) - Expect(KubectlConfigUseContext(context.Background(), veleroCfg.StandbyCluster)).To(Succeed()) + Expect(KubectlConfigUseContext(context.Background(), veleroCfg.StandbyClusterContext)).To(Succeed()) Expect(VeleroUninstall(ctx, veleroCfg.VeleroCLI, veleroCfg.VeleroNamespace)).To(Succeed()) Expect(DeleteCRDByName(context.Background(), crdName)).To(Succeed()) }) } - By(fmt.Sprintf("Switch to default kubeconfig context %s", veleroCfg.DefaultCluster), func() { - Expect(KubectlConfigUseContext(context.Background(), veleroCfg.DefaultCluster)).To(Succeed()) + By(fmt.Sprintf("Switch to default kubeconfig context %s", veleroCfg.DefaultClusterContext), func() { + Expect(KubectlConfigUseContext(context.Background(), veleroCfg.DefaultClusterContext)).To(Succeed()) veleroCfg.ClientToInstallVelero = veleroCfg.DefaultClient }) } @@ -98,14 +98,14 @@ func APIExtensionsVersionsTest() { backupName = "backup-" + UUIDgen.String() restoreName = "restore-" + UUIDgen.String() - By(fmt.Sprintf("Install Velero in cluster-A (%s) to backup workload", veleroCfg.DefaultCluster), func() { - Expect(KubectlConfigUseContext(context.Background(), veleroCfg.DefaultCluster)).To(Succeed()) + By(fmt.Sprintf("Install Velero in cluster-A (%s) to backup workload", veleroCfg.DefaultClusterContext), func() { + Expect(KubectlConfigUseContext(context.Background(), veleroCfg.DefaultClusterContext)).To(Succeed()) veleroCfg.Features = "EnableAPIGroupVersions" veleroCfg.UseVolumeSnapshots = false Expect(VeleroInstall(context.Background(), &veleroCfg, false)).To(Succeed()) }) - By(fmt.Sprintf("Install CRD of apiextenstions v1beta1 in cluster-A (%s)", veleroCfg.DefaultCluster), func() { + By(fmt.Sprintf("Install CRD of apiextenstions v1beta1 in cluster-A (%s)", veleroCfg.DefaultClusterContext), func() { Expect(InstallCRD(context.Background(), srcCrdYaml)).To(Succeed()) Expect(CRDShouldExist(context.Background(), crdName)).To(Succeed()) Expect(WaitForCRDEstablished(crdName)).To(Succeed()) @@ -128,17 +128,17 @@ func APIExtensionsVersionsTest() { }) }) - By(fmt.Sprintf("Install Velero in cluster-B (%s) to restore workload", veleroCfg.StandbyCluster), func() { - Expect(KubectlConfigUseContext(context.Background(), veleroCfg.StandbyCluster)).To(Succeed()) + By(fmt.Sprintf("Install Velero in cluster-B (%s) to restore workload", veleroCfg.StandbyClusterContext), func() { + Expect(KubectlConfigUseContext(context.Background(), veleroCfg.StandbyClusterContext)).To(Succeed()) veleroCfg.ClientToInstallVelero = veleroCfg.StandbyClient Expect(VeleroInstall(context.Background(), &veleroCfg, false)).To(Succeed()) }) - By(fmt.Sprintf("Waiting for backups sync to Velero in cluster-B (%s)", veleroCfg.StandbyCluster), func() { + By(fmt.Sprintf("Waiting for backups sync to Velero in cluster-B (%s)", veleroCfg.StandbyClusterContext), func() { Expect(WaitForBackupToBeCreated(context.Background(), veleroCfg.VeleroCLI, backupName, 5*time.Minute)).To(Succeed()) }) - By(fmt.Sprintf("CRD %s should not exist in cluster-B (%s)", crdName, veleroCfg.StandbyCluster), func() { + By(fmt.Sprintf("CRD %s should not exist in cluster-B (%s)", crdName, veleroCfg.StandbyClusterContext), func() { Expect(CRDShouldNotExist(context.Background(), crdName)).To(Succeed(), "Error: CRD already exists in cluster B, clean it and re-run test") }) diff --git a/test/e2e/e2e_suite_test.go b/test/e2e/e2e_suite_test.go index 9464dfd10..d285a8324 100644 --- a/test/e2e/e2e_suite_test.go +++ b/test/e2e/e2e_suite_test.go @@ -84,8 +84,8 @@ func init() { flag.StringVar(&VeleroCfg.Features, "features", "", "comma-separated list of features to enable for this Velero process.") flag.BoolVar(&VeleroCfg.Debug, "debug-e2e-test", false, "A Switch for enable or disable test data cleaning action.") flag.StringVar(&VeleroCfg.GCFrequency, "garbage-collection-frequency", "", "frequency of garbage collection.") - flag.StringVar(&VeleroCfg.DefaultCluster, "default-cluster", "", "default cluster's kube config context, it's for migration test.") - flag.StringVar(&VeleroCfg.StandbyCluster, "standby-cluster", "", "standby cluster's kube config context, it's for migration test.") + flag.StringVar(&VeleroCfg.DefaultClusterContext, "default-cluster", "", "default cluster's kube config context, it's for migration test.") + flag.StringVar(&VeleroCfg.StandbyClusterContext, "standby-cluster", "", "standby cluster's kube config context, it's for migration test.") flag.StringVar(&VeleroCfg.UploaderType, "uploader-type", "", "type of uploader for persistent volume backup.") flag.BoolVar(&VeleroCfg.VeleroServerDebugMode, "velero-server-debug-mode", false, "a switch for enable or disable having debug log of Velero server.") flag.BoolVar(&VeleroCfg.SnapshotMoveData, "snapshot-move-data", false, "a Switch for taking backup with Velero's data mover, if data-mover-plugin is not provided, using built-in plugin") @@ -95,6 +95,11 @@ func init() { flag.StringVar(&VeleroCfg.StandbyClusterOjbectStoreProvider, "standby-cluster-object-store-provider", "", "object store provider for standby cluster.") flag.BoolVar(&VeleroCfg.DebugVeleroPodRestart, "debug-velero-pod-restart", false, "a switch for debugging velero pod restart.") flag.BoolVar(&VeleroCfg.DisableInformerCache, "disable-informer-cache", false, "a switch for disable informer cache.") + flag.StringVar(&VeleroCfg.DefaultClusterName, "default-cluster-name", "", "default cluster's name in kube config file, it's for EKS IRSA test.") + flag.StringVar(&VeleroCfg.StandbyClusterName, "standby-cluster-name", "", "standby cluster's name in kube config file, it's for EKS IRSA test.") + flag.StringVar(&VeleroCfg.EKSPolicyARN, "eks-policy-arn", "", "EKS plicy ARN for creating AWS IAM service account.") + flag.StringVar(&VeleroCfg.ServiceAccountName, "service-account-name", "", "service account name.") + } var _ = Describe("[APIGroup][APIVersion] Velero tests with various CRD API group versions", APIGropuVersionsTest) @@ -164,20 +169,21 @@ var _ = Describe("[Basic][SelectedNode] Node selectors of persistent volume clai func GetKubeconfigContext() error { var err error var tcDefault, tcStandby TestClient - tcDefault, err = NewTestClient(VeleroCfg.DefaultCluster) + tcDefault, err = NewTestClient(VeleroCfg.DefaultClusterContext) VeleroCfg.DefaultClient = &tcDefault VeleroCfg.ClientToInstallVelero = VeleroCfg.DefaultClient + VeleroCfg.ClusterToInstallVelero = VeleroCfg.DefaultClusterName if err != nil { return err } - if VeleroCfg.DefaultCluster != "" { - err = KubectlConfigUseContext(context.Background(), VeleroCfg.DefaultCluster) + if VeleroCfg.DefaultClusterContext != "" { + err = KubectlConfigUseContext(context.Background(), VeleroCfg.DefaultClusterContext) if err != nil { return err } - if VeleroCfg.StandbyCluster != "" { - tcStandby, err = NewTestClient(VeleroCfg.StandbyCluster) + if VeleroCfg.StandbyClusterContext != "" { + tcStandby, err = NewTestClient(VeleroCfg.StandbyClusterContext) VeleroCfg.StandbyClient = &tcStandby if err != nil { return err diff --git a/test/e2e/migration/migration.go b/test/e2e/migration/migration.go index 202cad701..1bfb11e29 100644 --- a/test/e2e/migration/migration.go +++ b/test/e2e/migration/migration.go @@ -64,7 +64,7 @@ func MigrationTest(useVolumeSnapshots bool, veleroCLI2Version VeleroCLI2Version) Skip("Volume snapshots not supported on kind") } - if veleroCfg.DefaultCluster == "" && veleroCfg.StandbyCluster == "" { + if veleroCfg.DefaultClusterContext == "" && veleroCfg.StandbyClusterContext == "" { Skip("Migration test needs 2 clusters") } // need to uninstall Velero first in case of the affection of the existing global velero installation @@ -79,19 +79,19 @@ func MigrationTest(useVolumeSnapshots bool, veleroCLI2Version VeleroCLI2Version) }) AfterEach(func() { if !veleroCfg.Debug { - By(fmt.Sprintf("Uninstall Velero on cluster %s", veleroCfg.DefaultCluster), func() { + By(fmt.Sprintf("Uninstall Velero on cluster %s", veleroCfg.DefaultClusterContext), func() { ctx, ctxCancel := context.WithTimeout(context.Background(), time.Minute*5) defer ctxCancel() - Expect(KubectlConfigUseContext(context.Background(), veleroCfg.DefaultCluster)).To(Succeed()) + Expect(KubectlConfigUseContext(context.Background(), veleroCfg.DefaultClusterContext)).To(Succeed()) Expect(VeleroUninstall(ctx, veleroCfg.VeleroCLI, veleroCfg.VeleroNamespace)).To(Succeed()) DeleteNamespace(context.Background(), *veleroCfg.DefaultClient, migrationNamespace, true) }) - By(fmt.Sprintf("Uninstall Velero on cluster %s", veleroCfg.StandbyCluster), func() { + By(fmt.Sprintf("Uninstall Velero on cluster %s", veleroCfg.StandbyClusterContext), func() { ctx, ctxCancel := context.WithTimeout(context.Background(), time.Minute*5) defer ctxCancel() - Expect(KubectlConfigUseContext(context.Background(), veleroCfg.StandbyCluster)).To(Succeed()) + Expect(KubectlConfigUseContext(context.Background(), veleroCfg.StandbyClusterContext)).To(Succeed()) Expect(VeleroUninstall(ctx, veleroCfg.VeleroCLI, veleroCfg.VeleroNamespace)).To(Succeed()) DeleteNamespace(context.Background(), *veleroCfg.StandbyClient, migrationNamespace, true) @@ -102,9 +102,10 @@ func MigrationTest(useVolumeSnapshots bool, veleroCLI2Version VeleroCLI2Version) DeleteNamespace(context.Background(), *veleroCfg.StandbyClient, migrationNamespace, true) }) } - By(fmt.Sprintf("Switch to default kubeconfig context %s", veleroCfg.DefaultCluster), func() { - Expect(KubectlConfigUseContext(context.Background(), veleroCfg.DefaultCluster)).To(Succeed()) + By(fmt.Sprintf("Switch to default kubeconfig context %s", veleroCfg.DefaultClusterContext), func() { + Expect(KubectlConfigUseContext(context.Background(), veleroCfg.DefaultClusterContext)).To(Succeed()) veleroCfg.ClientToInstallVelero = veleroCfg.DefaultClient + veleroCfg.ClusterToInstallVelero = veleroCfg.DefaultClusterName }) } @@ -142,11 +143,12 @@ func MigrationTest(useVolumeSnapshots bool, veleroCLI2Version VeleroCLI2Version) }) } OriginVeleroCfg := veleroCfg - By(fmt.Sprintf("Install Velero in cluster-A (%s) to backup workload", veleroCfg.DefaultCluster), func() { - Expect(KubectlConfigUseContext(context.Background(), veleroCfg.DefaultCluster)).To(Succeed()) + By(fmt.Sprintf("Install Velero in cluster-A (%s) to backup workload", veleroCfg.DefaultClusterContext), func() { + Expect(KubectlConfigUseContext(context.Background(), veleroCfg.DefaultClusterContext)).To(Succeed()) OriginVeleroCfg.MigrateFromVeleroVersion = veleroCLI2Version.VeleroVersion OriginVeleroCfg.VeleroCLI = veleroCLI2Version.VeleroCLI OriginVeleroCfg.ClientToInstallVelero = OriginVeleroCfg.DefaultClient + OriginVeleroCfg.ClusterToInstallVelero = veleroCfg.DefaultClusterName OriginVeleroCfg.UseVolumeSnapshots = useVolumeSnapshots OriginVeleroCfg.UseNodeAgent = !useVolumeSnapshots @@ -272,20 +274,21 @@ func MigrationTest(useVolumeSnapshots bool, veleroCLI2Version VeleroCLI2Version) time.Sleep(5 * time.Minute) } - By(fmt.Sprintf("Install Velero in cluster-B (%s) to restore workload", veleroCfg.StandbyCluster), func() { + By(fmt.Sprintf("Install Velero in cluster-B (%s) to restore workload", veleroCfg.StandbyClusterContext), func() { //Ensure workload of "migrationNamespace" existed in cluster-A ns, err := GetNamespace(context.Background(), *veleroCfg.DefaultClient, migrationNamespace) Expect(ns.Name).To(Equal(migrationNamespace)) Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("get namespace in cluster-B err: %v", err)) //Ensure cluster-B is the target cluster - Expect(KubectlConfigUseContext(context.Background(), veleroCfg.StandbyCluster)).To(Succeed()) + Expect(KubectlConfigUseContext(context.Background(), veleroCfg.StandbyClusterContext)).To(Succeed()) _, err = GetNamespace(context.Background(), *veleroCfg.StandbyClient, migrationNamespace) Expect(err).To(HaveOccurred()) strings.Contains(fmt.Sprint(err), "namespaces \""+migrationNamespace+"\" not found") fmt.Println(err) veleroCfg.ClientToInstallVelero = veleroCfg.StandbyClient + veleroCfg.ClusterToInstallVelero = veleroCfg.StandbyClusterName veleroCfg.UseNodeAgent = !useVolumeSnapshots veleroCfg.UseRestic = false if veleroCfg.SnapshotMoveData { @@ -299,7 +302,7 @@ func MigrationTest(useVolumeSnapshots bool, veleroCLI2Version VeleroCLI2Version) Expect(VeleroInstall(context.Background(), &veleroCfg, true)).To(Succeed()) }) - By(fmt.Sprintf("Waiting for backups sync to Velero in cluster-B (%s)", veleroCfg.StandbyCluster), func() { + By(fmt.Sprintf("Waiting for backups sync to Velero in cluster-B (%s)", veleroCfg.StandbyClusterContext), func() { Expect(WaitForBackupToBeCreated(context.Background(), veleroCfg.VeleroCLI, backupName, 5*time.Minute)).To(Succeed()) Expect(WaitForBackupToBeCreated(context.Background(), veleroCfg.VeleroCLI, backupScName, 5*time.Minute)).To(Succeed()) }) diff --git a/test/perf/e2e_suite_test.go b/test/perf/e2e_suite_test.go index 7d1cae16f..f05f7ffe0 100644 --- a/test/perf/e2e_suite_test.go +++ b/test/perf/e2e_suite_test.go @@ -69,7 +69,7 @@ func init() { flag.DurationVar(&VeleroCfg.PodVolumeOperationTimeout, "pod-volume-operation-timeout", 360*time.Minute, "Timeout for pod volume operations. Optional.") //vmware-tanzu-experiments flag.StringVar(&VeleroCfg.Features, "features", "", "Comma-separated list of features to enable for this Velero process.") - flag.StringVar(&VeleroCfg.DefaultCluster, "default-cluster-context", "", "Default cluster context for migration test.") + flag.StringVar(&VeleroCfg.DefaultClusterContext, "default-cluster-context", "", "Default cluster context for migration test.") flag.BoolVar(&VeleroCfg.Debug, "debug-e2e-test", true, "Switch to control namespace cleaning.") flag.StringVar(&VeleroCfg.UploaderType, "uploader-type", "kopia", "Identify persistent volume backup uploader.") flag.BoolVar(&VeleroCfg.VeleroServerDebugMode, "velero-server-debug-mode", false, "Identify persistent volume backup uploader.") diff --git a/test/types.go b/test/types.go index ed00cada6..20b3ca14c 100644 --- a/test/types.go +++ b/test/types.go @@ -71,11 +71,14 @@ type VeleroConfig struct { KibishiiDirectory string Debug bool GCFrequency string - DefaultCluster string - StandbyCluster string + DefaultClusterContext string + StandbyClusterContext string ClientToInstallVelero *TestClient DefaultClient *TestClient StandbyClient *TestClient + ClusterToInstallVelero string + DefaultClusterName string + StandbyClusterName string ProvideSnapshotsVolumeParam bool VeleroServerDebugMode bool SnapshotMoveData bool @@ -87,6 +90,9 @@ type VeleroConfig struct { IsUpgradeTest bool WithoutDisableInformerCacheParam bool DisableInformerCache bool + CreateClusterRoleBinding bool + ServiceAccountName string + EKSPolicyARN string } type VeleroCfgInPerf struct { diff --git a/test/util/eks/eks.go b/test/util/eks/eks.go new file mode 100644 index 000000000..6b83e88df --- /dev/null +++ b/test/util/eks/eks.go @@ -0,0 +1,65 @@ +/* +Copyright the Velero contributors. + +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 k8s + +import ( + "fmt" + "os/exec" + "strings" + "time" + + "golang.org/x/net/context" + "k8s.io/apimachinery/pkg/util/wait" + + veleroexec "github.com/vmware-tanzu/velero/pkg/util/exec" +) + +func KubectlDeleteIAMServiceAcount(ctx context.Context, name, namespace, cluster string) error { + args := []string{"delete", "iamserviceaccount", name, + "--namespace", namespace, "--cluster", cluster, "--wait"} + fmt.Println(args) + + cmd := exec.CommandContext(ctx, "eksctl", args...) + fmt.Println(cmd) + stdout, stderr, err := veleroexec.RunCommand(cmd) + fmt.Printf("Output: %v\n", stdout) + if strings.Contains(stderr, "NotFound") { + err = nil + } + fmt.Printf("err: %v\n", err) + return err +} + +func KubectlCreateIAMServiceAcount(ctx context.Context, name, namespace, policyARN, cluster string) error { + args := []string{"create", "iamserviceaccount", name, + "--namespace", namespace, "--cluster", cluster, "--attach-policy-arn", policyARN, + "--approve", "--override-existing-serviceaccounts"} + + PollInterval := 1 * time.Minute + PollTimeout := 10 * time.Minute + return wait.Poll(PollInterval, PollTimeout, func() (bool, error) { + cmd := exec.CommandContext(ctx, "eksctl", args...) + fmt.Println(cmd) + stdout, stderr, err := veleroexec.RunCommand(cmd) + fmt.Printf("Output: %v|%v|%v\n", stdout, stderr, err) + if err != nil { + fmt.Printf("err: %v|%v|%v\n", stdout, stderr, err) + return false, nil + } + return true, nil + }) +} diff --git a/test/util/k8s/clusterrolebinding.go b/test/util/k8s/clusterrolebinding.go new file mode 100644 index 000000000..a9fbb7962 --- /dev/null +++ b/test/util/k8s/clusterrolebinding.go @@ -0,0 +1,47 @@ +/* +Copyright the Velero contributors. + +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 k8s + +import ( + "fmt" + "os/exec" + "strings" + + "golang.org/x/net/context" + + veleroexec "github.com/vmware-tanzu/velero/pkg/util/exec" +) + +func KubectlDeleteClusterRoleBinding(ctx context.Context, name string) error { + args := []string{"delete", "clusterrolebinding", name} + fmt.Println(args) + cmd := exec.CommandContext(ctx, "kubectl", args...) + fmt.Println(cmd) + _, stderr, err := veleroexec.RunCommand(cmd) + fmt.Printf("Ignore error: %v\n", stderr) + if strings.Contains(stderr, "NotFound") { + fmt.Printf("Ignore error: %v\n", stderr) + err = nil + } + return err +} + +func KubectlCreateClusterRoleBinding(ctx context.Context, name, clusterrole, namespace, serviceaccount string) error { + args := []string{"create", "clusterrolebinding", name, fmt.Sprintf("--clusterrole=%s", clusterrole), fmt.Sprintf("--serviceaccount=%s:%s", namespace, serviceaccount)} + fmt.Println(args) + return exec.CommandContext(ctx, "kubectl", args...).Run() +} diff --git a/test/util/k8s/namespace.go b/test/util/k8s/namespace.go index 3c7686756..479ac1b8a 100644 --- a/test/util/k8s/namespace.go +++ b/test/util/k8s/namespace.go @@ -19,12 +19,12 @@ package k8s import ( "context" "fmt" + "os/exec" "strings" "time" "github.com/pkg/errors" "github.com/sirupsen/logrus" - corev1api "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -233,3 +233,9 @@ func GetMappingNamespaces(ctx context.Context, client TestClient, excludeNS []st } return joinedNsMapping, nil } + +func KubectlCreateNamespace(ctx context.Context, name string) error { + args := []string{"create", "namespace", name} + fmt.Println(args) + return exec.CommandContext(ctx, "kubectl", args...).Run() +} diff --git a/test/util/providers/common.go b/test/util/providers/common.go index 0764e70de..8ef943e0f 100644 --- a/test/util/providers/common.go +++ b/test/util/providers/common.go @@ -47,6 +47,10 @@ func ObjectsShouldNotBeInBucket(cloudProvider, cloudCredentialsFile, bslBucket, var err error var exist bool fmt.Printf("|| VERIFICATION || - %s %s should not exist in object store %s\n", subPrefix, backupName, bslPrefix) + if cloudCredentialsFile == "" { + fmt.Printf("|| SKIPPED || - Skipping object storebackup checkpoint %s\n", backupName) + return nil + } for i := 0; i < retryTimes; i++ { exist, err = IsObjectsInBucket(cloudProvider, cloudCredentialsFile, bslBucket, bslPrefix, bslConfig, backupName, subPrefix) if err != nil { @@ -98,6 +102,10 @@ func IsObjectsInBucket(cloudProvider, cloudCredentialsFile, bslBucket, bslPrefix func DeleteObjectsInBucket(cloudProvider, cloudCredentialsFile, bslBucket, bslPrefix, bslConfig, backupName, subPrefix string) error { bslPrefix = getFullPrefix(bslPrefix, subPrefix) fmt.Printf("|| VERIFICATION || - Delete backup %s in storage %s\n", backupName, bslPrefix) + if cloudCredentialsFile == "" { + fmt.Printf("|| SKIPPED || - Skipping snapshots checkpoint %s\n", backupName) + return nil + } s, err := getProvider(cloudProvider) if err != nil { return errors.Wrapf(err, fmt.Sprintf("Cloud provider %s is not valid", cloudProvider)) @@ -111,6 +119,10 @@ func DeleteObjectsInBucket(cloudProvider, cloudCredentialsFile, bslBucket, bslPr func SnapshotsShouldNotExistInCloud(cloudProvider, cloudCredentialsFile, bslBucket, bslConfig, backupName string, snapshotCheckPoint SnapshotCheckPoint) error { fmt.Printf("|| VERIFICATION || - Snapshots should not exist in cloud, backup %s\n", backupName) + if cloudCredentialsFile == "" { + fmt.Printf("|| SKIPPED || - Skipping snapshots checkpoint %s\n", backupName) + return nil + } snapshotCheckPoint.ExpectCount = 0 err := IsSnapshotExisted(cloudProvider, cloudCredentialsFile, bslBucket, bslConfig, backupName, snapshotCheckPoint) if err != nil { @@ -122,6 +134,10 @@ func SnapshotsShouldNotExistInCloud(cloudProvider, cloudCredentialsFile, bslBuck func SnapshotsShouldBeCreatedInCloud(cloudProvider, cloudCredentialsFile, bslBucket, bslConfig, backupName string, snapshotCheckPoint SnapshotCheckPoint) error { fmt.Printf("|| VERIFICATION || - Snapshots should exist in cloud, backup %s\n", backupName) + if cloudCredentialsFile == "" { + fmt.Printf("|| SKIPPED || - Skipping snapshots checkpoint %s\n", backupName) + return nil + } err := IsSnapshotExisted(cloudProvider, cloudCredentialsFile, bslBucket, bslConfig, backupName, snapshotCheckPoint) if err != nil { return errors.Wrapf(err, fmt.Sprintf("|| UNEXPECTED ||Snapshots %s do not exist in cloud after backup as expected", backupName)) diff --git a/test/util/velero/install.go b/test/util/velero/install.go index 9c621259c..7eab157ea 100644 --- a/test/util/velero/install.go +++ b/test/util/velero/install.go @@ -39,6 +39,7 @@ import ( "github.com/vmware-tanzu/velero/pkg/cmd/cli/install" velerexec "github.com/vmware-tanzu/velero/pkg/util/exec" . "github.com/vmware-tanzu/velero/test" + . "github.com/vmware-tanzu/velero/test/util/eks" . "github.com/vmware-tanzu/velero/test/util/k8s" ) @@ -75,7 +76,7 @@ func VeleroInstall(ctx context.Context, veleroCfg *VeleroConfig, isStandbyCluste veleroCfg.CloudProvider = veleroCfg.StandbyClusterCloudProvider } if veleroCfg.CloudProvider != "kind" { - fmt.Printf("For cloud platforms, object store plugin provider will be set as cloud provider") + fmt.Printf("For cloud platforms, object store plugin provider will be set as cloud provider\n") // If ObjectStoreProvider is not provided, then using the value same as CloudProvider if veleroCfg.ObjectStoreProvider == "" { veleroCfg.ObjectStoreProvider = veleroCfg.CloudProvider @@ -112,27 +113,35 @@ func VeleroInstall(ctx context.Context, veleroCfg *VeleroConfig, isStandbyCluste if err != nil { return errors.WithMessagef(err, "Failed to get Velero InstallOptions for plugin provider %s", veleroCfg.ObjectStoreProvider) } - veleroInstallOptions.UseVolumeSnapshots = veleroCfg.UseVolumeSnapshots - if !veleroCfg.UseRestic { - veleroInstallOptions.UseNodeAgent = veleroCfg.UseNodeAgent - } - veleroInstallOptions.UseRestic = veleroCfg.UseRestic - veleroInstallOptions.Image = veleroCfg.VeleroImage - veleroInstallOptions.Namespace = veleroCfg.VeleroNamespace - veleroInstallOptions.UploaderType = veleroCfg.UploaderType - GCFrequency, _ := time.ParseDuration(veleroCfg.GCFrequency) - veleroInstallOptions.GarbageCollectionFrequency = GCFrequency - veleroInstallOptions.PodVolumeOperationTimeout = veleroCfg.PodVolumeOperationTimeout - veleroInstallOptions.NodeAgentPodCPULimit = veleroCfg.NodeAgentPodCPULimit - veleroInstallOptions.NodeAgentPodCPURequest = veleroCfg.NodeAgentPodCPURequest - veleroInstallOptions.NodeAgentPodMemLimit = veleroCfg.NodeAgentPodMemLimit - veleroInstallOptions.NodeAgentPodMemRequest = veleroCfg.NodeAgentPodMemRequest - veleroInstallOptions.VeleroPodCPULimit = veleroCfg.VeleroPodCPULimit - veleroInstallOptions.VeleroPodCPURequest = veleroCfg.VeleroPodCPURequest - veleroInstallOptions.VeleroPodMemLimit = veleroCfg.VeleroPodMemLimit - veleroInstallOptions.VeleroPodMemRequest = veleroCfg.VeleroPodMemRequest - veleroInstallOptions.DisableInformerCache = veleroCfg.DisableInformerCache + if veleroCfg.CloudProvider == "aws" && veleroInstallOptions.ServiceAccountName != "" { + _, err = GetNamespace(ctx, *veleroCfg.ClientToInstallVelero, veleroCfg.VeleroNamespace) + if !apierrors.IsNotFound(err) { + if err := VeleroUninstall(context.Background(), veleroCfg.VeleroCLI, veleroCfg.VeleroNamespace); err != nil { + return errors.Wrapf(err, "Failed to uninstall velero %s", veleroCfg.VeleroNamespace) + } + } + if err := KubectlCreateNamespace(ctx, veleroCfg.VeleroNamespace); err != nil { + return errors.Wrapf(err, "Failed to create namespace %s to install Velero", veleroCfg.VeleroNamespace) + } + if err := KubectlDeleteClusterRoleBinding(ctx, "velero-cluster-role"); err != nil { + fmt.Println(err) + return errors.Wrapf(err, "Failed to delete clusterrolebinding %s to %s namesapce", "velero-cluster-role", veleroCfg.VeleroNamespace) + } + if err := KubectlCreateClusterRoleBinding(ctx, "velero-cluster-role", "cluster-admin", veleroCfg.VeleroNamespace, veleroInstallOptions.ServiceAccountName); err != nil { + fmt.Println(err) + return errors.Wrapf(err, "Failed to create clusterrolebinding %s to %s namesapce", "velero-cluster-role", veleroCfg.VeleroNamespace) + } + if err := KubectlDeleteIAMServiceAcount(ctx, veleroCfg.ServiceAccountName, veleroCfg.VeleroNamespace, veleroCfg.ClusterToInstallVelero); err != nil { + fmt.Println(err) + return errors.Wrapf(err, "Failed to delete service account %s to %s namesapce", veleroCfg.ServiceAccountName, veleroCfg.VeleroNamespace) + } + time.Sleep(10 * time.Second) + if err := KubectlCreateIAMServiceAcount(ctx, veleroCfg.ServiceAccountName, veleroCfg.VeleroNamespace, veleroCfg.EKSPolicyARN, veleroCfg.ClusterToInstallVelero); err != nil { + fmt.Println(err) + return errors.Wrapf(err, "Failed to create service account %s to %s namesapce", veleroCfg.ServiceAccountName, veleroCfg.VeleroNamespace) + } + } err = installVeleroServer(ctx, veleroCfg.VeleroCLI, veleroCfg.CloudProvider, &installOptions{ Options: veleroInstallOptions, RegistryCredentialFile: veleroCfg.RegistryCredentialFile, @@ -239,8 +248,16 @@ func installVeleroServer(ctx context.Context, cli, cloudProvider string, options if len(options.Prefix) > 0 { args = append(args, "--prefix", options.Prefix) } - if len(options.SecretFile) > 0 { - args = append(args, "--secret-file", options.SecretFile) + //Treat ServiceAccountName priority higher than SecretFile + if len(options.ServiceAccountName) > 0 { + args = append(args, "--service-account-name", options.ServiceAccountName) + } else { + if len(options.SecretFile) > 0 { + args = append(args, "--secret-file", options.SecretFile) + } + } + if options.NoSecret { + args = append(args, "--no-secret") } if len(options.VolumeSnapshotConfig.Data()) > 0 { args = append(args, "--snapshot-location-config", options.VolumeSnapshotConfig.String()) @@ -257,11 +274,11 @@ func installVeleroServer(ctx context.Context, cli, cloudProvider string, options } } - fmt.Println("Start to install Azure VolumeSnapshotClass ...") if len(options.Features) > 0 { args = append(args, "--features", options.Features) if strings.EqualFold(options.Features, FeatureCSI) && options.UseVolumeSnapshots { if strings.EqualFold(cloudProvider, "azure") { + fmt.Println("Start to install Azure VolumeSnapshotClass ...") if err := KubectlApplyByFile(ctx, "../util/csi/AzureVolumeSnapshotClass.yaml"); err != nil { return err } diff --git a/test/util/velero/velero_utils.go b/test/util/velero/velero_utils.go index 61e1db22a..6eb924ac8 100644 --- a/test/util/velero/velero_utils.go +++ b/test/util/velero/velero_utils.go @@ -174,20 +174,27 @@ func getPluginsByVersion(version, cloudProvider, objectStoreProvider, feature st func getProviderVeleroInstallOptions(veleroCfg *VeleroConfig, plugins []string) (*cliinstall.Options, error) { - if veleroCfg.CloudCredentialsFile == "" { + if veleroCfg.CloudCredentialsFile == "" && veleroCfg.ServiceAccountName == "" { return nil, errors.Errorf("No credentials were supplied to use for E2E tests") } - realPath, err := filepath.Abs(veleroCfg.CloudCredentialsFile) - if err != nil { - return nil, err - } - io := cliinstall.NewInstallOptions() // always wait for velero and restic pods to be running. io.Wait = true io.ProviderName = veleroCfg.ObjectStoreProvider - io.SecretFile = veleroCfg.CloudCredentialsFile + + if veleroCfg.CloudCredentialsFile != "" { + realPath, err := filepath.Abs(veleroCfg.CloudCredentialsFile) + if err != nil { + return nil, err + } + io.SecretFile = realPath + } + + if veleroCfg.ServiceAccountName != "" { + io.ServiceAccountName = veleroCfg.ServiceAccountName + io.NoSecret = true + } io.BucketName = veleroCfg.BSLBucket io.Prefix = veleroCfg.BSLPrefix @@ -197,11 +204,31 @@ func getProviderVeleroInstallOptions(veleroCfg *VeleroConfig, io.VolumeSnapshotConfig = flag.NewMap() io.VolumeSnapshotConfig.Set(veleroCfg.VSLConfig) - io.SecretFile = realPath io.Plugins = flag.NewStringArray(plugins...) io.Features = veleroCfg.Features io.DefaultVolumesToFsBackup = veleroCfg.DefaultVolumesToFsBackup io.UseVolumeSnapshots = veleroCfg.UseVolumeSnapshots + + if !veleroCfg.UseRestic { + io.UseNodeAgent = veleroCfg.UseNodeAgent + } + io.UseRestic = veleroCfg.UseRestic + io.Image = veleroCfg.VeleroImage + io.Namespace = veleroCfg.VeleroNamespace + io.UploaderType = veleroCfg.UploaderType + GCFrequency, _ := time.ParseDuration(veleroCfg.GCFrequency) + io.GarbageCollectionFrequency = GCFrequency + io.PodVolumeOperationTimeout = veleroCfg.PodVolumeOperationTimeout + io.NodeAgentPodCPULimit = veleroCfg.NodeAgentPodCPULimit + io.NodeAgentPodCPURequest = veleroCfg.NodeAgentPodCPURequest + io.NodeAgentPodMemLimit = veleroCfg.NodeAgentPodMemLimit + io.NodeAgentPodMemRequest = veleroCfg.NodeAgentPodMemRequest + io.VeleroPodCPULimit = veleroCfg.VeleroPodCPULimit + io.VeleroPodCPURequest = veleroCfg.VeleroPodCPURequest + io.VeleroPodMemLimit = veleroCfg.VeleroPodMemLimit + io.VeleroPodMemRequest = veleroCfg.VeleroPodMemRequest + io.DisableInformerCache = veleroCfg.DisableInformerCache + return io, nil } From 5fc8b3f4263107a82217419fbf41099d27ced414 Mon Sep 17 00:00:00 2001 From: Ming Qiu Date: Tue, 30 Jan 2024 05:44:24 +0000 Subject: [PATCH 21/32] Fix server start failure when no default BSL Signed-off-by: Ming Qiu --- pkg/cmd/server/server.go | 8 +++++++- pkg/cmd/server/server_test.go | 9 +++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/pkg/cmd/server/server.go b/pkg/cmd/server/server.go index 06d164d74..da135e314 100644 --- a/pkg/cmd/server/server.go +++ b/pkg/cmd/server/server.go @@ -33,6 +33,7 @@ import ( "github.com/sirupsen/logrus" "github.com/spf13/cobra" corev1api "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" @@ -467,7 +468,12 @@ func setDefaultBackupLocation(ctx context.Context, client ctrlclient.Client, nam backupLocation := &velerov1api.BackupStorageLocation{} if err := client.Get(ctx, types.NamespacedName{Namespace: namespace, Name: defaultBackupLocation}, backupLocation); err != nil { - return errors.WithStack(err) + if apierrors.IsNotFound(err) { + logger.WithField("backupStorageLocation", defaultBackupLocation).WithError(err).Warn("Failed to set default backup storage location at server start") + return nil + } else { + return errors.WithStack(err) + } } if !backupLocation.Spec.Default { diff --git a/pkg/cmd/server/server_test.go b/pkg/cmd/server/server_test.go index e8c8c9b23..ab411c628 100644 --- a/pkg/cmd/server/server_test.go +++ b/pkg/cmd/server/server_test.go @@ -409,4 +409,13 @@ func Test_setDefaultBackupLocation(t *testing.T) { nonDefaultLocation := &velerov1api.BackupStorageLocation{} require.Nil(t, c.Get(context.Background(), client.ObjectKey{Namespace: "velero", Name: "non-default"}, nonDefaultLocation)) assert.False(t, nonDefaultLocation.Spec.Default) + + // no default location specified + c = fake.NewClientBuilder().WithScheme(scheme).Build() + err := setDefaultBackupLocation(context.Background(), c, "velero", "", logrus.New()) + assert.NoError(t, err) + + // no default location created + err = setDefaultBackupLocation(context.Background(), c, "velero", "default", logrus.New()) + assert.NoError(t, err) } From 7aaf62442ac30cf5739e8a37df762961c65303e9 Mon Sep 17 00:00:00 2001 From: Xun Jiang Date: Tue, 30 Jan 2024 22:28:59 +0800 Subject: [PATCH 22/32] Add `ParallelFilesUpload` for schedule creation. Modify restore-helper print information. Signed-off-by: Xun Jiang --- cmd/velero-restore-helper/velero-restore-helper.go | 4 ++-- pkg/cmd/cli/schedule/create.go | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/cmd/velero-restore-helper/velero-restore-helper.go b/cmd/velero-restore-helper/velero-restore-helper.go index a6039381d..748f1e6c8 100644 --- a/cmd/velero-restore-helper/velero-restore-helper.go +++ b/cmd/velero-restore-helper/velero-restore-helper.go @@ -66,10 +66,10 @@ func done() bool { doneFile := filepath.Join("/restores", child.Name(), ".velero", os.Args[1]) if _, err := os.Stat(doneFile); os.IsNotExist(err) { - fmt.Printf("Not found: %s\n", doneFile) + fmt.Printf("The filesystem restore done file %s is not found yet. Retry later.\n", doneFile) return false } else if err != nil { - fmt.Fprintf(os.Stderr, "ERROR looking for %s: %s\n", doneFile, err) + fmt.Fprintf(os.Stderr, "ERROR looking filesystem restore done file %s: %s\n", doneFile, err) return false } diff --git a/pkg/cmd/cli/schedule/create.go b/pkg/cmd/cli/schedule/create.go index 32f277136..c9304e078 100644 --- a/pkg/cmd/cli/schedule/create.go +++ b/pkg/cmd/cli/schedule/create.go @@ -171,6 +171,12 @@ func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error { schedule.Spec.Template.ResourcePolicy = &v1.TypedLocalObjectReference{Kind: resourcepolicies.ConfigmapRefType, Name: o.BackupOptions.ResPoliciesConfigmap} } + if o.BackupOptions.ParallelFilesUpload > 0 { + schedule.Spec.Template.UploaderConfig = &api.UploaderConfigForBackup{ + ParallelFilesUpload: o.BackupOptions.ParallelFilesUpload, + } + } + if printed, err := output.PrintWithFormat(c, schedule); printed || err != nil { return err } From 8f84f50711991a0caf099d587bb178bf4dda254e Mon Sep 17 00:00:00 2001 From: allenxu404 Date: Mon, 15 Jan 2024 14:24:45 +0800 Subject: [PATCH 23/32] Include the design for adding the finalization phase to the restore workflow Signed-off-by: allenxu404 --- changelogs/unreleased/7317-allenxu404 | 1 + design/restore-finalizing-phase_design.md | 120 ++++++++++++++++++++++ design/restore-phases-transition.png | Bin 0 -> 66236 bytes 3 files changed, 121 insertions(+) create mode 100644 changelogs/unreleased/7317-allenxu404 create mode 100644 design/restore-finalizing-phase_design.md create mode 100644 design/restore-phases-transition.png diff --git a/changelogs/unreleased/7317-allenxu404 b/changelogs/unreleased/7317-allenxu404 new file mode 100644 index 000000000..2c1628aa6 --- /dev/null +++ b/changelogs/unreleased/7317-allenxu404 @@ -0,0 +1 @@ +Include the design for adding the finalization phase to the restore workflow \ No newline at end of file diff --git a/design/restore-finalizing-phase_design.md b/design/restore-finalizing-phase_design.md new file mode 100644 index 000000000..0d4d0170d --- /dev/null +++ b/design/restore-finalizing-phase_design.md @@ -0,0 +1,120 @@ +# Design for Adding Finalization Phase in Restore Workflow + +## Abstract +This design proposes adding the finalization phase to the restore workflow. The finalization phase would be entered after all item restoration and plugin operations have been completed, similar to the way the backup process proceeds. Its purpose is to perform any wrap-up work necessary before transitioning the restore process to a terminal phase. + +## Background +Currently, the restore process enters a terminal phase once all item restoration and plugin operations have been completed. However, there are some wrap-up works that need to be performed after item restoration and plugin operations have been fully executed. There is no suitable opportunity to perform them at present. + +To address this, a new finalization phase should be added to the existing restore workflow. in this phase, all plugin operations and item restoration has been fully completed, which provides a clean opportunity to perform any wrap-up work before termination, improving the overall restore process. + +Wrap-up tasks in Velero can serve several purposes: +- Post-restore modification - Velero can modify the restored data that was temporarily changed for some purpose but required to be changed back finally or data that was newly created but missing some information. For example, [issue6435](https://github.com/vmware-tanzu/velero/issues/6435) indicates that some custom settings(like labels, reclaim policy) on restored PVs was lost because those restored PVs was newly dynamically provisioned. Velero can address it by patching the PVs' custom settings back in the finalization phase. +- Clean up unused data - Velero can identify and delete any data that are no longer needed after a successful restore in the finalization phase. +- Post-restore validation - Velero can validate the state of restored data and report any errors to help users locate the issue in the finalization phase. + +The uses of wrap-up tasks are not limited to these examples. Additional needs may be addressed as they develop over time. + +## Goals +- Add the finalization phase and the corresponding controller to restore workflow. + +## Non Goals +- Implement the specific wrap-up work. + + +## High-Level Design +- The finalization phase will be added to current restore workflow. +- The logic for handling current phase transition in restore and restore operations controller will be modified with the introduction of the finalization phase. +- A new restore finalizer controller will be implemented to handle the finalization phase. + +## Detailed Design + +### phase transition +Two new phases related to finalization will be added to restore workflow, which are `FinalizingPartiallyFailed` and `Finalizing`. The new phase transition will be similar to backup workflow, proceeding as follow: + +![image](restore-phases-transition.png) + +### restore finalizer controller +The new restore finalizer controller will be implemented to watch for restores in `FinalizingPartiallyFailed` and `Finalizing` phases. Any wrap-up work that needs to wait for the completion of item restoration and plugin operations will be executed by this controller, and the phase will be set to either `Completed` or `PartiallyFailed` based on the results of these works. + +Points worth noting about the new restore finalizer controller: + +A new structure `finalizerContext` will be created to facilitate the implementation of any wrap-up tasks. It includes all the dependencies the tasks require as well as a function `execute()` to orderly implement task logic. +``` +// finalizerContext includes all the dependencies required by wrap-up tasks +type finalizerContext struct { + ....... + restore *velerov1api.Restore + log logrus.FieldLogger + ....... +} + +// execute executes all the wrap-up tasks and return the result +func (ctx *finalizerContext) execute() (results.Result, results.Result) { + // execute task1 + ....... + + // execute task2 + ....... + + // the task execution logic will be expanded as new tasks are included + ....... +} + +// newFinalizerContext returns a finalizerContext object, the parameters will be added as new tasks are included. +func newFinalizerContext(restore *velerov1api.Restore, log logrus.FieldLogger, ...) *finalizerContext{ + return &finalizerContext{ + ....... + restore: restore, + log: log, + ....... + } +} +``` +The finalizer controller is responsible for collecting all dependencies and creating a `finalizerContext` object using those dependencies. It then invokes the `execute` function. +``` +func (r *restoreFinalizerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + ....... + + // collect all dependencies required by wrap-up tasks + ....... + + // create a finalizerContext object and invoke execute() + finalizerCtx := newFinalizerContext(restore, log, ...) + warnings, errs := finalizerCtx.execute() + + ....... +} + +``` +After completing all necessary tasks, the result metadata in object storage will be updated if any errors or warnings occur during the execution. This behavior breaks the feature of keeping metadata files in object storage immutable, However, we believe the tradeoff is justified because it provides users with the access to examine the error/warning details when the wrap-up tasks go wrong. + +``` +// UpdateResults updates the result metadata in object storage if necessary +func (r *restoreFinalizerReconciler) UpdateResults(restore *api.Restore, newWarnings *results.Result, newErrs *results.Result, backupStore persistence.BackupStore) error { + originResults, err := backupStore.GetRestoreResults(restore.Name) + if err != nil { + return errors.Wrap(err, "error getting restore results") + } + warnings := originResults["warnings"] + errs := originResults["errors"] + warnings.Merge(newWarnings) + errs.Merge(newErrs) + + m := map[string]results.Result{ + "warnings": warnings, + "errors": errs, + } + if err := putResults(restore, m, backupStore); err != nil { + return errors.Wrap(err, "error putting restore results") + } + + return nil +} +``` + +## Compatibility +The new finalization phases are added without modifying the existing phases in the restore workflow. Both new and ongoing restore processes will continue to eventually transition to a terminal phase from any prior phase, ensuring backward compatibility. + +## Implementation +This will be implemented during the Velero 1.14 development cycle. diff --git a/design/restore-phases-transition.png b/design/restore-phases-transition.png new file mode 100644 index 0000000000000000000000000000000000000000..46724c6522eae8f83d04eaf34f9396f3ee573e49 GIT binary patch literal 66236 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 literal 0 HcmV?d00001 From 30728c248ceff71773c2e92e31a92caf8ee8eb49 Mon Sep 17 00:00:00 2001 From: Daniel Jiang Date: Tue, 30 Jan 2024 15:52:21 +0800 Subject: [PATCH 24/32] Respect the config in BSL when IRSA is configured This commit makes sure when kopia connects to the repository the crendentials file specified in BSL.spec.config has the higher priority over Pod Environment credentials when IRSA is configured. Signed-off-by: Daniel Jiang --- changelogs/unreleased/7374-reasonerjt | 1 + go.mod | 4 +- pkg/repository/config/aws.go | 72 +++++++++++++++++++++++-- pkg/repository/provider/unified_repo.go | 2 +- 4 files changed, 71 insertions(+), 8 deletions(-) create mode 100644 changelogs/unreleased/7374-reasonerjt diff --git a/changelogs/unreleased/7374-reasonerjt b/changelogs/unreleased/7374-reasonerjt new file mode 100644 index 000000000..ca09bda51 --- /dev/null +++ b/changelogs/unreleased/7374-reasonerjt @@ -0,0 +1 @@ +Respect and use `credentialsFile` specified in BSL.spec.config when IRSA is configured over Velero Pod Environment credentials \ No newline at end of file diff --git a/go.mod b/go.mod index 9e2f41bd6..33717f919 100644 --- a/go.mod +++ b/go.mod @@ -18,9 +18,11 @@ require ( github.com/Azure/go-autorest/autorest/to v0.3.0 github.com/aws/aws-sdk-go-v2 v1.24.1 github.com/aws/aws-sdk-go-v2/config v1.26.3 + github.com/aws/aws-sdk-go-v2/credentials v1.16.14 github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.11 github.com/aws/aws-sdk-go-v2/service/ec2 v1.143.0 github.com/aws/aws-sdk-go-v2/service/s3 v1.48.0 + github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 github.com/bombsimon/logrusr/v3 v3.0.0 github.com/evanphx/json-patch v5.6.0+incompatible github.com/fatih/color v1.15.0 @@ -83,7 +85,6 @@ require ( github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.16.14 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 // indirect @@ -95,7 +96,6 @@ require ( github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.10 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.18.6 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.6 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 // indirect github.com/aws/smithy-go v1.19.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect diff --git a/pkg/repository/config/aws.go b/pkg/repository/config/aws.go index 6cb87f0a6..567fec54c 100644 --- a/pkg/repository/config/aws.go +++ b/pkg/repository/config/aws.go @@ -19,7 +19,13 @@ package config import ( "context" + "fmt" "os" + "time" + + "github.com/aws/aws-sdk-go-v2/credentials" + "github.com/aws/aws-sdk-go-v2/credentials/stscreds" + "github.com/aws/aws-sdk-go-v2/service/sts" "github.com/aws/aws-sdk-go-v2/aws" awsconfig "github.com/aws/aws-sdk-go-v2/config" @@ -31,13 +37,13 @@ import ( const ( // AWS specific environment variable awsProfileEnvVar = "AWS_PROFILE" - awsRoleEnvVar = "AWS_ROLE_ARN" awsKeyIDEnvVar = "AWS_ACCESS_KEY_ID" awsSecretKeyEnvVar = "AWS_SECRET_ACCESS_KEY" awsSessTokenEnvVar = "AWS_SESSION_TOKEN" awsProfileKey = "profile" awsCredentialsFileEnvVar = "AWS_SHARED_CREDENTIALS_FILE" awsConfigFileEnvVar = "AWS_CONFIG_FILE" + awsDefaultProfile = "default" ) // GetS3ResticEnvVars gets the environment variables that restic @@ -72,10 +78,6 @@ func GetS3ResticEnvVars(config map[string]string) (map[string]string, error) { // GetS3Credentials gets the S3 credential values according to the information // of the provided config or the system's environment variables func GetS3Credentials(config map[string]string) (*aws.Credentials, error) { - if os.Getenv(awsRoleEnvVar) != "" { - return nil, nil - } - var opts []func(*awsconfig.LoadOptions) error credentialsFile := config[CredentialsFileKey] if credentialsFile == "" { @@ -93,6 +95,25 @@ func GetS3Credentials(config map[string]string) (*aws.Credentials, error) { if err != nil { return nil, err } + + if credentialsFile != "" && os.Getenv("AWS_WEB_IDENTITY_TOKEN_FILE") != "" && os.Getenv("AWS_ROLE_ARN") != "" { + // Reset the config to use the credentials from the credentials/config file + profile := config[awsProfileKey] + if profile == "" { + profile = awsDefaultProfile + } + sfp, err := awsconfig.LoadSharedConfigProfile(context.Background(), profile, func(o *awsconfig.LoadSharedConfigOptions) { + o.ConfigFiles = []string{credentialsFile} + o.CredentialsFiles = []string{credentialsFile} + }) + if err != nil { + return nil, fmt.Errorf("error loading config profile '%s': %v", profile, err) + } + if err := resolveCredsFromProfile(&cfg, &sfp); err != nil { + return nil, fmt.Errorf("error resolving creds from profile '%s': %v", profile, err) + } + } + creds, err := cfg.Credentials.Retrieve(context.Background()) return &creds, err @@ -115,3 +136,44 @@ func GetAWSBucketRegion(bucket string) (string, error) { } return region, nil } + +func resolveCredsFromProfile(cfg *aws.Config, sharedConfig *awsconfig.SharedConfig) error { + var err error + switch { + case sharedConfig.Source != nil: + // Assume IAM role with credentials source from a different profile. + err = resolveCredsFromProfile(cfg, sharedConfig.Source) + case sharedConfig.Credentials.HasKeys(): + // Static Credentials from Shared Config/Credentials file. + cfg.Credentials = credentials.StaticCredentialsProvider{ + Value: sharedConfig.Credentials, + } + } + if err != nil { + return err + } + if len(sharedConfig.RoleARN) > 0 { + credsFromAssumeRole(cfg, sharedConfig) + } + return nil +} + +func credsFromAssumeRole(cfg *aws.Config, sharedCfg *awsconfig.SharedConfig) { + optFns := []func(*stscreds.AssumeRoleOptions){ + func(options *stscreds.AssumeRoleOptions) { + options.RoleSessionName = sharedCfg.RoleSessionName + if sharedCfg.RoleDurationSeconds != nil { + if *sharedCfg.RoleDurationSeconds/time.Minute > 15 { + options.Duration = *sharedCfg.RoleDurationSeconds + } + } + if len(sharedCfg.ExternalID) > 0 { + options.ExternalID = aws.String(sharedCfg.ExternalID) + } + if len(sharedCfg.MFASerial) != 0 { + options.SerialNumber = aws.String(sharedCfg.MFASerial) + } + }, + } + cfg.Credentials = stscreds.NewAssumeRoleProvider(sts.NewFromConfig(*cfg), sharedCfg.RoleARN, optFns...) +} diff --git a/pkg/repository/provider/unified_repo.go b/pkg/repository/provider/unified_repo.go index 76ae36351..e3422e693 100644 --- a/pkg/repository/provider/unified_repo.go +++ b/pkg/repository/provider/unified_repo.go @@ -505,7 +505,7 @@ func getStorageVariables(backupLocation *velerov1api.BackupStorageLocation, repo } s3URL = url.Host - disableTLS = (url.Scheme == "http") + disableTLS = url.Scheme == "http" } result[udmrepo.StoreOptionS3Endpoint] = strings.Trim(s3URL, "/") From b509df5172208ba5e90b00b2418579dcc56d91b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wenkai=20Yin=28=E5=B0=B9=E6=96=87=E5=BC=80=29?= Date: Fri, 2 Feb 2024 02:02:42 +0800 Subject: [PATCH 25/32] Upgrade the version of go plugin related libs/tools (#7373) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Upgrade the version of go plugin related libs/tools Signed-off-by: Wenkai Yin(尹文开) --- changelogs/unreleased/7373-ywk253100 | 1 + go.mod | 4 ++-- go.sum | 18 ++++++++---------- hack/build-image/Dockerfile | 6 +++--- 4 files changed, 14 insertions(+), 15 deletions(-) create mode 100644 changelogs/unreleased/7373-ywk253100 diff --git a/changelogs/unreleased/7373-ywk253100 b/changelogs/unreleased/7373-ywk253100 new file mode 100644 index 000000000..179b85d8a --- /dev/null +++ b/changelogs/unreleased/7373-ywk253100 @@ -0,0 +1 @@ +Upgrade the version of go plugin related libs/tools \ No newline at end of file diff --git a/go.mod b/go.mod index 9e2f41bd6..0053a3142 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( github.com/google/go-cmp v0.5.9 github.com/google/uuid v1.3.1 github.com/hashicorp/go-hclog v0.14.1 - github.com/hashicorp/go-plugin v1.4.3 + github.com/hashicorp/go-plugin v1.6.0 github.com/joho/godotenv v1.3.0 github.com/kopia/kopia v0.14.1 github.com/kubernetes-csi/external-snapshotter/client/v4 v4.2.0 @@ -123,7 +123,7 @@ require ( github.com/googleapis/enterprise-certificate-proxy v0.3.1 // indirect github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/hashicorp/cronexpr v1.1.2 // indirect - github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect + github.com/hashicorp/yamux v0.1.1 // indirect github.com/imdario/mergo v0.3.13 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect diff --git a/go.sum b/go.sum index bffe4194d..182e02e87 100644 --- a/go.sum +++ b/go.sum @@ -193,6 +193,8 @@ github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnweb github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bombsimon/logrusr/v3 v3.0.0 h1:tcAoLfuAhKP9npBxWzSdpsvKPQt1XV02nSf2lZA82TQ= github.com/bombsimon/logrusr/v3 v3.0.0/go.mod h1:PksPPgSFEL2I52pla2glgCyyd2OqOHAnFF5E+g8Ixco= +github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= +github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= @@ -475,8 +477,8 @@ github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39 github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM= -github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= +github.com/hashicorp/go-plugin v1.6.0 h1:wgd4KxHJTVGGqWBq4QPB1i5BZNEx9BR8+OFmHDmTk8A= +github.com/hashicorp/go-plugin v1.6.0/go.mod h1:lBS5MtSSBZk0SHc66KACcjjlU6WzEVP/8pwz68aMkCI= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= @@ -490,8 +492,8 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= -github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= +github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -502,8 +504,8 @@ github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= -github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= +github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= +github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= @@ -601,7 +603,6 @@ github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceT github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= @@ -921,7 +922,6 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1219,7 +1219,6 @@ google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -1272,7 +1271,6 @@ google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb h1: google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 h1:N3bU/SQDCDyD6R528GJ/PwW9KjYcJA3dgyH+MovAkIM= google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:KSqppvjFjtoCI+KGd4PELB0qLNxdJHRGqRI09mB6pQA= -google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= diff --git a/hack/build-image/Dockerfile b/hack/build-image/Dockerfile index 5a7bbc6f5..7d00e1b24 100644 --- a/hack/build-image/Dockerfile +++ b/hack/build-image/Dockerfile @@ -39,15 +39,15 @@ RUN go install golang.org/x/tools/cmd/goimports@11e9d9cc0042e6bd10337d4d2c3e5d92 # get protoc compiler and golang plugin WORKDIR /root RUN apt-get update && apt-get install -y unzip -RUN wget --quiet https://github.com/protocolbuffers/protobuf/releases/download/v3.14.0/protoc-3.14.0-linux-x86_64.zip && \ - unzip protoc-3.14.0-linux-x86_64.zip && \ +RUN wget --quiet https://github.com/protocolbuffers/protobuf/releases/download/v25.2/protoc-25.2-linux-x86_64.zip && \ + unzip protoc-25.2-linux-x86_64.zip && \ mv bin/protoc /usr/bin/protoc && \ mv include/google /usr/include && \ chmod a+x /usr/include/google && \ chmod a+x /usr/include/google/protobuf && \ chmod a+r -R /usr/include/google && \ chmod +x /usr/bin/protoc -RUN go install github.com/golang/protobuf/protoc-gen-go@v1.4.3 +RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.32.0 # get goreleaser RUN wget --quiet https://github.com/goreleaser/goreleaser/releases/download/v1.15.2/goreleaser_Linux_x86_64.tar.gz && \ From 72438b7319c9a8d7052e5791d8946022d14fcb06 Mon Sep 17 00:00:00 2001 From: danfengl Date: Wed, 24 Jan 2024 06:21:04 +0000 Subject: [PATCH 26/32] Support IRSA for data mover pipeline Signed-off-by: danfengl --- test/e2e/Makefile | 10 ++++++---- test/e2e/README.md | 8 ++++---- test/e2e/bsl-mgmt/deletion.go | 2 +- test/e2e/e2e_suite_test.go | 8 +++++--- test/e2e/migration/migration.go | 2 ++ test/types.go | 4 +++- test/util/eks/eks.go | 6 ++---- test/util/k8s/clusterrolebinding.go | 1 - test/util/providers/common.go | 12 ++++-------- test/util/velero/install.go | 26 ++++++++++++++------------ test/util/velero/velero_utils.go | 6 +++--- 11 files changed, 44 insertions(+), 41 deletions(-) diff --git a/test/e2e/Makefile b/test/e2e/Makefile index e00e29432..3bc3cbc7a 100644 --- a/test/e2e/Makefile +++ b/test/e2e/Makefile @@ -67,7 +67,8 @@ MIGRATE_FROM_VELERO_CLI ?= VELERO_NAMESPACE ?= velero CREDS_FILE ?= -SERVICE_ACCOUNT_NAME ?= +DEFAULT_CLS_SERVICE_ACCOUNT_NAME ?= +STANDBY_CLS_SERVICE_ACCOUNT_NAME ?= BSL_BUCKET ?= BSL_PREFIX ?= BSL_CONFIG ?= @@ -153,8 +154,8 @@ run: ginkgo -kibishii-directory=$(KIBISHII_DIRECTORY) \ -debug-e2e-test=$(DEBUG_E2E_TEST) \ -velero-server-debug-mode=$(VELERO_SERVER_DEBUG_MODE) \ - -default-cluster=$(DEFAULT_CLUSTER) \ - -standby-cluster=$(STANDBY_CLUSTER) \ + -default-cluster-context=$(DEFAULT_CLUSTER) \ + -standby-cluster-context=$(STANDBY_CLUSTER) \ -uploader-type=$(UPLOADER_TYPE) \ -snapshot-move-data=$(SNAPSHOT_MOVE_DATA) \ -data-mover-plugin=$(DATA_MOVER_PLUGIN) \ @@ -166,7 +167,8 @@ run: ginkgo -default-cluster-name=$(DEFAULT_CLUSTER_NAME) \ -standby-cluster-name=$(STANDBY_CLUSTER_NAME) \ -eks-policy-arn=$(EKS_POLICY_ARN) \ - -service-account-name=$(SERVICE_ACCOUNT_NAME) + -default-cls-service-account-name=$(DEFAULT_CLS_SERVICE_ACCOUNT_NAME) \ + -standby-cls-service-account-name=$(STANDBY_CLS_SERVICE_ACCOUNT_NAME) build: ginkgo mkdir -p $(OUTPUT_DIR) diff --git a/test/e2e/README.md b/test/e2e/README.md index 9207e2da8..cb1d001fd 100644 --- a/test/e2e/README.md +++ b/test/e2e/README.md @@ -69,8 +69,8 @@ the object-store-provider to be specified. 1. `-debug-e2e-test`: A Switch for enable or disable test data cleaning action. 1. `-garbage-collection-frequency`: frequency of garbage collection. It is a parameter for Velero installation. Optional. 1. `-velero-server-debug-mode`: A switch for enable or disable having debug log of Velero server. -1. `-default-cluster`: Default (source) cluster's kube config context, it's for migration test. -1. `-standby-cluster`: Standby (destination) cluster's kube config context, it's for migration test. +1. `-default-cluster-context`: Default (source) cluster's kube config context, it's for migration test. +1. `-standby-cluster-context`: Standby (destination) cluster's kube config context, it's for migration test. 1. `-uploader-type`: Type of uploader for persistent volume backup. 1. `-snapshot-move-data`: A Switch for taking backup with Velero's data mover, if data-mover-plugin is not provided, using built-in plugin. 1. `-data-mover-plugin`: Customized plugin for data mover. @@ -118,8 +118,8 @@ Below is a mapping between `make` variables to E2E configuration flags. 1. `KIBISHII_DIRECTORY`: `-kibishii-directory`. Optional. 1. `DEBUG_E2E_TEST`: `-debug-e2e-test`. Optional. 1. `VELERO_SERVER_DEBUG_MODE`: `-velero-server-debug-mode`. Optional. -1. `DEFAULT_CLUSTER`: `-default-cluster`. Optional. -1. `STANDBY_CLUSTER`: `-standby-cluster`. Optional. +1. `DEFAULT_CLUSTER`: `-default-cluster-context`. Optional. +1. `STANDBY_CLUSTER`: `-standby-cluster-context`. Optional. 1. `UPLOADER_TYPE`: `-uploader-type`. Optional. 1. `SNAPSHOT_MOVE_DATA`: `-snapshot-move-data`. Optional. 1. `DATA_MOVER_plugin`: `-data-mover-plugin`. Optional. diff --git a/test/e2e/bsl-mgmt/deletion.go b/test/e2e/bsl-mgmt/deletion.go index 909458e03..4dc7a0561 100644 --- a/test/e2e/bsl-mgmt/deletion.go +++ b/test/e2e/bsl-mgmt/deletion.go @@ -229,7 +229,7 @@ func BslDeletionTest(useVolumeSnapshots bool) { }) By(fmt.Sprintf("Snapshot of bsl %s should be created in cloud object store", backupLocation_2), func() { snapshotCheckPoint, err = GetSnapshotCheckPoint(*veleroCfg.ClientToInstallVelero, veleroCfg, 1, bslDeletionTestNs, backupName_2, []string{podName_2}) - Expect(err).NotTo(HaveOccurred(), "Fail to get Azure CSI snapshot checkpoint") + Expect(err).NotTo(HaveOccurred(), "Fail to get snapshot checkpoint") var BSLCredentials, BSLConfig string if veleroCfg.CloudProvider == "vsphere" { BSLCredentials = veleroCfg.AdditionalBSLCredentials diff --git a/test/e2e/e2e_suite_test.go b/test/e2e/e2e_suite_test.go index d285a8324..99dd7b16d 100644 --- a/test/e2e/e2e_suite_test.go +++ b/test/e2e/e2e_suite_test.go @@ -84,8 +84,8 @@ func init() { flag.StringVar(&VeleroCfg.Features, "features", "", "comma-separated list of features to enable for this Velero process.") flag.BoolVar(&VeleroCfg.Debug, "debug-e2e-test", false, "A Switch for enable or disable test data cleaning action.") flag.StringVar(&VeleroCfg.GCFrequency, "garbage-collection-frequency", "", "frequency of garbage collection.") - flag.StringVar(&VeleroCfg.DefaultClusterContext, "default-cluster", "", "default cluster's kube config context, it's for migration test.") - flag.StringVar(&VeleroCfg.StandbyClusterContext, "standby-cluster", "", "standby cluster's kube config context, it's for migration test.") + flag.StringVar(&VeleroCfg.DefaultClusterContext, "default-cluster-context", "", "default cluster's kube config context, it's for migration test.") + flag.StringVar(&VeleroCfg.StandbyClusterContext, "standby-cluster-context", "", "standby cluster's kube config context, it's for migration test.") flag.StringVar(&VeleroCfg.UploaderType, "uploader-type", "", "type of uploader for persistent volume backup.") flag.BoolVar(&VeleroCfg.VeleroServerDebugMode, "velero-server-debug-mode", false, "a switch for enable or disable having debug log of Velero server.") flag.BoolVar(&VeleroCfg.SnapshotMoveData, "snapshot-move-data", false, "a Switch for taking backup with Velero's data mover, if data-mover-plugin is not provided, using built-in plugin") @@ -98,7 +98,8 @@ func init() { flag.StringVar(&VeleroCfg.DefaultClusterName, "default-cluster-name", "", "default cluster's name in kube config file, it's for EKS IRSA test.") flag.StringVar(&VeleroCfg.StandbyClusterName, "standby-cluster-name", "", "standby cluster's name in kube config file, it's for EKS IRSA test.") flag.StringVar(&VeleroCfg.EKSPolicyARN, "eks-policy-arn", "", "EKS plicy ARN for creating AWS IAM service account.") - flag.StringVar(&VeleroCfg.ServiceAccountName, "service-account-name", "", "service account name.") + flag.StringVar(&VeleroCfg.DefaultCLSServiceAccountName, "default-cls-service-account-name", "", "default cluster service account name.") + flag.StringVar(&VeleroCfg.StandbyCLSServiceAccountName, "standby-cls-service-account-name", "", "standby cluster service account name.") } @@ -173,6 +174,7 @@ func GetKubeconfigContext() error { VeleroCfg.DefaultClient = &tcDefault VeleroCfg.ClientToInstallVelero = VeleroCfg.DefaultClient VeleroCfg.ClusterToInstallVelero = VeleroCfg.DefaultClusterName + VeleroCfg.ServiceAccountNameToInstall = VeleroCfg.DefaultCLSServiceAccountName if err != nil { return err } diff --git a/test/e2e/migration/migration.go b/test/e2e/migration/migration.go index 1bfb11e29..7395970a5 100644 --- a/test/e2e/migration/migration.go +++ b/test/e2e/migration/migration.go @@ -149,6 +149,7 @@ func MigrationTest(useVolumeSnapshots bool, veleroCLI2Version VeleroCLI2Version) OriginVeleroCfg.VeleroCLI = veleroCLI2Version.VeleroCLI OriginVeleroCfg.ClientToInstallVelero = OriginVeleroCfg.DefaultClient OriginVeleroCfg.ClusterToInstallVelero = veleroCfg.DefaultClusterName + OriginVeleroCfg.ServiceAccountNameToInstall = veleroCfg.DefaultCLSServiceAccountName OriginVeleroCfg.UseVolumeSnapshots = useVolumeSnapshots OriginVeleroCfg.UseNodeAgent = !useVolumeSnapshots @@ -289,6 +290,7 @@ func MigrationTest(useVolumeSnapshots bool, veleroCLI2Version VeleroCLI2Version) veleroCfg.ClientToInstallVelero = veleroCfg.StandbyClient veleroCfg.ClusterToInstallVelero = veleroCfg.StandbyClusterName + veleroCfg.ServiceAccountNameToInstall = veleroCfg.StandbyCLSServiceAccountName veleroCfg.UseNodeAgent = !useVolumeSnapshots veleroCfg.UseRestic = false if veleroCfg.SnapshotMoveData { diff --git a/test/types.go b/test/types.go index 20b3ca14c..0d27e96be 100644 --- a/test/types.go +++ b/test/types.go @@ -91,7 +91,9 @@ type VeleroConfig struct { WithoutDisableInformerCacheParam bool DisableInformerCache bool CreateClusterRoleBinding bool - ServiceAccountName string + DefaultCLSServiceAccountName string + StandbyCLSServiceAccountName string + ServiceAccountNameToInstall string EKSPolicyARN string } diff --git a/test/util/eks/eks.go b/test/util/eks/eks.go index 6b83e88df..ff3df3c31 100644 --- a/test/util/eks/eks.go +++ b/test/util/eks/eks.go @@ -40,11 +40,10 @@ func KubectlDeleteIAMServiceAcount(ctx context.Context, name, namespace, cluster if strings.Contains(stderr, "NotFound") { err = nil } - fmt.Printf("err: %v\n", err) return err } -func KubectlCreateIAMServiceAcount(ctx context.Context, name, namespace, policyARN, cluster string) error { +func EksctlCreateIAMServiceAcount(ctx context.Context, name, namespace, policyARN, cluster string) error { args := []string{"create", "iamserviceaccount", name, "--namespace", namespace, "--cluster", cluster, "--attach-policy-arn", policyARN, "--approve", "--override-existing-serviceaccounts"} @@ -55,9 +54,8 @@ func KubectlCreateIAMServiceAcount(ctx context.Context, name, namespace, policyA cmd := exec.CommandContext(ctx, "eksctl", args...) fmt.Println(cmd) stdout, stderr, err := veleroexec.RunCommand(cmd) - fmt.Printf("Output: %v|%v|%v\n", stdout, stderr, err) if err != nil { - fmt.Printf("err: %v|%v|%v\n", stdout, stderr, err) + fmt.Printf("eksctl return stdout: %v, stderr: %v, err: %v\n", stdout, stderr, err) return false, nil } return true, nil diff --git a/test/util/k8s/clusterrolebinding.go b/test/util/k8s/clusterrolebinding.go index a9fbb7962..c7ce32e31 100644 --- a/test/util/k8s/clusterrolebinding.go +++ b/test/util/k8s/clusterrolebinding.go @@ -32,7 +32,6 @@ func KubectlDeleteClusterRoleBinding(ctx context.Context, name string) error { cmd := exec.CommandContext(ctx, "kubectl", args...) fmt.Println(cmd) _, stderr, err := veleroexec.RunCommand(cmd) - fmt.Printf("Ignore error: %v\n", stderr) if strings.Contains(stderr, "NotFound") { fmt.Printf("Ignore error: %v\n", stderr) err = nil diff --git a/test/util/providers/common.go b/test/util/providers/common.go index 8ef943e0f..2576d5357 100644 --- a/test/util/providers/common.go +++ b/test/util/providers/common.go @@ -48,8 +48,7 @@ func ObjectsShouldNotBeInBucket(cloudProvider, cloudCredentialsFile, bslBucket, var exist bool fmt.Printf("|| VERIFICATION || - %s %s should not exist in object store %s\n", subPrefix, backupName, bslPrefix) if cloudCredentialsFile == "" { - fmt.Printf("|| SKIPPED || - Skipping object storebackup checkpoint %s\n", backupName) - return nil + return errors.New(fmt.Sprintf("|| ERROR || - Please provide credential file of cloud %s \n", cloudProvider)) } for i := 0; i < retryTimes; i++ { exist, err = IsObjectsInBucket(cloudProvider, cloudCredentialsFile, bslBucket, bslPrefix, bslConfig, backupName, subPrefix) @@ -103,8 +102,7 @@ func DeleteObjectsInBucket(cloudProvider, cloudCredentialsFile, bslBucket, bslPr bslPrefix = getFullPrefix(bslPrefix, subPrefix) fmt.Printf("|| VERIFICATION || - Delete backup %s in storage %s\n", backupName, bslPrefix) if cloudCredentialsFile == "" { - fmt.Printf("|| SKIPPED || - Skipping snapshots checkpoint %s\n", backupName) - return nil + return errors.New(fmt.Sprintf("|| ERROR || - Please provide credential file of cloud %s \n", cloudProvider)) } s, err := getProvider(cloudProvider) if err != nil { @@ -120,8 +118,7 @@ func DeleteObjectsInBucket(cloudProvider, cloudCredentialsFile, bslBucket, bslPr func SnapshotsShouldNotExistInCloud(cloudProvider, cloudCredentialsFile, bslBucket, bslConfig, backupName string, snapshotCheckPoint SnapshotCheckPoint) error { fmt.Printf("|| VERIFICATION || - Snapshots should not exist in cloud, backup %s\n", backupName) if cloudCredentialsFile == "" { - fmt.Printf("|| SKIPPED || - Skipping snapshots checkpoint %s\n", backupName) - return nil + return errors.New(fmt.Sprintf("|| ERROR || - Please provide credential file of cloud %s \n", cloudProvider)) } snapshotCheckPoint.ExpectCount = 0 err := IsSnapshotExisted(cloudProvider, cloudCredentialsFile, bslBucket, bslConfig, backupName, snapshotCheckPoint) @@ -135,8 +132,7 @@ func SnapshotsShouldNotExistInCloud(cloudProvider, cloudCredentialsFile, bslBuck func SnapshotsShouldBeCreatedInCloud(cloudProvider, cloudCredentialsFile, bslBucket, bslConfig, backupName string, snapshotCheckPoint SnapshotCheckPoint) error { fmt.Printf("|| VERIFICATION || - Snapshots should exist in cloud, backup %s\n", backupName) if cloudCredentialsFile == "" { - fmt.Printf("|| SKIPPED || - Skipping snapshots checkpoint %s\n", backupName) - return nil + return errors.New(fmt.Sprintf("|| ERROR || - Please provide credential file of cloud %s \n", cloudProvider)) } err := IsSnapshotExisted(cloudProvider, cloudCredentialsFile, bslBucket, bslConfig, backupName, snapshotCheckPoint) if err != nil { diff --git a/test/util/velero/install.go b/test/util/velero/install.go index 7eab157ea..cdb61e740 100644 --- a/test/util/velero/install.go +++ b/test/util/velero/install.go @@ -76,7 +76,7 @@ func VeleroInstall(ctx context.Context, veleroCfg *VeleroConfig, isStandbyCluste veleroCfg.CloudProvider = veleroCfg.StandbyClusterCloudProvider } if veleroCfg.CloudProvider != "kind" { - fmt.Printf("For cloud platforms, object store plugin provider will be set as cloud provider\n") + fmt.Println("For cloud platforms, object store plugin provider will be set as cloud provider") // If ObjectStoreProvider is not provided, then using the value same as CloudProvider if veleroCfg.ObjectStoreProvider == "" { veleroCfg.ObjectStoreProvider = veleroCfg.CloudProvider @@ -114,32 +114,34 @@ func VeleroInstall(ctx context.Context, veleroCfg *VeleroConfig, isStandbyCluste return errors.WithMessagef(err, "Failed to get Velero InstallOptions for plugin provider %s", veleroCfg.ObjectStoreProvider) } + // For AWS IRSA credential test, AWS IAM service account is required, so if ServiceAccountName and EKSPolicyARN + // are both provided, we assume IRSA test is running, otherwise skip this IAM service account creation part. if veleroCfg.CloudProvider == "aws" && veleroInstallOptions.ServiceAccountName != "" { + if veleroCfg.EKSPolicyARN == "" { + return errors.New("Please provide EKSPolicyARN for IRSA test.") + } _, err = GetNamespace(ctx, *veleroCfg.ClientToInstallVelero, veleroCfg.VeleroNamespace) + // We should uninstall Velero for a new service account creation. if !apierrors.IsNotFound(err) { if err := VeleroUninstall(context.Background(), veleroCfg.VeleroCLI, veleroCfg.VeleroNamespace); err != nil { return errors.Wrapf(err, "Failed to uninstall velero %s", veleroCfg.VeleroNamespace) } } + // If velero namespace does not exist, we should create it for service account creation if err := KubectlCreateNamespace(ctx, veleroCfg.VeleroNamespace); err != nil { return errors.Wrapf(err, "Failed to create namespace %s to install Velero", veleroCfg.VeleroNamespace) } if err := KubectlDeleteClusterRoleBinding(ctx, "velero-cluster-role"); err != nil { - fmt.Println(err) - return errors.Wrapf(err, "Failed to delete clusterrolebinding %s to %s namesapce", "velero-cluster-role", veleroCfg.VeleroNamespace) + return errors.Wrapf(err, "Failed to delete clusterrolebinding %s to %s namespace", "velero-cluster-role", veleroCfg.VeleroNamespace) } if err := KubectlCreateClusterRoleBinding(ctx, "velero-cluster-role", "cluster-admin", veleroCfg.VeleroNamespace, veleroInstallOptions.ServiceAccountName); err != nil { - fmt.Println(err) - return errors.Wrapf(err, "Failed to create clusterrolebinding %s to %s namesapce", "velero-cluster-role", veleroCfg.VeleroNamespace) + return errors.Wrapf(err, "Failed to create clusterrolebinding %s to %s namespace", "velero-cluster-role", veleroCfg.VeleroNamespace) } - if err := KubectlDeleteIAMServiceAcount(ctx, veleroCfg.ServiceAccountName, veleroCfg.VeleroNamespace, veleroCfg.ClusterToInstallVelero); err != nil { - fmt.Println(err) - return errors.Wrapf(err, "Failed to delete service account %s to %s namesapce", veleroCfg.ServiceAccountName, veleroCfg.VeleroNamespace) + if err := KubectlDeleteIAMServiceAcount(ctx, veleroInstallOptions.ServiceAccountName, veleroCfg.VeleroNamespace, veleroCfg.ClusterToInstallVelero); err != nil { + return errors.Wrapf(err, "Failed to delete service account %s to %s namespace", veleroInstallOptions.ServiceAccountName, veleroCfg.VeleroNamespace) } - time.Sleep(10 * time.Second) - if err := KubectlCreateIAMServiceAcount(ctx, veleroCfg.ServiceAccountName, veleroCfg.VeleroNamespace, veleroCfg.EKSPolicyARN, veleroCfg.ClusterToInstallVelero); err != nil { - fmt.Println(err) - return errors.Wrapf(err, "Failed to create service account %s to %s namesapce", veleroCfg.ServiceAccountName, veleroCfg.VeleroNamespace) + if err := EksctlCreateIAMServiceAcount(ctx, veleroInstallOptions.ServiceAccountName, veleroCfg.VeleroNamespace, veleroCfg.EKSPolicyARN, veleroCfg.ClusterToInstallVelero); err != nil { + return errors.Wrapf(err, "Failed to create service account %s to %s namespace", veleroInstallOptions.ServiceAccountName, veleroCfg.VeleroNamespace) } } err = installVeleroServer(ctx, veleroCfg.VeleroCLI, veleroCfg.CloudProvider, &installOptions{ diff --git a/test/util/velero/velero_utils.go b/test/util/velero/velero_utils.go index 6eb924ac8..91f3f8b04 100644 --- a/test/util/velero/velero_utils.go +++ b/test/util/velero/velero_utils.go @@ -174,7 +174,7 @@ func getPluginsByVersion(version, cloudProvider, objectStoreProvider, feature st func getProviderVeleroInstallOptions(veleroCfg *VeleroConfig, plugins []string) (*cliinstall.Options, error) { - if veleroCfg.CloudCredentialsFile == "" && veleroCfg.ServiceAccountName == "" { + if veleroCfg.CloudCredentialsFile == "" && veleroCfg.ServiceAccountNameToInstall == "" { return nil, errors.Errorf("No credentials were supplied to use for E2E tests") } @@ -191,8 +191,8 @@ func getProviderVeleroInstallOptions(veleroCfg *VeleroConfig, io.SecretFile = realPath } - if veleroCfg.ServiceAccountName != "" { - io.ServiceAccountName = veleroCfg.ServiceAccountName + if veleroCfg.ServiceAccountNameToInstall != "" { + io.ServiceAccountName = veleroCfg.ServiceAccountNameToInstall io.NoSecret = true } From b1d95cf2aae62c8ec80816333654742f309ff319 Mon Sep 17 00:00:00 2001 From: Tiger Kaovilai Date: Wed, 24 Jan 2024 14:28:58 -0500 Subject: [PATCH 27/32] Set `GOBIN` so Makefile don't modify $PATH on `go install` Fix realPath resolving when cloud credentials is prefixed by `~` for home dir Use `~/.docker/config.json` if REGISTRY_CREDENTIAL_FILE not defined and skip step if does not exists since it is optional Signed-off-by: Tiger Kaovilai Set `GOBIN` so Makefile don't modify $PATH on `go install` Fix realPath resolving when cloud credentials is prefixed by `~` for home dir Use `~/.docker/config.json` if REGISTRY_CREDENTIAL_FILE not defined and skip step if does not exists since it is optional Signed-off-by: Tiger Kaovilai Add kind testdata storageclass Signed-off-by: Tiger Kaovilai Add kind testdata storageclass Signed-off-by: Tiger Kaovilai log `Start to install Azure VolumeSnapshotClass ...` only on azure when csi is enabled Signed-off-by: Tiger Kaovilai Add BSL_CONFIG example and notes Signed-off-by: Tiger Kaovilai Makefile: Set `GOBIN` for `_output/...` Signed-off-by: Tiger Kaovilai README spacing Signed-off-by: Tiger Kaovilai StandbyClusterObjectStoreProvider typo Signed-off-by: Tiger Kaovilai Specify velero namespace during get/delete command Signed-off-by: Tiger Kaovilai Use object stores rather than cloudProvider for bucket queries Signed-off-by: Tiger Kaovilai Remove debug print Signed-off-by: Tiger Kaovilai simplify NS get changes, add velero NS to `DeleteBackupResource` Signed-off-by: Tiger Kaovilai Skip file system backups on kind which uses hostPath volumes Signed-off-by: Tiger Kaovilai Add StorageClass change test to PR kind e2e Signed-off-by: Tiger Kaovilai Add more tests to pr Signed-off-by: Tiger Kaovilai Add NS mapping to PR e2e Signed-off-by: Tiger Kaovilai Add `SKIP_KIND` to some jobs containing volumes Signed-off-by: Tiger Kaovilai Remove kind from kibishii tests Signed-off-by: Tiger Kaovilai Label volume resource policies as restic, skip restic/snapshot tests, add more tests Signed-off-by: Tiger Kaovilai TTLTest is a snapshot test Signed-off-by: Tiger Kaovilai Remove non working tests Signed-off-by: Tiger Kaovilai Resolve https://github.com/vmware-tanzu/velero/pull/7353#issuecomment-1925660077 Signed-off-by: Tiger Kaovilai address https://github.com/vmware-tanzu/velero/pull/7353/files#r1477218762 Signed-off-by: Tiger Kaovilai Address https://github.com/vmware-tanzu/velero/pull/7353#issuecomment-1923414840 Signed-off-by: Tiger Kaovilai --- .github/workflows/e2e-test-kind.yaml | 13 ++++++++++++- Makefile | 3 +++ hack/build.sh | 4 ++++ test/e2e/Makefile | 12 +++++++++--- test/e2e/README.md | 19 +++++++++++++++--- test/e2e/backup/backup.go | 8 ++++++-- test/e2e/backups/deletion.go | 13 ++++++++----- test/e2e/backups/sync_backups.go | 4 ++-- test/e2e/backups/ttl.go | 6 +++--- test/e2e/basic/namespace-mapping.go | 9 ++++++++- test/e2e/bsl-mgmt/deletion.go | 6 +++--- test/e2e/e2e_suite_test.go | 28 +++++++++++++++++++-------- test/e2e/migration/migration.go | 2 +- test/perf/Makefile | 12 +++++++++--- test/testdata/storage-class/kind.yaml | 10 ++++++++++ test/types.go | 2 +- test/util/k8s/serviceaccount.go | 8 ++++++++ test/util/providers/common.go | 28 +++++++++++++++------------ test/util/velero/install.go | 12 +++++------- test/util/velero/velero_utils.go | 25 ++++++++++++++++-------- 20 files changed, 161 insertions(+), 63 deletions(-) create mode 100644 test/testdata/storage-class/kind.yaml diff --git a/.github/workflows/e2e-test-kind.yaml b/.github/workflows/e2e-test-kind.yaml index 3640de599..5fcefa25b 100644 --- a/.github/workflows/e2e-test-kind.yaml +++ b/.github/workflows/e2e-test-kind.yaml @@ -67,6 +67,16 @@ jobs: - 1.23.6 - 1.24.0 - 1.25.3 + focus: + # tests to focus on, use `|` to concatenate multiple regexes to run on the same job + # ordered according to e2e_suite_test.go order + - Basic\]\[ClusterResource + - ResourceFiltering + - ResourceModifier|Backups|PrivilegesMgmt\]\[SSR + - Schedule\]\[OrderedResources + - NamespaceMapping\]\[Single\]\[Restic|NamespaceMapping\]\[Multiple\]\[Restic + - Basic\]\[Nodeport + - Basic\]\[StorageClass fail-fast: false steps: - name: Set up Go @@ -123,7 +133,8 @@ jobs: CREDS_FILE=/tmp/credential BSL_BUCKET=bucket \ ADDITIONAL_OBJECT_STORE_PROVIDER=aws ADDITIONAL_BSL_CONFIG=region=minio,s3ForcePathStyle="true",s3Url=http://$(hostname -i):9000 \ ADDITIONAL_CREDS_FILE=/tmp/credential ADDITIONAL_BSL_BUCKET=additional-bucket \ - GINKGO_FOCUS='Basic\]\[ClusterResource' VELERO_IMAGE=velero:pr-test \ + GINKGO_FOCUS='${{ matrix.focus }}' VELERO_IMAGE=velero:pr-test \ + GINKGO_SKIP='SKIP_KIND|pv-backup|Restic|Snapshot|LongTime' \ make -C test/e2e run timeout-minutes: 30 - name: Upload debug bundle diff --git a/Makefile b/Makefile index 3fce3e924..41c4f3ef5 100644 --- a/Makefile +++ b/Makefile @@ -108,6 +108,7 @@ platform_temp = $(subst -, ,$(ARCH)) GOOS = $(word 1, $(platform_temp)) GOARCH = $(word 2, $(platform_temp)) GOPROXY ?= https://proxy.golang.org +GOBIN=$$(pwd)/.go/bin # If you want to build all binaries, see the 'all-build' rule. # If you want to build all containers, see the 'all-containers' rule. @@ -129,6 +130,7 @@ local: build-dirs # Add DEBUG=1 to enable debug locally GOOS=$(GOOS) \ GOARCH=$(GOARCH) \ + GOBIN=$(GOBIN) \ VERSION=$(VERSION) \ REGISTRY=$(REGISTRY) \ PKG=$(PKG) \ @@ -145,6 +147,7 @@ _output/bin/$(GOOS)/$(GOARCH)/$(BIN): build-dirs $(MAKE) shell CMD="-c '\ GOOS=$(GOOS) \ GOARCH=$(GOARCH) \ + GOBIN=$(GOBIN) \ VERSION=$(VERSION) \ REGISTRY=$(REGISTRY) \ PKG=$(PKG) \ diff --git a/hack/build.sh b/hack/build.sh index 47052e81e..064b583ef 100755 --- a/hack/build.sh +++ b/hack/build.sh @@ -32,6 +32,10 @@ if [[ -z "${GOOS}" ]]; then echo "GOOS must be set" exit 1 fi +if [[ -z "${GOBIN}" ]]; then + echo "GOBIN must be set" + exit 1 +fi if [[ -z "${GOARCH}" ]]; then echo "GOARCH must be set" exit 1 diff --git a/test/e2e/Makefile b/test/e2e/Makefile index 3bc3cbc7a..13df7733e 100644 --- a/test/e2e/Makefile +++ b/test/e2e/Makefile @@ -41,8 +41,10 @@ help: ## Display this help TOOLS_DIR := $(REPO_ROOT)/hack/tools BIN_DIR := bin +# Try to not modify PATH if possible +GOBIN := $(REPO_ROOT)/.go/bin TOOLS_BIN_DIR := $(TOOLS_DIR)/$(BIN_DIR) -GINKGO := $(shell go env GOPATH)/bin/ginkgo +GINKGO := $(GOBIN)/ginkgo KUSTOMIZE := $(TOOLS_BIN_DIR)/kustomize OUTPUT_DIR := _output/$(GOOS)/$(GOARCH)/bin GINKGO_FOCUS ?= @@ -113,9 +115,13 @@ STANDBY_CLUSTER_NAME ?= EKS_POLICY_ARN ?= +# Make sure ginkgo is in $GOBIN .PHONY:ginkgo -ginkgo: # Make sure ginkgo is in $GOPATH/bin - go install github.com/onsi/ginkgo/ginkgo@v1.16.5 +ginkgo: ${GOBIN}/ginkgo + +# This target does not run if ginkgo is already in $GOBIN +${GOBIN}/ginkgo: + GOBIN=${GOBIN} go install github.com/onsi/ginkgo/ginkgo@v1.16.5 .PHONY: run run: ginkgo diff --git a/test/e2e/README.md b/test/e2e/README.md index cb1d001fd..10a43deeb 100644 --- a/test/e2e/README.md +++ b/test/e2e/README.md @@ -66,7 +66,7 @@ the object-store-provider to be specified. 1. `-features`: Comma-separated list of features to enable for this Velero process. 1. `-registry-credential-file`: File containing credential for the image registry, follows the same format rules as the ~/.docker/config.json file. This credential will be loaded in Velero server pod to help on Docker Hub rate limit issue. 1. `-kibishii-directory`: The file directory or URL path to install Kibishii. It's configurable in case the default path is not accessible for your own test environment. -1. `-debug-e2e-test`: A Switch for enable or disable test data cleaning action. +1. `-debug-e2e-test`: A Switch for enable or disable test data cleaning action. 1. `-garbage-collection-frequency`: frequency of garbage collection. It is a parameter for Velero installation. Optional. 1. `-velero-server-debug-mode`: A switch for enable or disable having debug log of Velero server. 1. `-default-cluster-context`: Default (source) cluster's kube config context, it's for migration test. @@ -101,7 +101,7 @@ Below is a mapping between `make` variables to E2E configuration flags. 1. `VELERO_NAMESPACE `: the `-velero-namespace`. Optional. 1. `PLUGINS `: the `-plugins`. Optional. 1. `BSL_PREFIX`: `-prefix`. Optional. -1. `BSL_CONFIG`: `-bsl-config`. Optional. +1. `BSL_CONFIG`: `-bsl-config`. Optional. Example: BSL_CONFIG="region=us-east-1". May be required for some object store provider 1. `VSL_CONFIG`: `-vsl-config`. Optional. 1. `UPGRADE_FROM_VELERO_CLI `: `-upgrade-from-velero-cli`. Optional. 1. `UPGRADE_FROM_VELERO_VERSION `: `-upgrade-from-velero-version`. Optional. @@ -136,9 +136,18 @@ Below is a mapping between `make` variables to E2E configuration flags. Basic examples: 1. Run Velero tests in a kind cluster with AWS (or Minio) as the storage provider: + + Start kind cluster + ```bash + kind create cluster + ``` ```bash BSL_PREFIX= BSL_BUCKET= CREDS_FILE=/path/to/aws-creds CLOUD_PROVIDER=kind OBJECT_STORE_PROVIDER=aws make test-e2e ``` + Stop kind cluster + ```bash + kind delete cluster + ``` 1. Run Velero tests in an AWS cluster: ```bash BSL_PREFIX= BSL_BUCKET= CREDS_FILE=/path/to/aws-creds CLOUD_PROVIDER=aws make test-e2e @@ -303,4 +312,8 @@ The TestFunc function concatenate all the flows in a test. It will reduce the frequency of velero installation by installing and checking Velero with the global Velero. It's Need to explicit reinstall Velero if the case has specicial configuration, such as API Group test case we need to enable feature EnableCSI. ### Tips -Look for the ⛵ emoji printed at the end of each install and uninstall log. There should not be two install/unintall in a row, and there should be tests between an install and an uninstall. \ No newline at end of file +Look for the ⛵ emoji printed at the end of each install and uninstall log. There should not be two install/unintall in a row, and there should be tests between an install and an uninstall. + +#### Troubleshooting + +If velero log shows `level=error msg="Failed to get bucket region, bucket: xbucket, error: operation error S3: HeadBucket, failed to resolve service endpoint, endpoint rule error, A region must be set when sending requests to S3." backup-storage-location=velero/default cmd=/plugins/velero-plugin-for-aws controller=backup-storage-location logSource="/go/src/velero-plugin-for-aws/velero-plugin-for-aws/object_store.go:136" pluginName=velero-plugin-for-aws`, it means you need to set `BSL_CONFIG` to include `region=`. diff --git a/test/e2e/backup/backup.go b/test/e2e/backup/backup.go index 7fd31c7b9..0703c7ce0 100644 --- a/test/e2e/backup/backup.go +++ b/test/e2e/backup/backup.go @@ -74,8 +74,12 @@ func BackupRestoreTest(backupRestoreTestConfig BackupRestoreTestConfig) { veleroCfg.KibishiiDirectory = veleroCfg.KibishiiDirectory + backupRestoreTestConfig.kibishiiPatchSubDir veleroCfg.UseVolumeSnapshots = useVolumeSnapshots veleroCfg.UseNodeAgent = !useVolumeSnapshots - if useVolumeSnapshots && veleroCfg.CloudProvider == "kind" { - Skip("Volume snapshots not supported on kind") + if veleroCfg.CloudProvider == "kind" { + Skip("Volume snapshots plugin and File System Backups are not supported on kind") + // on kind cluster snapshots are not supported since there is no velero snapshot plugin for kind volumes. + // and PodVolumeBackups are not supported because PVB creation gets skipped for hostpath volumes, which are the only + // volumes created on kind clusters using the default storage class and provisioner (provisioner: rancher.io/local-path) + // This test suite checks for volume snapshots and PVBs generated from FileSystemBackups, so skip it on kind clusters } // [SKIP]: Static provisioning for vSphere CSI driver works differently from other drivers. // For vSphere CSI, after you create a PV specifying an existing volume handle, CSI diff --git a/test/e2e/backups/deletion.go b/test/e2e/backups/deletion.go index 76b078532..ff929e1f5 100644 --- a/test/e2e/backups/deletion.go +++ b/test/e2e/backups/deletion.go @@ -86,6 +86,9 @@ func backup_deletion_test(useVolumeSnapshots bool) { // runUpgradeTests runs upgrade test on the provider by kibishii. func runBackupDeletionTests(client TestClient, veleroCfg VeleroConfig, backupName, backupLocation string, useVolumeSnapshots bool, kibishiiDirectory string) error { + if useVolumeSnapshots && veleroCfg.CloudProvider == "kind" { + Skip("Volume snapshots not supported on kind") + } oneHourTimeout, ctxCancel := context.WithTimeout(context.Background(), time.Minute*60) defer ctxCancel() veleroCLI := veleroCfg.VeleroCLI @@ -111,7 +114,7 @@ func runBackupDeletionTests(client TestClient, veleroCfg VeleroConfig, backupNam registryCredentialFile, veleroFeatures, kibishiiDirectory, useVolumeSnapshots, DefaultKibishiiData); err != nil { return errors.Wrapf(err, "Failed to install and prepare data for kibishii %s", deletionTest) } - err := ObjectsShouldNotBeInBucket(veleroCfg.CloudProvider, veleroCfg.CloudCredentialsFile, veleroCfg.BSLBucket, veleroCfg.BSLPrefix, veleroCfg.BSLConfig, backupName, BackupObjectsPrefix, 1) + err := ObjectsShouldNotBeInBucket(veleroCfg.ObjectStoreProvider, veleroCfg.CloudCredentialsFile, veleroCfg.BSLBucket, veleroCfg.BSLPrefix, veleroCfg.BSLConfig, backupName, BackupObjectsPrefix, 1) if err != nil { return err } @@ -139,7 +142,7 @@ func runBackupDeletionTests(client TestClient, veleroCfg VeleroConfig, backupNam return errors.Wrapf(err, "Error waiting for uploads to complete") } } - err = ObjectsShouldBeInBucket(veleroCfg.CloudProvider, veleroCfg.CloudCredentialsFile, veleroCfg.BSLBucket, bslPrefix, bslConfig, backupName, BackupObjectsPrefix) + err = ObjectsShouldBeInBucket(veleroCfg.ObjectStoreProvider, veleroCfg.CloudCredentialsFile, veleroCfg.BSLBucket, bslPrefix, bslConfig, backupName, BackupObjectsPrefix) if err != nil { return err } @@ -167,7 +170,7 @@ func runBackupDeletionTests(client TestClient, veleroCfg VeleroConfig, backupNam } } - err = ObjectsShouldNotBeInBucket(veleroCfg.CloudProvider, veleroCfg.CloudCredentialsFile, veleroCfg.BSLBucket, bslPrefix, bslConfig, backupName, BackupObjectsPrefix, 5) + err = ObjectsShouldNotBeInBucket(veleroCfg.ObjectStoreProvider, veleroCfg.CloudCredentialsFile, veleroCfg.BSLBucket, bslPrefix, bslConfig, backupName, BackupObjectsPrefix, 5) if err != nil { return err } @@ -194,12 +197,12 @@ func runBackupDeletionTests(client TestClient, veleroCfg VeleroConfig, backupNam }) }) - err = DeleteObjectsInBucket(veleroCfg.CloudProvider, veleroCfg.CloudCredentialsFile, veleroCfg.BSLBucket, bslPrefix, bslConfig, backupName, BackupObjectsPrefix) + err = DeleteObjectsInBucket(veleroCfg.ObjectStoreProvider, veleroCfg.CloudCredentialsFile, veleroCfg.BSLBucket, bslPrefix, bslConfig, backupName, BackupObjectsPrefix) if err != nil { return err } - err = ObjectsShouldNotBeInBucket(veleroCfg.CloudProvider, veleroCfg.CloudCredentialsFile, veleroCfg.BSLBucket, bslPrefix, bslConfig, backupName, BackupObjectsPrefix, 1) + err = ObjectsShouldNotBeInBucket(veleroCfg.ObjectStoreProvider, veleroCfg.CloudCredentialsFile, veleroCfg.BSLBucket, bslPrefix, bslConfig, backupName, BackupObjectsPrefix, 1) if err != nil { return err } diff --git a/test/e2e/backups/sync_backups.go b/test/e2e/backups/sync_backups.go index 9a5c81113..65e1a7d87 100644 --- a/test/e2e/backups/sync_backups.go +++ b/test/e2e/backups/sync_backups.go @@ -148,13 +148,13 @@ func BackupsSyncTest() { }) By(fmt.Sprintf("Delete %s backup files in object store", test.backupName), func() { - err = DeleteObjectsInBucket(VeleroCfg.CloudProvider, VeleroCfg.CloudCredentialsFile, VeleroCfg.BSLBucket, + err = DeleteObjectsInBucket(VeleroCfg.ObjectStoreProvider, VeleroCfg.CloudCredentialsFile, VeleroCfg.BSLBucket, VeleroCfg.BSLPrefix, VeleroCfg.BSLConfig, test.backupName, BackupObjectsPrefix) Expect(err).To(Succeed(), fmt.Sprintf("Failed to delete object in bucket %s with err %v", test.backupName, err)) }) By(fmt.Sprintf("Check %s backup files in object store is deleted", test.backupName), func() { - err = ObjectsShouldNotBeInBucket(VeleroCfg.CloudProvider, VeleroCfg.CloudCredentialsFile, VeleroCfg.BSLBucket, + err = ObjectsShouldNotBeInBucket(VeleroCfg.ObjectStoreProvider, VeleroCfg.CloudCredentialsFile, VeleroCfg.BSLBucket, VeleroCfg.BSLPrefix, VeleroCfg.BSLConfig, test.backupName, BackupObjectsPrefix, 1) Expect(err).To(Succeed(), fmt.Sprintf("Failed to delete object in bucket %s with err %v", test.backupName, err)) }) diff --git a/test/e2e/backups/ttl.go b/test/e2e/backups/ttl.go index 9c353eb34..b66af4e2e 100644 --- a/test/e2e/backups/ttl.go +++ b/test/e2e/backups/ttl.go @@ -155,7 +155,7 @@ func TTLTest() { }) By("Associated Restores should be created", func() { - Expect(ObjectsShouldBeInBucket(veleroCfg.CloudProvider, + Expect(ObjectsShouldBeInBucket(veleroCfg.ObjectStoreProvider, veleroCfg.CloudCredentialsFile, veleroCfg.BSLBucket, veleroCfg.BSLPrefix, veleroCfg.BSLConfig, test.restoreName, RestoreObjectsPrefix)).NotTo(HaveOccurred(), "Fail to get restore object") @@ -179,7 +179,7 @@ func TTLTest() { }) By("Backup file from cloud object storage should be deleted", func() { - Expect(ObjectsShouldNotBeInBucket(veleroCfg.CloudProvider, + Expect(ObjectsShouldNotBeInBucket(veleroCfg.ObjectStoreProvider, veleroCfg.CloudCredentialsFile, veleroCfg.BSLBucket, veleroCfg.BSLPrefix, veleroCfg.BSLConfig, test.backupName, BackupObjectsPrefix, 5)).NotTo(HaveOccurred(), "Fail to get Azure CSI snapshot checkpoint") @@ -194,7 +194,7 @@ func TTLTest() { }) By("Associated Restores should be deleted", func() { - Expect(ObjectsShouldNotBeInBucket(veleroCfg.CloudProvider, + Expect(ObjectsShouldNotBeInBucket(veleroCfg.ObjectStoreProvider, veleroCfg.CloudCredentialsFile, veleroCfg.BSLBucket, veleroCfg.BSLPrefix, veleroCfg.BSLConfig, test.restoreName, RestoreObjectsPrefix, 5)).NotTo(HaveOccurred(), "Fail to get restore object") diff --git a/test/e2e/basic/namespace-mapping.go b/test/e2e/basic/namespace-mapping.go index 27e74af65..5cc18921a 100644 --- a/test/e2e/basic/namespace-mapping.go +++ b/test/e2e/basic/namespace-mapping.go @@ -38,6 +38,9 @@ func (n *NamespaceMapping) Init() error { n.VeleroCfg.UseVolumeSnapshots = n.UseVolumeSnapshots n.VeleroCfg.UseNodeAgent = !n.UseVolumeSnapshots n.kibishiiData = &KibishiiData{Levels: 2, DirsPerLevel: 10, FilesPerLevel: 10, FileLength: 1024, BlockSize: 1024, PassNum: 0, ExpectedNodes: 2} + if n.VeleroCfg.CloudProvider == "kind" { + n.kibishiiData = &KibishiiData{Levels: 0, DirsPerLevel: 0, FilesPerLevel: 0, FileLength: 0, BlockSize: 0, PassNum: 0, ExpectedNodes: 2} + } backupType := "restic" if n.UseVolumeSnapshots { backupType = "snapshot" @@ -67,7 +70,11 @@ func (n *NamespaceMapping) Init() error { "create", "--namespace", n.VeleroCfg.VeleroNamespace, "backup", n.BackupName, "--include-namespaces", strings.Join(*n.NSIncluded, ","), "--wait", } - if n.UseVolumeSnapshots { + if VeleroCfg.CloudProvider == "kind" { + // don't test volume snapshotter or file system backup on kind + n.BackupArgs = append(n.BackupArgs, "--snapshot-volumes=false") + n.UseVolumeSnapshots = false + } else if n.UseVolumeSnapshots { n.BackupArgs = append(n.BackupArgs, "--snapshot-volumes") } else { n.BackupArgs = append(n.BackupArgs, "--snapshot-volumes=false") diff --git a/test/e2e/bsl-mgmt/deletion.go b/test/e2e/bsl-mgmt/deletion.go index 4dc7a0561..5cd9f9d87 100644 --- a/test/e2e/bsl-mgmt/deletion.go +++ b/test/e2e/bsl-mgmt/deletion.go @@ -276,7 +276,7 @@ func BslDeletionTest(useVolumeSnapshots bool) { Expect(cmp.Diff(backupsInBsl1AndBsl2, backupsBeforeDel, cmpopts.SortSlices(less))).Should(BeEmpty()) By(fmt.Sprintf("Backup1 %s should exist in cloud object store before bsl deletion", backupName_1), func() { - Expect(ObjectsShouldBeInBucket(veleroCfg.CloudProvider, veleroCfg.CloudCredentialsFile, + Expect(ObjectsShouldBeInBucket(veleroCfg.ObjectStoreProvider, veleroCfg.CloudCredentialsFile, veleroCfg.BSLBucket, veleroCfg.BSLPrefix, veleroCfg.BSLConfig, backupName_1, BackupObjectsPrefix)).To(Succeed()) }) @@ -295,14 +295,14 @@ func BslDeletionTest(useVolumeSnapshots bool) { }) By(fmt.Sprintf("Backup1 %s should still exist in cloud object store after bsl deletion", backupName_1), func() { - Expect(ObjectsShouldBeInBucket(veleroCfg.CloudProvider, veleroCfg.CloudCredentialsFile, + Expect(ObjectsShouldBeInBucket(veleroCfg.ObjectStoreProvider, veleroCfg.CloudCredentialsFile, veleroCfg.BSLBucket, veleroCfg.BSLPrefix, veleroCfg.BSLConfig, backupName_1, BackupObjectsPrefix)).To(Succeed()) }) // TODO: Choose additional BSL to be deleted as an new test case // By(fmt.Sprintf("Backup %s should still exist in cloud object store", backupName_2), func() { - // Expect(ObjectsShouldBeInBucket(veleroCfg.CloudProvider, veleroCfg.AdditionalBSLCredentials, + // Expect(ObjectsShouldBeInBucket(veleroCfg.ObjectStoreProvider, veleroCfg.AdditionalBSLCredentials, // veleroCfg.AdditionalBSLBucket, veleroCfg.AdditionalBSLPrefix, veleroCfg.AdditionalBSLConfig, // backupName_2, BackupObjectsPrefix)).To(Succeed()) // }) diff --git a/test/e2e/e2e_suite_test.go b/test/e2e/e2e_suite_test.go index 99dd7b16d..7c7da5c5c 100644 --- a/test/e2e/e2e_suite_test.go +++ b/test/e2e/e2e_suite_test.go @@ -92,7 +92,7 @@ func init() { flag.StringVar(&VeleroCfg.DataMoverPlugin, "data-mover-plugin", "", "customized plugin for data mover.") flag.StringVar(&VeleroCfg.StandbyClusterCloudProvider, "standby-cluster-cloud-provider", "", "cloud provider for standby cluster.") flag.StringVar(&VeleroCfg.StandbyClusterPlugins, "standby-cluster-plugins", "", "plugins provider for standby cluster.") - flag.StringVar(&VeleroCfg.StandbyClusterOjbectStoreProvider, "standby-cluster-object-store-provider", "", "object store provider for standby cluster.") + flag.StringVar(&VeleroCfg.StandbyClusterObjectStoreProvider, "standby-cluster-object-store-provider", "", "object store provider for standby cluster.") flag.BoolVar(&VeleroCfg.DebugVeleroPodRestart, "debug-velero-pod-restart", false, "a switch for debugging velero pod restart.") flag.BoolVar(&VeleroCfg.DisableInformerCache, "disable-informer-cache", false, "a switch for disable informer cache.") flag.StringVar(&VeleroCfg.DefaultClusterName, "default-cluster-name", "", "default cluster's name in kube config file, it's for EKS IRSA test.") @@ -103,8 +103,8 @@ func init() { } -var _ = Describe("[APIGroup][APIVersion] Velero tests with various CRD API group versions", APIGropuVersionsTest) -var _ = Describe("[APIGroup][APIExtensions] CRD of apiextentions v1beta1 should be B/R successfully from cluster(k8s version < 1.22) to cluster(k8s version >= 1.22)", APIExtensionsVersionsTest) +var _ = Describe("[APIGroup][APIVersion][SKIP_KIND] Velero tests with various CRD API group versions", APIGropuVersionsTest) +var _ = Describe("[APIGroup][APIExtensions][SKIP_KIND] CRD of apiextentions v1beta1 should be B/R successfully from cluster(k8s version < 1.22) to cluster(k8s version >= 1.22)", APIExtensionsVersionsTest) // Test backup and restore of Kibishi using restic var _ = Describe("[Basic][Restic] Velero tests on cluster using the plugin provider for object storage and Restic for volume backups", BackupRestoreWithRestic) @@ -134,18 +134,18 @@ var _ = Describe("[ResourceFiltering][IncludeNamespaces][Restore] Velero test on var _ = Describe("[ResourceFiltering][IncludeResources][Backup] Velero test on include resources from the cluster backup", BackupWithIncludeResources) var _ = Describe("[ResourceFiltering][IncludeResources][Restore] Velero test on include resources from the cluster restore", RestoreWithIncludeResources) var _ = Describe("[ResourceFiltering][LabelSelector] Velero test on backup include resources matching the label selector", BackupWithLabelSelector) -var _ = Describe("[ResourceFiltering][ResourcePolicies] Velero test on skip backup of volume by resource policies", ResourcePoliciesTest) +var _ = Describe("[ResourceFiltering][ResourcePolicies][Restic] Velero test on skip backup of volume by resource policies", ResourcePoliciesTest) var _ = Describe("[ResourceModifier][Restore] Velero test on resource modifiers from the cluster restore", ResourceModifiersTest) var _ = Describe("[Backups][Deletion][Restic] Velero tests of Restic backup deletion", BackupDeletionWithRestic) var _ = Describe("[Backups][Deletion][Snapshot] Velero tests of snapshot backup deletion", BackupDeletionWithSnapshots) -var _ = Describe("[Backups][TTL][LongTime] Local backups and restic repos will be deleted once the corresponding backup storage location is deleted", TTLTest) +var _ = Describe("[Backups][TTL][LongTime][Snapshot] Local backups and restic repos will be deleted once the corresponding backup storage location is deleted", TTLTest) var _ = Describe("[Backups][BackupsSync] Backups in object storage are synced to a new Velero and deleted backups in object storage are synced to be deleted in Velero", BackupsSyncTest) var _ = Describe("[Schedule][BR][Pause][LongTime] Backup will be created periodly by schedule defined by a Cron expression", ScheduleBackupTest) -var _ = Describe("[Schedule][OrederedResources] Backup resources should follow the specific order in schedule", ScheduleOrderedResources) -var _ = Describe("[Schedule][BackupCreation] Schedule controller wouldn't create a new backup when it still has pending or InProgress backup", ScheduleBackupCreationTest) +var _ = Describe("[Schedule][OrderedResources] Backup resources should follow the specific order in schedule", ScheduleOrderedResources) +var _ = Describe("[Schedule][BackupCreation][SKIP_KIND] Schedule controller wouldn't create a new backup when it still has pending or InProgress backup", ScheduleBackupCreationTest) var _ = Describe("[PrivilegesMgmt][SSR] Velero test on ssr object when controller namespace mix-ups", SSRTest) @@ -165,7 +165,7 @@ var _ = Describe("[pv-backup][Opt-Out] Backup resources should follow the specif var _ = Describe("[Basic][Nodeport] Service nodeport reservation during restore is configurable", NodePortTest) var _ = Describe("[Basic][StorageClass] Storage class of persistent volumes and persistent volume claims can be changed during restores", StorageClasssChangingTest) -var _ = Describe("[Basic][SelectedNode] Node selectors of persistent volume claims can be changed during restores", PVCSelectedNodeChangingTest) +var _ = Describe("[Basic][SelectedNode][SKIP_KIND] Node selectors of persistent volume claims can be changed during restores", PVCSelectedNodeChangingTest) func GetKubeconfigContext() error { var err error @@ -206,6 +206,18 @@ func TestE2e(t *testing.T) { t.Skip("Skipping E2E tests") } + if VeleroCfg.CloudProvider != "kind" { + fmt.Println("For cloud platforms, object store plugin provider will be set as cloud provider") + // If ObjectStoreProvider is not provided, then using the value same as CloudProvider + if VeleroCfg.ObjectStoreProvider == "" { + VeleroCfg.ObjectStoreProvider = VeleroCfg.CloudProvider + } + } else { + if VeleroCfg.ObjectStoreProvider == "" { + t.Error(errors.New("No object store provider specified - must be specified when using kind as the cloud provider")) // Must have an object store provider + } + } + var err error if err = GetKubeconfigContext(); err != nil { fmt.Println(err) diff --git a/test/e2e/migration/migration.go b/test/e2e/migration/migration.go index 7395970a5..56f0a9e6b 100644 --- a/test/e2e/migration/migration.go +++ b/test/e2e/migration/migration.go @@ -298,7 +298,7 @@ func MigrationTest(useVolumeSnapshots bool, veleroCLI2Version VeleroCLI2Version) // For SnapshotMoveData pipelines, we should use standby clustr setting for Velero installation // In nightly CI, StandbyClusterPlugins is set properly if pipeline is for SnapshotMoveData. veleroCfg.Plugins = veleroCfg.StandbyClusterPlugins - veleroCfg.ObjectStoreProvider = veleroCfg.StandbyClusterOjbectStoreProvider + veleroCfg.ObjectStoreProvider = veleroCfg.StandbyClusterObjectStoreProvider } Expect(VeleroInstall(context.Background(), &veleroCfg, true)).To(Succeed()) diff --git a/test/perf/Makefile b/test/perf/Makefile index 759d19aba..7500d0b1b 100644 --- a/test/perf/Makefile +++ b/test/perf/Makefile @@ -41,8 +41,10 @@ help: ## Display this help TOOLS_DIR := $(REPO_ROOT)/hack/tools BIN_DIR := bin +# Try to not modify PATH if possible +GOBIN := $(REPO_ROOT)/.go/bin TOOLS_BIN_DIR := $(TOOLS_DIR)/$(BIN_DIR) -GINKGO := $(GOPATH)/bin/ginkgo +GINKGO := $(GOBIN)/ginkgo KUSTOMIZE := $(TOOLS_BIN_DIR)/kustomize OUTPUT_DIR := _output/$(GOOS)/$(GOARCH)/bin GINKGO_FOCUS ?= @@ -88,9 +90,13 @@ VELERO_POD_CPU_REQUEST ?= 2 VELERO_POD_MEM_REQUEST ?= 2Gi POD_VOLUME_OPERATION_TIMEOUT ?= 6h +# Make sure ginkgo is in $GOBIN .PHONY:ginkgo -ginkgo: # Make sure ginkgo is in $GOPATH/bin - go install github.com/onsi/ginkgo/ginkgo@v1.16.5 +ginkgo: ${GOBIN}/ginkgo + +# This target does not run if ginkgo is already in $GOBIN +${GOBIN}/ginkgo: + GOBIN=${GOBIN} go install github.com/onsi/ginkgo/ginkgo@v1.16.5 .PHONY: run run: ginkgo diff --git a/test/testdata/storage-class/kind.yaml b/test/testdata/storage-class/kind.yaml new file mode 100644 index 000000000..b794b9332 --- /dev/null +++ b/test/testdata/storage-class/kind.yaml @@ -0,0 +1,10 @@ +allowVolumeExpansion: true +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + name: e2e-storage-class +parameters: + type: pd-standard +provisioner: rancher.io/local-path +reclaimPolicy: Delete +volumeBindingMode: WaitForFirstConsumer \ No newline at end of file diff --git a/test/types.go b/test/types.go index 0d27e96be..4b1d78485 100644 --- a/test/types.go +++ b/test/types.go @@ -85,7 +85,7 @@ type VeleroConfig struct { DataMoverPlugin string StandbyClusterCloudProvider string StandbyClusterPlugins string - StandbyClusterOjbectStoreProvider string + StandbyClusterObjectStoreProvider string DebugVeleroPodRestart bool IsUpgradeTest bool WithoutDisableInformerCacheParam bool diff --git a/test/util/k8s/serviceaccount.go b/test/util/k8s/serviceaccount.go index 5013a628d..24d748aea 100644 --- a/test/util/k8s/serviceaccount.go +++ b/test/util/k8s/serviceaccount.go @@ -47,8 +47,16 @@ func WaitUntilServiceAccountCreated(ctx context.Context, client TestClient, name } func PatchServiceAccountWithImagePullSecret(ctx context.Context, client TestClient, namespace, serviceAccount, dockerCredentialFile string) error { + if dockerCredentialFile == "" { + // use the default docker credential file in the home directory + dockerCredentialFile = os.Getenv("HOME") + "/.docker/config.json" + } + // if file do not exist, do not patch the service account, just return credential, err := os.ReadFile(dockerCredentialFile) if err != nil { + if os.IsNotExist(err) { + return nil + } return errors.Wrapf(err, "failed to read the docker credential file %q", dockerCredentialFile) } secretName := "image-pull-secret" diff --git a/test/util/providers/common.go b/test/util/providers/common.go index 2576d5357..13b1cf8ee 100644 --- a/test/util/providers/common.go +++ b/test/util/providers/common.go @@ -34,24 +34,24 @@ type ObjectsInStorage interface { IsSnapshotExisted(cloudCredentialsFile, bslConfig, backupName string, snapshotCheck SnapshotCheckPoint) error } -func ObjectsShouldBeInBucket(cloudProvider, cloudCredentialsFile, bslBucket, bslPrefix, bslConfig, backupName, subPrefix string) error { +func ObjectsShouldBeInBucket(objectStoreProvider, cloudCredentialsFile, bslBucket, bslPrefix, bslConfig, backupName, subPrefix string) error { fmt.Printf("|| VERIFICATION || - %s should exist in storage [%s %s]\n", backupName, bslPrefix, subPrefix) - exist, err := IsObjectsInBucket(cloudProvider, cloudCredentialsFile, bslBucket, bslPrefix, bslConfig, backupName, subPrefix) + exist, err := IsObjectsInBucket(objectStoreProvider, cloudCredentialsFile, bslBucket, bslPrefix, bslConfig, backupName, subPrefix) if !exist { return errors.Wrap(err, fmt.Sprintf("|| UNEXPECTED ||Backup object %s is not exist in object store after backup as expected\n", backupName)) } fmt.Printf("|| EXPECTED || - Backup %s exist in object storage bucket %s\n", backupName, bslBucket) return nil } -func ObjectsShouldNotBeInBucket(cloudProvider, cloudCredentialsFile, bslBucket, bslPrefix, bslConfig, backupName, subPrefix string, retryTimes int) error { +func ObjectsShouldNotBeInBucket(objectStoreProvider, cloudCredentialsFile, bslBucket, bslPrefix, bslConfig, backupName, subPrefix string, retryTimes int) error { var err error var exist bool fmt.Printf("|| VERIFICATION || - %s %s should not exist in object store %s\n", subPrefix, backupName, bslPrefix) if cloudCredentialsFile == "" { - return errors.New(fmt.Sprintf("|| ERROR || - Please provide credential file of cloud %s \n", cloudProvider)) + return errors.New(fmt.Sprintf("|| ERROR || - Please provide credential file of cloud %s \n", objectStoreProvider)) } for i := 0; i < retryTimes; i++ { - exist, err = IsObjectsInBucket(cloudProvider, cloudCredentialsFile, bslBucket, bslPrefix, bslConfig, backupName, subPrefix) + exist, err = IsObjectsInBucket(objectStoreProvider, cloudCredentialsFile, bslBucket, bslPrefix, bslConfig, backupName, subPrefix) if err != nil { return errors.Wrapf(err, "|| UNEXPECTED || - Failed to get backup %s in object store\n", backupName) } @@ -63,6 +63,10 @@ func ObjectsShouldNotBeInBucket(cloudProvider, cloudCredentialsFile, bslBucket, } return errors.New(fmt.Sprintf("|| UNEXPECTED ||Backup object %s still exist in object store after backup deletion\n", backupName)) } + +// This function returns a storage interface based on the cloud provider for querying objects and snapshots +// When cloudProvider is kind, pass in object storage provider instead. For example, "aws". +// Snapshots are not supported on kind. func getProvider(cloudProvider string) (ObjectsInStorage, error) { var s ObjectsInStorage switch cloudProvider { @@ -89,24 +93,24 @@ func getFullPrefix(bslPrefix, subPrefix string) string { } return bslPrefix } -func IsObjectsInBucket(cloudProvider, cloudCredentialsFile, bslBucket, bslPrefix, bslConfig, backupName, subPrefix string) (bool, error) { +func IsObjectsInBucket(objectStoreProvider, cloudCredentialsFile, bslBucket, bslPrefix, bslConfig, backupName, subPrefix string) (bool, error) { bslPrefix = getFullPrefix(bslPrefix, subPrefix) - s, err := getProvider(cloudProvider) + s, err := getProvider(objectStoreProvider) if err != nil { - return false, errors.Wrapf(err, fmt.Sprintf("Cloud provider %s is not valid", cloudProvider)) + return false, errors.Wrapf(err, fmt.Sprintf("Object store provider %s is not valid", objectStoreProvider)) } return s.IsObjectsInBucket(cloudCredentialsFile, bslBucket, bslPrefix, bslConfig, backupName) } -func DeleteObjectsInBucket(cloudProvider, cloudCredentialsFile, bslBucket, bslPrefix, bslConfig, backupName, subPrefix string) error { +func DeleteObjectsInBucket(objectStoreProvider, cloudCredentialsFile, bslBucket, bslPrefix, bslConfig, backupName, subPrefix string) error { bslPrefix = getFullPrefix(bslPrefix, subPrefix) fmt.Printf("|| VERIFICATION || - Delete backup %s in storage %s\n", backupName, bslPrefix) if cloudCredentialsFile == "" { - return errors.New(fmt.Sprintf("|| ERROR || - Please provide credential file of cloud %s \n", cloudProvider)) + return errors.New(fmt.Sprintf("|| ERROR || - Please provide credential file of cloud %s \n", objectStoreProvider)) } - s, err := getProvider(cloudProvider) + s, err := getProvider(objectStoreProvider) if err != nil { - return errors.Wrapf(err, fmt.Sprintf("Cloud provider %s is not valid", cloudProvider)) + return errors.Wrapf(err, fmt.Sprintf("Object store provider %s is not valid", objectStoreProvider)) } err = s.DeleteObjectsInBucket(cloudCredentialsFile, bslBucket, bslPrefix, bslConfig, backupName) if err != nil { diff --git a/test/util/velero/install.go b/test/util/velero/install.go index cdb61e740..5a19eb064 100644 --- a/test/util/velero/install.go +++ b/test/util/velero/install.go @@ -75,6 +75,7 @@ func VeleroInstall(ctx context.Context, veleroCfg *VeleroConfig, isStandbyCluste if isStandbyCluster { veleroCfg.CloudProvider = veleroCfg.StandbyClusterCloudProvider } + if veleroCfg.CloudProvider != "kind" { fmt.Println("For cloud platforms, object store plugin provider will be set as cloud provider") // If ObjectStoreProvider is not provided, then using the value same as CloudProvider @@ -278,14 +279,11 @@ func installVeleroServer(ctx context.Context, cli, cloudProvider string, options if len(options.Features) > 0 { args = append(args, "--features", options.Features) - if strings.EqualFold(options.Features, FeatureCSI) && options.UseVolumeSnapshots { - if strings.EqualFold(cloudProvider, "azure") { - fmt.Println("Start to install Azure VolumeSnapshotClass ...") - if err := KubectlApplyByFile(ctx, "../util/csi/AzureVolumeSnapshotClass.yaml"); err != nil { - return err - } + if strings.EqualFold(cloudProvider, "azure") && strings.EqualFold(options.Features, FeatureCSI) && options.UseVolumeSnapshots { + fmt.Println("Start to install Azure VolumeSnapshotClass ...") + if err := KubectlApplyByFile(ctx, "../util/csi/AzureVolumeSnapshotClass.yaml"); err != nil { + return err } - } } if options.GarbageCollectionFrequency > 0 { diff --git a/test/util/velero/velero_utils.go b/test/util/velero/velero_utils.go index 91f3f8b04..29d0afc14 100644 --- a/test/util/velero/velero_utils.go +++ b/test/util/velero/velero_utils.go @@ -26,6 +26,7 @@ import ( "net/http" "os" "os/exec" + "os/user" "path/filepath" "reflect" "regexp" @@ -184,6 +185,14 @@ func getProviderVeleroInstallOptions(veleroCfg *VeleroConfig, io.ProviderName = veleroCfg.ObjectStoreProvider if veleroCfg.CloudCredentialsFile != "" { + // Expand home directory if it is specified. https://stackoverflow.com/a/17617721 + if strings.HasPrefix(veleroCfg.CloudCredentialsFile, "~/") { + usr, _ := user.Current() + dir := usr.HomeDir + // Use strings.HasPrefix so we don't match paths like + // "/something/~/something/" + veleroCfg.CloudCredentialsFile = filepath.Join(dir, veleroCfg.CloudCredentialsFile[2:]) + } realPath, err := filepath.Abs(veleroCfg.CloudCredentialsFile) if err != nil { return nil, err @@ -927,7 +936,7 @@ func getVeleroCliTarball(cliTarballUrl string) (*os.File, error) { } func DeleteBackupResource(ctx context.Context, veleroCLI string, backupName string) error { - args := []string{"backup", "delete", backupName, "--confirm"} + args := []string{"--namespace", VeleroCfg.VeleroNamespace, "backup", "delete", backupName, "--confirm"} cmd := exec.CommandContext(ctx, veleroCLI, args...) fmt.Println("Delete backup Command:" + cmd.String()) @@ -939,7 +948,7 @@ func DeleteBackupResource(ctx context.Context, veleroCLI string, backupName stri output := strings.Replace(stdout, "\n", " ", -1) fmt.Println("Backup delete command output:" + output) - args = []string{"backup", "get", backupName} + args = []string{"--namespace", VeleroCfg.VeleroNamespace, "backup", "get", backupName} retryTimes := 5 for i := 1; i < retryTimes+1; i++ { @@ -958,13 +967,13 @@ func DeleteBackupResource(ctx context.Context, veleroCLI string, backupName stri return nil } -func GetBackup(ctx context.Context, veleroCLI string, backupName string) (string, string, error) { - args := []string{"backup", "get", backupName} +func GetBackup(ctx context.Context, veleroCLI, backupName string) (string, string, error) { + args := []string{"--namespace", VeleroCfg.VeleroNamespace, "backup", "get", backupName} cmd := exec.CommandContext(ctx, veleroCLI, args...) return veleroexec.RunCommand(cmd) } -func IsBackupExist(ctx context.Context, veleroCLI string, backupName string) (bool, error) { +func IsBackupExist(ctx context.Context, veleroCLI, backupName string) (bool, error) { out, outerr, err := GetBackup(ctx, veleroCLI, backupName) if err != nil { if strings.Contains(outerr, "not found") { @@ -977,7 +986,7 @@ func IsBackupExist(ctx context.Context, veleroCLI string, backupName string) (bo return true, nil } -func WaitBackupDeleted(ctx context.Context, veleroCLI string, backupName string, timeout time.Duration) error { +func WaitBackupDeleted(ctx context.Context, veleroCLI, backupName string, timeout time.Duration) error { return wait.PollImmediate(10*time.Second, timeout, func() (bool, error) { if exist, err := IsBackupExist(ctx, veleroCLI, backupName); err != nil { return false, err @@ -992,7 +1001,7 @@ func WaitBackupDeleted(ctx context.Context, veleroCLI string, backupName string, }) } -func WaitForExpectedStateOfBackup(ctx context.Context, veleroCLI string, backupName string, +func WaitForExpectedStateOfBackup(ctx context.Context, veleroCLI, backupName string, timeout time.Duration, existing bool) error { return wait.PollImmediate(10*time.Second, timeout, func() (bool, error) { if exist, err := IsBackupExist(ctx, veleroCLI, backupName); err != nil { @@ -1013,7 +1022,7 @@ func WaitForExpectedStateOfBackup(ctx context.Context, veleroCLI string, backupN }) } -func WaitForBackupToBeCreated(ctx context.Context, veleroCLI string, backupName string, timeout time.Duration) error { +func WaitForBackupToBeCreated(ctx context.Context, veleroCLI, backupName string, timeout time.Duration) error { return WaitForExpectedStateOfBackup(ctx, veleroCLI, backupName, timeout, true) } From 9649619a6f645f167857f4c83962e027d092a696 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wenkai=20Yin=28=E5=B0=B9=E6=96=87=E5=BC=80=29?= Date: Mon, 22 Jan 2024 16:37:19 +0800 Subject: [PATCH 28/32] Put credential related config into getStorageCredentials function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Put credential related config into getStorageCredentials function Signed-off-by: Wenkai Yin(尹文开) --- pkg/repository/provider/unified_repo.go | 19 +++------ pkg/repository/provider/unified_repo_test.go | 41 ++++++++++---------- 2 files changed, 26 insertions(+), 34 deletions(-) diff --git a/pkg/repository/provider/unified_repo.go b/pkg/repository/provider/unified_repo.go index e3422e693..11c084549 100644 --- a/pkg/repository/provider/unified_repo.go +++ b/pkg/repository/provider/unified_repo.go @@ -53,7 +53,7 @@ var getGCPCredentials = repoconfig.GetGCPCredentials var getS3BucketRegion = repoconfig.GetAWSBucketRegion type localFuncTable struct { - getStorageVariables func(*velerov1api.BackupStorageLocation, string, string, credentials.FileStore) (map[string]string, error) + getStorageVariables func(*velerov1api.BackupStorageLocation, string, string) (map[string]string, error) getStorageCredentials func(*velerov1api.BackupStorageLocation, credentials.FileStore) (map[string]string, error) } @@ -347,7 +347,7 @@ func (urp *unifiedRepoProvider) GetStoreOptions(param interface{}) (map[string]s return map[string]string{}, errors.Errorf("invalid parameter, expect %T, actual %T", RepoParam{}, param) } - storeVar, err := funcTable.getStorageVariables(repoParam.BackupLocation, urp.repoBackend, repoParam.BackupRepo.Spec.VolumeNamespace, urp.credentialGetter.FromFile) + storeVar, err := funcTable.getStorageVariables(repoParam.BackupLocation, urp.repoBackend, repoParam.BackupRepo.Spec.VolumeNamespace) if err != nil { return map[string]string{}, errors.Wrap(err, "error to get storage variables") } @@ -438,8 +438,9 @@ func getStorageCredentials(backupLocation *velerov1api.BackupStorageLocation, cr result[udmrepo.StoreOptionS3Token] = credValue.SessionToken } case repoconfig.AzureBackend: - // do nothing here, will retrieve the credential in Azure Storage - return nil, nil + if config[repoconfig.CredentialsFileKey] != "" { + result[repoconfig.CredentialsFileKey] = config[repoconfig.CredentialsFileKey] + } case repoconfig.GCPBackend: result[udmrepo.StoreOptionCredentialFile] = getGCPCredentials(config) } @@ -447,8 +448,7 @@ func getStorageCredentials(backupLocation *velerov1api.BackupStorageLocation, cr return result, nil } -func getStorageVariables(backupLocation *velerov1api.BackupStorageLocation, repoBackend string, repoName string, - credentialFileStore credentials.FileStore) (map[string]string, error) { +func getStorageVariables(backupLocation *velerov1api.BackupStorageLocation, repoBackend string, repoName string) (map[string]string, error) { result := make(map[string]string) backendType := repoconfig.GetBackendType(backupLocation.Spec.Provider, backupLocation.Spec.Config) @@ -460,13 +460,6 @@ func getStorageVariables(backupLocation *velerov1api.BackupStorageLocation, repo if config == nil { config = map[string]string{} } - if backupLocation.Spec.Credential != nil { - credsFile, err := credentialFileStore.Path(backupLocation.Spec.Credential) - if err != nil { - return map[string]string{}, errors.WithStack(err) - } - config[repoconfig.CredentialsFileKey] = credsFile - } bucket := strings.Trim(config["bucket"], "/") prefix := strings.Trim(config["prefix"], "/") diff --git a/pkg/repository/provider/unified_repo_test.go b/pkg/repository/provider/unified_repo_test.go index 3f8e241b9..9a7dd84da 100644 --- a/pkg/repository/provider/unified_repo_test.go +++ b/pkg/repository/provider/unified_repo_test.go @@ -168,7 +168,7 @@ func TestGetStorageCredentials(t *testing.T) { }, }, credFileStore: new(credmock.FileStore), - expected: nil, + expected: map[string]string{}, }, { name: "gcp, Credential section not exists in BSL", @@ -437,12 +437,11 @@ func TestGetStorageVariables(t *testing.T) { }, } - credFileStore := new(credmock.FileStore) for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { getS3BucketRegion = tc.getS3BucketRegion - actual, err := getStorageVariables(&tc.backupLocation, tc.repoBackend, tc.repoName, credFileStore) + actual, err := getStorageVariables(&tc.backupLocation, tc.repoBackend, tc.repoName) require.Equal(t, tc.expected, actual) @@ -531,7 +530,7 @@ func TestGetStoreOptions(t *testing.T) { BackupRepo: &velerov1api.BackupRepository{}, }, funcTable: localFuncTable{ - getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string, velerocredentials.FileStore) (map[string]string, error) { + getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string) (map[string]string, error) { return map[string]string{}, errors.New("fake-error-2") }, }, @@ -545,7 +544,7 @@ func TestGetStoreOptions(t *testing.T) { BackupRepo: &velerov1api.BackupRepository{}, }, funcTable: localFuncTable{ - getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string, velerocredentials.FileStore) (map[string]string, error) { + getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string) (map[string]string, error) { return map[string]string{}, nil }, getStorageCredentials: func(*velerov1api.BackupStorageLocation, velerocredentials.FileStore) (map[string]string, error) { @@ -605,7 +604,7 @@ func TestPrepareRepo(t *testing.T) { repoService: new(reposervicenmocks.BackupRepoService), credStoreReturn: "fake-password", funcTable: localFuncTable{ - getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string, velerocredentials.FileStore) (map[string]string, error) { + getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string) (map[string]string, error) { return map[string]string{}, errors.New("fake-store-option-error") }, }, @@ -616,7 +615,7 @@ func TestPrepareRepo(t *testing.T) { getter: new(credmock.SecretStore), credStoreReturn: "fake-password", funcTable: localFuncTable{ - getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string, velerocredentials.FileStore) (map[string]string, error) { + getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string) (map[string]string, error) { return map[string]string{}, nil }, getStorageCredentials: func(*velerov1api.BackupStorageLocation, velerocredentials.FileStore) (map[string]string, error) { @@ -636,7 +635,7 @@ func TestPrepareRepo(t *testing.T) { getter: new(credmock.SecretStore), credStoreReturn: "fake-password", funcTable: localFuncTable{ - getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string, velerocredentials.FileStore) (map[string]string, error) { + getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string) (map[string]string, error) { return map[string]string{}, nil }, getStorageCredentials: func(*velerov1api.BackupStorageLocation, velerocredentials.FileStore) (map[string]string, error) { @@ -657,7 +656,7 @@ func TestPrepareRepo(t *testing.T) { getter: new(credmock.SecretStore), credStoreReturn: "fake-password", funcTable: localFuncTable{ - getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string, velerocredentials.FileStore) (map[string]string, error) { + getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string) (map[string]string, error) { return map[string]string{}, nil }, getStorageCredentials: func(*velerov1api.BackupStorageLocation, velerocredentials.FileStore) (map[string]string, error) { @@ -734,7 +733,7 @@ func TestForget(t *testing.T) { getter: new(credmock.SecretStore), credStoreReturn: "fake-password", funcTable: localFuncTable{ - getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string, velerocredentials.FileStore) (map[string]string, error) { + getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string) (map[string]string, error) { return map[string]string{}, nil }, getStorageCredentials: func(*velerov1api.BackupStorageLocation, velerocredentials.FileStore) (map[string]string, error) { @@ -758,7 +757,7 @@ func TestForget(t *testing.T) { getter: new(credmock.SecretStore), credStoreReturn: "fake-password", funcTable: localFuncTable{ - getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string, velerocredentials.FileStore) (map[string]string, error) { + getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string) (map[string]string, error) { return map[string]string{}, nil }, getStorageCredentials: func(*velerov1api.BackupStorageLocation, velerocredentials.FileStore) (map[string]string, error) { @@ -786,7 +785,7 @@ func TestForget(t *testing.T) { getter: new(credmock.SecretStore), credStoreReturn: "fake-password", funcTable: localFuncTable{ - getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string, velerocredentials.FileStore) (map[string]string, error) { + getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string) (map[string]string, error) { return map[string]string{}, nil }, getStorageCredentials: func(*velerov1api.BackupStorageLocation, velerocredentials.FileStore) (map[string]string, error) { @@ -878,7 +877,7 @@ func TestInitRepo(t *testing.T) { getter: new(credmock.SecretStore), credStoreReturn: "fake-password", funcTable: localFuncTable{ - getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string, velerocredentials.FileStore) (map[string]string, error) { + getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string) (map[string]string, error) { return map[string]string{}, nil }, getStorageCredentials: func(*velerov1api.BackupStorageLocation, velerocredentials.FileStore) (map[string]string, error) { @@ -896,7 +895,7 @@ func TestInitRepo(t *testing.T) { getter: new(credmock.SecretStore), credStoreReturn: "fake-password", funcTable: localFuncTable{ - getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string, velerocredentials.FileStore) (map[string]string, error) { + getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string) (map[string]string, error) { return map[string]string{}, nil }, getStorageCredentials: func(*velerov1api.BackupStorageLocation, velerocredentials.FileStore) (map[string]string, error) { @@ -966,7 +965,7 @@ func TestConnectToRepo(t *testing.T) { getter: new(credmock.SecretStore), credStoreReturn: "fake-password", funcTable: localFuncTable{ - getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string, velerocredentials.FileStore) (map[string]string, error) { + getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string) (map[string]string, error) { return map[string]string{}, nil }, getStorageCredentials: func(*velerov1api.BackupStorageLocation, velerocredentials.FileStore) (map[string]string, error) { @@ -984,7 +983,7 @@ func TestConnectToRepo(t *testing.T) { getter: new(credmock.SecretStore), credStoreReturn: "fake-password", funcTable: localFuncTable{ - getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string, velerocredentials.FileStore) (map[string]string, error) { + getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string) (map[string]string, error) { return map[string]string{}, nil }, getStorageCredentials: func(*velerov1api.BackupStorageLocation, velerocredentials.FileStore) (map[string]string, error) { @@ -1058,7 +1057,7 @@ func TestBoostRepoConnect(t *testing.T) { getter: new(credmock.SecretStore), credStoreReturn: "fake-password", funcTable: localFuncTable{ - getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string, velerocredentials.FileStore) (map[string]string, error) { + getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string) (map[string]string, error) { return map[string]string{}, nil }, getStorageCredentials: func(*velerov1api.BackupStorageLocation, velerocredentials.FileStore) (map[string]string, error) { @@ -1085,7 +1084,7 @@ func TestBoostRepoConnect(t *testing.T) { getter: new(credmock.SecretStore), credStoreReturn: "fake-password", funcTable: localFuncTable{ - getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string, velerocredentials.FileStore) (map[string]string, error) { + getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string) (map[string]string, error) { return map[string]string{}, nil }, getStorageCredentials: func(*velerov1api.BackupStorageLocation, velerocredentials.FileStore) (map[string]string, error) { @@ -1111,7 +1110,7 @@ func TestBoostRepoConnect(t *testing.T) { getter: new(credmock.SecretStore), credStoreReturn: "fake-password", funcTable: localFuncTable{ - getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string, velerocredentials.FileStore) (map[string]string, error) { + getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string) (map[string]string, error) { return map[string]string{}, nil }, getStorageCredentials: func(*velerov1api.BackupStorageLocation, velerocredentials.FileStore) (map[string]string, error) { @@ -1198,7 +1197,7 @@ func TestPruneRepo(t *testing.T) { getter: new(credmock.SecretStore), credStoreReturn: "fake-password", funcTable: localFuncTable{ - getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string, velerocredentials.FileStore) (map[string]string, error) { + getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string) (map[string]string, error) { return map[string]string{}, nil }, getStorageCredentials: func(*velerov1api.BackupStorageLocation, velerocredentials.FileStore) (map[string]string, error) { @@ -1216,7 +1215,7 @@ func TestPruneRepo(t *testing.T) { getter: new(credmock.SecretStore), credStoreReturn: "fake-password", funcTable: localFuncTable{ - getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string, velerocredentials.FileStore) (map[string]string, error) { + getStorageVariables: func(*velerov1api.BackupStorageLocation, string, string) (map[string]string, error) { return map[string]string{}, nil }, getStorageCredentials: func(*velerov1api.BackupStorageLocation, velerocredentials.FileStore) (map[string]string, error) { From b9f3f410e7e11afe405404145ebc177960a2fcc2 Mon Sep 17 00:00:00 2001 From: Tiger Kaovilai Date: Wed, 5 Apr 2023 16:15:35 -0400 Subject: [PATCH 29/32] Make build-image arm64 compatible Signed-off-by: Tiger Kaovilai --- hack/build-image/Dockerfile | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/hack/build-image/Dockerfile b/hack/build-image/Dockerfile index 7d00e1b24..9f3c6ca82 100644 --- a/hack/build-image/Dockerfile +++ b/hack/build-image/Dockerfile @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -FROM --platform=linux/amd64 golang:1.21-bookworm +FROM --platform=$TARGETPLATFORM golang:1.21-bookworm ARG GOPROXY @@ -21,12 +21,12 @@ ENV GO111MODULE=on ENV GOPROXY=${GOPROXY} # kubebuilder test bundle is separated from kubebuilder. Need to setup it for CI test. -RUN curl -sSLo envtest-bins.tar.gz https://go.kubebuilder.io/test-tools/1.22.1/linux/amd64 && \ +RUN curl -sSLo envtest-bins.tar.gz https://go.kubebuilder.io/test-tools/1.22.1/linux/$(go env GOARCH) && \ mkdir /usr/local/kubebuilder && \ tar -C /usr/local/kubebuilder --strip-components=1 -zvxf envtest-bins.tar.gz -RUN wget --quiet https://github.com/kubernetes-sigs/kubebuilder/releases/download/v3.2.0/kubebuilder_linux_amd64 && \ - mv kubebuilder_linux_amd64 /usr/local/kubebuilder/bin/kubebuilder && \ +RUN wget --quiet https://github.com/kubernetes-sigs/kubebuilder/releases/download/v3.2.0/kubebuilder_linux_$(go env GOARCH) && \ + mv kubebuilder_linux_$(go env GOARCH) /usr/local/kubebuilder/bin/kubebuilder && \ chmod +x /usr/local/kubebuilder/bin/kubebuilder # get controller-tools @@ -39,19 +39,30 @@ RUN go install golang.org/x/tools/cmd/goimports@11e9d9cc0042e6bd10337d4d2c3e5d92 # get protoc compiler and golang plugin WORKDIR /root RUN apt-get update && apt-get install -y unzip -RUN wget --quiet https://github.com/protocolbuffers/protobuf/releases/download/v25.2/protoc-25.2-linux-x86_64.zip && \ - unzip protoc-25.2-linux-x86_64.zip && \ +RUN if [ "$(go env GOARCH)" = "arm64" ] ; then \ + wget --quiet https://github.com/protocolbuffers/protobuf/releases/download/v25.2/protoc-25.2-linux-aarch_64.zip && \ + unzip protoc-25.2-linux-aarch_64.zip; \ + else \ + wget --quiet https://github.com/protocolbuffers/protobuf/releases/download/v25.2/protoc-25.2-linux-x86_64.zip && \ + unzip protoc-25.2-linux-x86_64.zip; \ + fi && \ + rm *.zip && \ mv bin/protoc /usr/bin/protoc && \ mv include/google /usr/include && \ - chmod a+x /usr/include/google && \ - chmod a+x /usr/include/google/protobuf && \ + chmod a+x /usr/include/google && \ + chmod a+x /usr/include/google/protobuf && \ chmod a+r -R /usr/include/google && \ chmod +x /usr/bin/protoc RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.32.0 # get goreleaser -RUN wget --quiet https://github.com/goreleaser/goreleaser/releases/download/v1.15.2/goreleaser_Linux_x86_64.tar.gz && \ - tar xvf goreleaser_Linux_x86_64.tar.gz && \ +RUN if [ "$(go env GOARCH)" = "arm64" ] ; then \ + wget --quiet "https://github.com/goreleaser/goreleaser/releases/download/v1.15.2/goreleaser_Linux_arm64.tar.gz" && \ + tar xvf goreleaser_Linux_arm64.tar.gz; \ + else \ + wget --quiet "https://github.com/goreleaser/goreleaser/releases/download/v1.15.2/goreleaser_Linux_x86_64.tar.gz" && \ + tar xvf goreleaser_Linux_x86_64.tar.gz; \ + fi && \ mv goreleaser /usr/bin/goreleaser && \ chmod +x /usr/bin/goreleaser @@ -59,6 +70,6 @@ RUN wget --quiet https://github.com/goreleaser/goreleaser/releases/download/v1.1 RUN curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.54.2 # install kubectl -RUN curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl +RUN curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/$(go env GOARCH)/kubectl RUN chmod +x ./kubectl RUN mv ./kubectl /usr/local/bin From 5adb7d0def8250d128fb5b5920c61414fb4ae21d Mon Sep 17 00:00:00 2001 From: Tiger Kaovilai Date: Mon, 5 Feb 2024 22:24:57 -0500 Subject: [PATCH 30/32] Make arch more flexible Signed-off-by: Tiger Kaovilai --- hack/build-image/Dockerfile | 46 +++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/hack/build-image/Dockerfile b/hack/build-image/Dockerfile index 9f3c6ca82..5818271c7 100644 --- a/hack/build-image/Dockerfile +++ b/hack/build-image/Dockerfile @@ -39,13 +39,27 @@ RUN go install golang.org/x/tools/cmd/goimports@11e9d9cc0042e6bd10337d4d2c3e5d92 # get protoc compiler and golang plugin WORKDIR /root RUN apt-get update && apt-get install -y unzip -RUN if [ "$(go env GOARCH)" = "arm64" ] ; then \ - wget --quiet https://github.com/protocolbuffers/protobuf/releases/download/v25.2/protoc-25.2-linux-aarch_64.zip && \ - unzip protoc-25.2-linux-aarch_64.zip; \ +# protobuf uses bazel cpunames except following +# if cpu == "systemz": +# cpu = "s390_64" +# elif cpu == "aarch64": +# cpu = "aarch_64" +# elif cpu == "ppc64": +# cpu = "ppcle_64" +# snippet from: https://github.com/protocolbuffers/protobuf/blob/d445953603e66eb8992a39b4e10fcafec8501f24/protobuf_release.bzl#L18-L24 +# cpu names: https://github.com/bazelbuild/platforms/blob/main/cpu/BUILD +RUN ARCH=$(go env GOARCH) && \ + if [ "$ARCH" = "s390x" ] ; then \ + ARCH="s390_64"; \ + elif [ "$ARCH" = "arm64" ] ; then \ + ARCH="aarch_64"; \ + elif [ "$ARCH" = "ppc64" ] ; then \ + ARCH="ppcle_64"; \ else \ - wget --quiet https://github.com/protocolbuffers/protobuf/releases/download/v25.2/protoc-25.2-linux-x86_64.zip && \ - unzip protoc-25.2-linux-x86_64.zip; \ - fi && \ + ARCH=$(uname -m); \ + fi && echo "ARCH=$ARCH" && \ + wget --quiet https://github.com/protocolbuffers/protobuf/releases/download/v25.2/protoc-25.2-linux-$ARCH.zip && \ + unzip protoc-25.2-linux-$ARCH.zip; \ rm *.zip && \ mv bin/protoc /usr/bin/protoc && \ mv include/google /usr/include && \ @@ -56,13 +70,21 @@ RUN if [ "$(go env GOARCH)" = "arm64" ] ; then \ RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.32.0 # get goreleaser -RUN if [ "$(go env GOARCH)" = "arm64" ] ; then \ - wget --quiet "https://github.com/goreleaser/goreleaser/releases/download/v1.15.2/goreleaser_Linux_arm64.tar.gz" && \ - tar xvf goreleaser_Linux_arm64.tar.gz; \ - else \ - wget --quiet "https://github.com/goreleaser/goreleaser/releases/download/v1.15.2/goreleaser_Linux_x86_64.tar.gz" && \ - tar xvf goreleaser_Linux_x86_64.tar.gz; \ +# goreleaser name template per arch is basically goarch except for amd64 and 386 https://github.com/goreleaser/goreleaser/blob/ec8819a95c5527fae65e5cb41673f5bbc3245fda/.goreleaser.yaml#L167C1-L173C42 +# {{- .ProjectName }}_ +# {{- title .Os }}_ +# {{- if eq .Arch "amd64" }}x86_64 +# {{- else if eq .Arch "386" }}i386 +# {{- else }}{{ .Arch }}{{ end }} +# {{- if .Arm }}v{{ .Arm }}{{ end -}} +RUN ARCH=$(go env GOARCH) && \ + if [ "$ARCH" = "amd64" ] ; then \ + ARCH="x86_64"; \ + elif [ "$ARCH" = "386" ] ; then \ + ARCH="i386"; \ fi && \ + wget --quiet "https://github.com/goreleaser/goreleaser/releases/download/v1.15.2/goreleaser_Linux_$ARCH.tar.gz" && \ + tar xvf goreleaser_Linux_$ARCH.tar.gz; \ mv goreleaser /usr/bin/goreleaser && \ chmod +x /usr/bin/goreleaser From 2375f78d0fe02488277e7423eb55b0eca5c0c7c1 Mon Sep 17 00:00:00 2001 From: Tiger Kaovilai Date: Mon, 5 Feb 2024 23:31:56 -0500 Subject: [PATCH 31/32] ppc64le fix for protocolbuffers Signed-off-by: Tiger Kaovilai --- hack/build-image/Dockerfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hack/build-image/Dockerfile b/hack/build-image/Dockerfile index 5818271c7..7165f9370 100644 --- a/hack/build-image/Dockerfile +++ b/hack/build-image/Dockerfile @@ -53,6 +53,8 @@ RUN ARCH=$(go env GOARCH) && \ ARCH="s390_64"; \ elif [ "$ARCH" = "arm64" ] ; then \ ARCH="aarch_64"; \ + elif [ "$ARCH" = "ppc64le" ] ; then \ + ARCH="ppcle_64"; \ elif [ "$ARCH" = "ppc64" ] ; then \ ARCH="ppcle_64"; \ else \ @@ -82,6 +84,8 @@ RUN ARCH=$(go env GOARCH) && \ ARCH="x86_64"; \ elif [ "$ARCH" = "386" ] ; then \ ARCH="i386"; \ + elif [ "$ARCH" = "ppc64le" ] ; then \ + ARCH="ppc64"; \ fi && \ wget --quiet "https://github.com/goreleaser/goreleaser/releases/download/v1.15.2/goreleaser_Linux_$ARCH.tar.gz" && \ tar xvf goreleaser_Linux_$ARCH.tar.gz; \ From a5c72a4866ea162c6b855b4f5474a18d268ce63a Mon Sep 17 00:00:00 2001 From: Tiger Kaovilai Date: Tue, 6 Feb 2024 10:13:30 -0500 Subject: [PATCH 32/32] BackupRepositories associated with a BSL are invalidated when BSL is (re-)created. (#7380) * Add BackupRepositories invalidation on BSL Create Simplify comments Signed-off-by: Tiger Kaovilai * Simplify Signed-off-by: Tiger Kaovilai --------- Signed-off-by: Tiger Kaovilai --- changelogs/unreleased/7380-kaovilai | 1 + pkg/controller/backup_repository_controller.go | 11 ++++++++--- pkg/util/kube/event_handler.go | 6 ++---- 3 files changed, 11 insertions(+), 7 deletions(-) create mode 100644 changelogs/unreleased/7380-kaovilai diff --git a/changelogs/unreleased/7380-kaovilai b/changelogs/unreleased/7380-kaovilai new file mode 100644 index 000000000..a95fa4db2 --- /dev/null +++ b/changelogs/unreleased/7380-kaovilai @@ -0,0 +1 @@ +BackupRepositories associated with a BSL are invalidated when BSL is (re-)created. \ No newline at end of file diff --git a/pkg/controller/backup_repository_controller.go b/pkg/controller/backup_repository_controller.go index 70918c861..a84a583a2 100644 --- a/pkg/controller/backup_repository_controller.go +++ b/pkg/controller/backup_repository_controller.go @@ -76,7 +76,11 @@ func (r *BackupRepoReconciler) SetupWithManager(mgr ctrl.Manager) error { For(&velerov1api.BackupRepository{}). Watches(s, nil). Watches(&source.Kind{Type: &velerov1api.BackupStorageLocation{}}, kube.EnqueueRequestsFromMapUpdateFunc(r.invalidateBackupReposForBSL), - builder.WithPredicates(kube.NewUpdateEventPredicate(r.needInvalidBackupRepo))). + builder.WithPredicates( + // When BSL updates, check if the backup repositories need to be invalidated + kube.NewUpdateEventPredicate(r.needInvalidBackupRepo), + // When BSL is created, invalidate any backup repositories that reference it + kube.NewCreateEventPredicate(func(client.Object) bool { return true }))). Complete(r) } @@ -90,13 +94,13 @@ func (r *BackupRepoReconciler) invalidateBackupReposForBSL(bslObj client.Object) }).AsSelector(), } if err := r.List(context.TODO(), list, options); err != nil { - r.logger.WithField("BSL", bsl.Name).WithError(err).Error("unable to list BackupRepositorys") + r.logger.WithField("BSL", bsl.Name).WithError(err).Error("unable to list BackupRepositories") return []reconcile.Request{} } for i := range list.Items { r.logger.WithField("BSL", bsl.Name).Infof("Invalidating Backup Repository %s", list.Items[i].Name) - if err := r.patchBackupRepository(context.Background(), &list.Items[i], repoNotReady("re-establish on BSL change")); err != nil { + if err := r.patchBackupRepository(context.Background(), &list.Items[i], repoNotReady("re-establish on BSL change or create")); err != nil { r.logger.WithField("BSL", bsl.Name).WithError(err).Errorf("fail to patch BackupRepository %s", list.Items[i].Name) } } @@ -104,6 +108,7 @@ func (r *BackupRepoReconciler) invalidateBackupReposForBSL(bslObj client.Object) return []reconcile.Request{} } +// needInvalidBackupRepo returns true if the BSL's storage type, bucket, prefix, CACert, or config has changed func (r *BackupRepoReconciler) needInvalidBackupRepo(oldObj client.Object, newObj client.Object) bool { oldBSL := oldObj.(*velerov1api.BackupStorageLocation) newBSL := newObj.(*velerov1api.BackupStorageLocation) diff --git a/pkg/util/kube/event_handler.go b/pkg/util/kube/event_handler.go index 471a41c81..e07d17344 100644 --- a/pkg/util/kube/event_handler.go +++ b/pkg/util/kube/event_handler.go @@ -26,10 +26,8 @@ import ( type MapUpdateFunc func(client.Object) []reconcile.Request -// EnqueueRequestsFromMapUpdateFunc is for the same purpose with EnqueueRequestsFromMapFunc. -// Merely, it is more friendly to updating the mapped objects in the MapUpdateFunc, because -// on Update event, MapUpdateFunc is called for only once with the new object, so if MapUpdateFunc -// does some update to the mapped objects, the update is done for once +// EnqueueRequestsFromMapUpdateFunc has the same purpose with handler.EnqueueRequestsFromMapFunc. +// MapUpdateFunc is simpler on Update event because mapAndEnqueue is called once with the new object. EnqueueRequestsFromMapFunc is called twice with the old and new object. func EnqueueRequestsFromMapUpdateFunc(fn MapUpdateFunc) handler.EventHandler { return &enqueueRequestsFromMapFunc{ toRequests: fn,