github.com/vmware/go-vcloud-director/v2@v2.24.0/govcd/catalog_subscription_test.go (about)

     1  //go:build catalog || functional || ALL
     2  
     3  /*
     4   * Copyright 2019 VMware, Inc.  All rights reserved.  Licensed under the Apache v2 License.
     5   */
     6  
     7  package govcd
     8  
     9  import (
    10  	"fmt"
    11  	"net/url"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/vmware/go-vcloud-director/v2/types/v56"
    16  	. "gopkg.in/check.v1"
    17  )
    18  
    19  type subscriptionTestData struct {
    20  	fromOrg                   *AdminOrg
    21  	toOrg                     *AdminOrg
    22  	ovaPath                   string
    23  	mediaPath                 string
    24  	localCopy                 bool
    25  	storageProfile            types.CatalogStorageProfiles
    26  	uploadWhen                string
    27  	preservePublishingCatalog bool
    28  	asynchronousSubscription  bool
    29  }
    30  
    31  // Test_SubscribedCatalog tests four scenarios of Catalog subscription
    32  // All cases use a publishing catalog in one Org and a subscribing catalog
    33  // in a different Org.
    34  // The scenarios are a combination of these two facts:
    35  // * whether the subscribing catalog was created before or after the publishing catalog was filled
    36  // * whether the subscribing catalog enabled automatic downloads (localCopy)
    37  //
    38  // To see the inner working of the test components, you may run it as follows:
    39  // $ export GOVCD_TASK_MONITOR=simple_show
    40  // $ go test -tags catalog -check.f Test_SubscribedCatalog -vcd-verbose -check.vv -timeout 0
    41  // When running this way, you will see the tasks originated by the catalogs and the ones started by the catalog items
    42  func (vcd *TestVCD) Test_SubscribedCatalog(check *C) {
    43  	vcd.skipIfNotSysAdmin(check)
    44  	fromOrg, err := vcd.client.GetAdminOrgByName(vcd.config.VCD.Org)
    45  	check.Assert(err, IsNil)
    46  	toOrg, err := vcd.client.GetAdminOrgByName(vcd.config.VCD.Org + "-1")
    47  	check.Assert(err, IsNil)
    48  
    49  	if toOrg.AdminOrg.Vdcs == nil || len(toOrg.AdminOrg.Vdcs.Vdcs) == 0 {
    50  		check.Skip(fmt.Sprintf("receiving org %s does not have any storage", toOrg.AdminOrg.Name))
    51  	}
    52  	// TODO: remove this workaround when support for 10.3.3 is dropped
    53  	// See Test_PublishToExternalOrganizations for details
    54  	fromOrg.AdminOrg.OrgSettings.OrgGeneralSettings.CanPublishCatalogs = true
    55  	fromOrg.AdminOrg.OrgSettings.OrgGeneralSettings.CanPublishExternally = true
    56  	_, err = fromOrg.Update()
    57  
    58  	check.Assert(err, IsNil)
    59  	vdc, err := fromOrg.GetVDCByName(vcd.config.VCD.Nsxt.Vdc, false)
    60  	check.Assert(err, IsNil)
    61  
    62  	storageProfile, err := vdc.FindStorageProfileReference(vcd.config.VCD.StorageProfile.SP1)
    63  	check.Assert(err, IsNil)
    64  	createStorageProfiles := types.CatalogStorageProfiles{VdcStorageProfile: []*types.Reference{&storageProfile}}
    65  
    66  	testSubscribedCatalog(subscriptionTestData{
    67  		fromOrg:                  fromOrg,
    68  		toOrg:                    toOrg,
    69  		ovaPath:                  vcd.config.OVA.OvaPath,
    70  		mediaPath:                vcd.config.Media.MediaPath,
    71  		localCopy:                false,
    72  		storageProfile:           createStorageProfiles,
    73  		uploadWhen:               "after_subscription",
    74  		asynchronousSubscription: true,
    75  	}, check)
    76  
    77  	testSubscribedCatalog(subscriptionTestData{
    78  		fromOrg:                   fromOrg,
    79  		toOrg:                     toOrg,
    80  		ovaPath:                   vcd.config.OVA.OvaPath,
    81  		mediaPath:                 vcd.config.Media.MediaPath,
    82  		localCopy:                 true,
    83  		storageProfile:            createStorageProfiles,
    84  		uploadWhen:                "after_subscription",
    85  		preservePublishingCatalog: true,
    86  		asynchronousSubscription:  true,
    87  	}, check)
    88  
    89  	// For the tests where the items are uploaded before subscription, we can keep the publishing catalog
    90  	// from the previous test
    91  	testSubscribedCatalog(subscriptionTestData{
    92  		fromOrg:                   fromOrg,
    93  		toOrg:                     toOrg,
    94  		ovaPath:                   vcd.config.OVA.OvaPath,
    95  		mediaPath:                 vcd.config.Media.MediaPath,
    96  		localCopy:                 false,
    97  		storageProfile:            createStorageProfiles,
    98  		uploadWhen:                "before_subscription",
    99  		preservePublishingCatalog: true,
   100  	}, check)
   101  	testSubscribedCatalog(subscriptionTestData{
   102  		fromOrg:                   fromOrg,
   103  		toOrg:                     toOrg,
   104  		ovaPath:                   vcd.config.OVA.OvaPath,
   105  		mediaPath:                 vcd.config.Media.MediaPath,
   106  		localCopy:                 true,
   107  		storageProfile:            createStorageProfiles,
   108  		uploadWhen:                "before_subscription",
   109  		preservePublishingCatalog: false, // at the last subtest, we remove the publishing catalog
   110  	}, check)
   111  
   112  }
   113  
   114  func uploadTestItems(org *AdminOrg, catalogName, templatePath, mediaPath string, numTemplates, numMedia int) error {
   115  	var taskList []*Task
   116  
   117  	catalog, err := org.GetCatalogByName(catalogName, true)
   118  	if err != nil {
   119  		return fmt.Errorf("catalog %s not found: %s", catalogName, err)
   120  	}
   121  
   122  	for i := 1; i <= numTemplates; i++ {
   123  		templateName := fmt.Sprintf("test-vt-%d", i)
   124  		uploadTask, err := catalog.UploadOvf(templatePath, templateName, "upload from test", 1024)
   125  		if err != nil {
   126  			return err
   127  		}
   128  		taskList = append(taskList, uploadTask.Task)
   129  	}
   130  	for i := 1; i <= numMedia; i++ {
   131  		mediaName := fmt.Sprintf("test_media-%d", i)
   132  		uploadTask, err := catalog.UploadMediaImage(mediaName, "upload from test", mediaPath, 1024)
   133  		if err != nil {
   134  			return err
   135  		}
   136  		taskList = append(taskList, uploadTask.Task)
   137  	}
   138  	_, err = WaitTaskListCompletionMonitor(taskList, testMonitor)
   139  	fmt.Println()
   140  	return err
   141  }
   142  
   143  func testSubscribedCatalog(testData subscriptionTestData, check *C) {
   144  
   145  	startSubtest := time.Now()
   146  	drawHeader := func(char, msg string) {
   147  		fmt.Println(strings.Repeat(char, 80))
   148  		fmt.Printf("%s %s\n", char, msg)
   149  	}
   150  	drawHeader("*", fmt.Sprintf("START: upload %s - local copy: %v", testData.uploadWhen, testData.localCopy))
   151  
   152  	fromOrg := testData.fromOrg
   153  	toOrg := testData.toOrg
   154  
   155  	publishingCatalogName := "Publisher"
   156  	subscribingCatalogName := "Subscriber"
   157  
   158  	var fromCatalog *AdminCatalog
   159  	var err error
   160  	fromCatalog, err = fromOrg.GetAdminCatalogByName(publishingCatalogName, true)
   161  	if err == nil {
   162  		drawHeader("-", "publishing catalog retrieved from previous test")
   163  	} else {
   164  		drawHeader("-", "creating publishing catalog")
   165  		fromCatalog, err = fromOrg.CreateCatalogWithStorageProfile(publishingCatalogName, "publisher catalog", &testData.storageProfile)
   166  		check.Assert(err, IsNil)
   167  		AddToCleanupList(publishingCatalogName, "catalog", fromOrg.AdminOrg.Name, check.TestName())
   168  	}
   169  
   170  	subscriptionPassword := "superUnknown"
   171  	err = fromCatalog.PublishToExternalOrganizations(types.PublishExternalCatalogParams{
   172  		IsPublishedExternally:    addrOf(true),
   173  		Password:                 subscriptionPassword,
   174  		IsCachedEnabled:          addrOf(true),
   175  		PreserveIdentityInfoFlag: addrOf(true),
   176  	})
   177  	check.Assert(err, IsNil)
   178  
   179  	uploadItemsIf := func(wanted string) {
   180  		if wanted != testData.uploadWhen {
   181  			return
   182  		}
   183  		howManyTemplates := 3
   184  		howManyMediaItems := 3
   185  		publishedCatalogItems, err := fromCatalog.QueryCatalogItemList()
   186  		if err == nil && len(publishedCatalogItems) == (howManyMediaItems+howManyTemplates) {
   187  			return
   188  		}
   189  		drawHeader("-", fmt.Sprintf("uploading catalog items - %s", wanted))
   190  		err = uploadTestItems(fromOrg, fromCatalog.AdminCatalog.Name, testData.ovaPath, testData.mediaPath, howManyTemplates, howManyMediaItems)
   191  		check.Assert(err, IsNil)
   192  	}
   193  	err = fromCatalog.Refresh()
   194  	check.Assert(err, IsNil)
   195  
   196  	check.Assert(fromCatalog.AdminCatalog.PublishExternalCatalogParams, NotNil)
   197  	check.Assert(fromCatalog.AdminCatalog.PublishExternalCatalogParams.CatalogPublishedUrl, Not(Equals), "")
   198  
   199  	uploadItemsIf("before_subscription")
   200  	err = fromCatalog.Refresh()
   201  	check.Assert(err, IsNil)
   202  
   203  	subscriptionUrl, err := fromCatalog.FullSubscriptionUrl()
   204  	check.Assert(err, IsNil)
   205  
   206  	subscriptionParams := types.ExternalCatalogSubscription{
   207  		SubscribeToExternalFeeds: true,
   208  		Location:                 subscriptionUrl,
   209  		Password:                 subscriptionPassword,
   210  		LocalCopy:                testData.localCopy,
   211  	}
   212  
   213  	var toCatalog *AdminCatalog
   214  	testSubscribedCatalogWithInvalidParameters(toOrg, subscriptionParams, subscribingCatalogName, subscriptionPassword, testData.localCopy, check)
   215  	if testData.asynchronousSubscription {
   216  		drawHeader("-", "creating subscribed catalog asynchronously")
   217  		// With asynchronous subscription the catalog starts the subscription but does not report its state, which is
   218  		// monitored by its internal Task
   219  		toCatalog, err = toOrg.CreateCatalogFromSubscriptionAsync(
   220  			subscriptionParams,     // params
   221  			nil,                    // storage profile
   222  			subscribingCatalogName, // catalog name
   223  			subscriptionPassword,   // password
   224  			testData.localCopy)     // local copy
   225  	} else {
   226  		drawHeader("-", "creating subscribed catalog and waiting for completion")
   227  		toCatalog, err = toOrg.CreateCatalogFromSubscription(
   228  			subscriptionParams,     // params
   229  			nil,                    // storage profile
   230  			subscribingCatalogName, // catalog name
   231  			subscriptionPassword,   // password
   232  			testData.localCopy,     // local copy
   233  			10*time.Minute)         // timeout
   234  	}
   235  	check.Assert(err, IsNil)
   236  	AddToCleanupList(subscribingCatalogName, "catalog", toOrg.AdminOrg.Name, check.TestName())
   237  
   238  	if testData.asynchronousSubscription {
   239  		err = toCatalog.Refresh()
   240  		check.Assert(err, IsNil)
   241  		if ResourceInProgress(toCatalog.AdminCatalog.Tasks) {
   242  			fmt.Println("catalog subscription tasks still in progress")
   243  			for _, task := range toCatalog.AdminCatalog.Tasks.Task {
   244  				testMonitor(task)
   245  			}
   246  		} else {
   247  			fmt.Println("catalog subscription tasks complete")
   248  		}
   249  	}
   250  
   251  	uploadItemsIf("after_subscription")
   252  
   253  	// If the catalog items were uploaded before the catalog subscription, we don't need to
   254  	// synchronise, as the subscription would have got at least the list of items
   255  	if testData.uploadWhen != "before_subscription" {
   256  		drawHeader("-", "synchronising catalog")
   257  		err = toCatalog.Sync()
   258  		check.Assert(err, IsNil)
   259  	}
   260  
   261  	publishedCatalogItems, err := fromCatalog.QueryCatalogItemList()
   262  	check.Assert(err, IsNil)
   263  	subscribedCatalogItems, err := toCatalog.QueryCatalogItemList()
   264  	check.Assert(err, IsNil)
   265  	fmt.Printf("Catalog items after catalog sync: %d\n", len(subscribedCatalogItems))
   266  	publishedVappTemplates, err := fromCatalog.QueryVappTemplateList()
   267  	check.Assert(err, IsNil)
   268  	subscribedVappTemplates, err := toCatalog.QueryVappTemplateList()
   269  	check.Assert(err, IsNil)
   270  	publishedMediaItems, err := fromCatalog.QueryMediaList()
   271  	check.Assert(err, IsNil)
   272  	subscribedMediaItems, err := toCatalog.QueryMediaList()
   273  	check.Assert(err, IsNil)
   274  
   275  	fmt.Printf("vApp template after catalog sync %d\n", len(subscribedVappTemplates))
   276  	fmt.Printf("media item after catalog sync %d\n", len(subscribedMediaItems))
   277  
   278  	check.Assert(len(subscribedCatalogItems), Equals, len(publishedCatalogItems))
   279  	check.Assert(len(subscribedVappTemplates), Equals, len(publishedVappTemplates))
   280  	check.Assert(len(subscribedMediaItems), Equals, len(publishedMediaItems))
   281  
   282  	if testData.localCopy && testData.uploadWhen == "before_subscription" {
   283  		// we should have all the contents here if the data was available early
   284  		// and the subscribed catalog uses automatic download
   285  		retrieveCatalogItems(toCatalog, subscribedCatalogItems, check)
   286  	}
   287  
   288  	// Synchronising all vApp templates and media items. If the subscription includes local copy,
   289  	// the synchronisation has alredy happened, and this extra call is very quick (~5 seconds)
   290  	drawHeader("-", "synchronising vApp templates and media items")
   291  	tasksVappTemplates, err := toCatalog.LaunchSynchronisationAllVappTemplates()
   292  	check.Assert(err, IsNil)
   293  	tasksMediaItems, err := toCatalog.LaunchSynchronisationAllMediaItems()
   294  	check.Assert(err, IsNil)
   295  
   296  	// Wait for all synchronisation tasks to end
   297  	var allTasks []*Task
   298  	allTasks = append(allTasks, tasksVappTemplates...)
   299  	allTasks = append(allTasks, tasksMediaItems...)
   300  	_, err = WaitTaskListCompletionMonitor(allTasks, testMonitor)
   301  	if !testVerbose {
   302  		fmt.Println()
   303  	}
   304  	check.Assert(err, IsNil)
   305  
   306  	// after a full synchronisation, all data should be available	under every condition
   307  	retrieveCatalogItems(toCatalog, subscribedCatalogItems, check)
   308  
   309  	startDelete := time.Now()
   310  	err = toCatalog.Delete(true, true)
   311  	check.Assert(err, IsNil)
   312  	fmt.Printf("subscribed catalog deletion done in %s\n", time.Since(startDelete))
   313  	startDelete = time.Now()
   314  	if !testData.preservePublishingCatalog {
   315  		err = fromCatalog.Delete(true, true)
   316  		check.Assert(err, IsNil)
   317  		fmt.Printf("published catalog deletion done in %s\n", time.Since(startDelete))
   318  	}
   319  	drawHeader("=", fmt.Sprintf("END: upload %s - local copy: %v - Time taken: %s", testData.uploadWhen, testData.localCopy, time.Since(startSubtest)))
   320  }
   321  
   322  func retrieveCatalogItems(toCatalog *AdminCatalog, subscribed []*types.QueryResultCatalogItemType, check *C) {
   323  	for _, item := range subscribed {
   324  		catalogItem, err := toCatalog.GetCatalogItemByHref(item.HREF)
   325  		check.Assert(err, IsNil)
   326  		switch catalogItem.CatalogItem.Entity.Type {
   327  		case types.MimeVAppTemplate:
   328  			vAppTemplate, err := catalogItem.GetVAppTemplate()
   329  			check.Assert(err, IsNil)
   330  			check.Assert(vAppTemplate.VAppTemplate.HREF, Equals, catalogItem.CatalogItem.Entity.HREF)
   331  		case types.MimeMediaItem:
   332  			mediaItem, err := toCatalog.GetMediaByHref(catalogItem.CatalogItem.Entity.HREF)
   333  			check.Assert(err, IsNil)
   334  			check.Assert(extractUuid(mediaItem.Media.ID), Equals, extractUuid(catalogItem.CatalogItem.Entity.HREF))
   335  		}
   336  	}
   337  }
   338  
   339  func testMonitor(task *types.Task) {
   340  	if testVerbose {
   341  		fmt.Printf("task %s - owner %s - operation %s -  status %s - progress %d\n", task.ID, task.Owner.Name, task.Operation, task.Status, task.Progress)
   342  	} else {
   343  		marker := "."
   344  		if task.Status == "success" {
   345  			marker = "+"
   346  		}
   347  		if task.Status == "error" {
   348  			marker = "-"
   349  		}
   350  		fmt.Print(marker)
   351  	}
   352  }
   353  
   354  func testSubscribedCatalogWithInvalidParameters(org *AdminOrg, subscription types.ExternalCatalogSubscription,
   355  	name, password string, localCopy bool, check *C) {
   356  
   357  	uuid := extractUuid(subscription.Location)
   358  	params := subscription
   359  	params.Location = strings.Replace(params.Location, uuid, "deadbeef-d72f-4a21-a4d2-4dc9e0b36555", 1)
   360  	// Use a valid host with invalid UUID
   361  	_, err := org.CreateCatalogFromSubscriptionAsync(params, nil, name, password, localCopy)
   362  	check.Assert(err, ErrorMatches, ".*RESOURCE_NOT_FOUND.*")
   363  
   364  	newUrl, err := url.Parse(subscription.Location)
   365  	check.Assert(err, IsNil)
   366  
   367  	params = subscription
   368  	params.Location = strings.Replace(params.Location, newUrl.Host, "fake.example.com", 1)
   369  	// use an invalid host
   370  	_, err = org.CreateCatalogFromSubscriptionAsync(params, nil, name, password, localCopy)
   371  	check.Assert(err, ErrorMatches, ".*INVALID_URL_OR_PASSWORD.*")
   372  
   373  	params = subscription
   374  	params.Location = "not-an-URL"
   375  	// use an invalid URL
   376  	_, err = org.CreateCatalogFromSubscriptionAsync(params, nil, name, password, localCopy)
   377  	check.Assert(err, ErrorMatches, ".*UNKNOWN_ERROR.*")
   378  }